helmfile/AGENTS.md

5.0 KiB

AGENTS.md

Build and Test Commands

Essential Setup

# Check Go version (requires 1.24.2+)
go version

# Check Helm dependency (required at runtime)
helm version  # Must show version 3.x

# Install gci tool for import formatting
go install github.com/daixiang0/gci@latest

Build Commands

# Standard build
make build

# Direct build
go build -o helmfile .

# Cross-platform builds
make cross

# Build test tools (required for integration tests)
make build-test-tools

Linting and Formatting

# Run go vet (always available)
make check

# Format code with gci
make fmt

# Run golangci-lint
golangci-lint run

Testing Commands

# Run all unit tests
make test

# Run specific test package
go test -v ./pkg/app/...

# Run single test function
go test -v ./pkg/app/... -run TestSpecificFunction

# Run tests with coverage
go test -v ./pkg/... -coverprofile cover.out -race -p=1
go tool cover -func cover.out

# Run integration tests (requires Kubernetes cluster)
make integration

Code Style Guidelines

Imports

Import ordering (enforced by gci):

  1. Standard library (stdlib)
  2. Default (third-party)
  3. Local (github.com/helmfile/helmfile prefix)

Always use aliases for common stdlib packages to avoid conflicts:

import (
    goContext "context"  // Alias to avoid naming conflicts
    goruntime "runtime"
    "fmt"
    "os"

    "github.com/helmfile/helmfile/pkg/app"
)

Formatting

  • Use go fmt for standard formatting
  • Use gci for import organization: gci write --skip-generated -s standard -s default -s 'prefix(github.com/helmfile/helmfile)' .
  • Run make fmt before committing (requires gci installation)

Types

  • Exported types use PascalCase: type App struct
  • Private types use PascalCase: type helmKey struct
  • Interface names should describe behavior: type Interface interface
  • Use any instead of interface{}

Naming Conventions

  • Exported functions/variables: PascalCase
  • Private functions/variables: camelCase
  • Constants: PascalCase
  • Test functions: TestXxx with descriptive names
  • Package names: lowercase, single word when possible
  • Error variables: ErrXxx

Error Handling

  • Use fmt.Errorf with %w for wrapping errors
  • Check errors explicitly; never ignore
  • Use custom error types in pkg/errors/ when appropriate
  • Return error as last return value
if err != nil {
    return fmt.Errorf("failed to parse helm version '%s': %w", version, err)
}

Testing

  • Use testify/assert for assertions: assert.Equal(t, expected, actual)
  • Use testify/require for critical assertions: require.NoError(t, err)
  • Test files: *_test.go
  • Use table-driven tests for multiple scenarios
  • Run tests with -race flag: -race -p=1
  • Use test helper packages: pkg/testutil, pkg/testhelper
func TestFunctionName(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    string
        wantErr bool
    }{
        {
            name:  "valid input",
            input: "test",
            want:  "result",
        },
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := FunctionUnderTest(tt.input)
            if (err != nil) != tt.wantErr {
                t.Errorf("error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            assert.Equal(t, tt.want, got)
        })
    }
}

Logging

  • Use zap.SugaredLogger: logger.Infof("message")
  • Logger injected via dependency injection
  • Don't use fmt.Print for production logging

Comments

  • Exported functions must have comments
  • Comments should be complete sentences with capital first letter
  • Use // for single-line comments
  • Use /* */ for package documentation

Linting Configuration (from .golangci.yaml)

Key enabled linters:

  • errcheck: Check unhandled errors
  • staticcheck: Advanced static analysis
  • revive: Fast linter
  • govet: Go vet checks
  • ineffassign: Detect ineffectual assignments
  • misspell: Spell checking
  • unused: Detect unused code

Important thresholds:

  • Max function length: 280 lines
  • Max statements per function: 140
  • Max cognitive complexity: 110
  • Max naked return lines: 50
  • Line length: 120 characters

Structure

  • Package main: Entry point only (main.go)
  • cmd/: CLI commands using cobra
  • pkg/: Core library code organized by domain
  • test/: Integration and E2E tests
  • Use dependency injection for testability
  • Prefer composition over inheritance

Critical Rules

  1. Always handle errors
  2. Run make check before committing
  3. Run golangci-lint run and fix all issues
  4. Write tests for new pkg/ functionality
  5. Update docs/ for user-facing changes
  6. Follow declarative design principles (desired state in config, operational via flags)

Common Issues

  • First build downloads 200+ packages (2-3 minutes)
  • Integration tests require Kubernetes cluster (minikube/kind)
  • Make fmt requires gci installation
  • Use -p=1 for tests to avoid race conditions
  • Always initialize Helm plugins with helmfile init after installation