Skip to content

Latest commit

 

History

History
243 lines (188 loc) · 8.77 KB

AGENTS.md

File metadata and controls

243 lines (188 loc) · 8.77 KB

AGENTS.md - Quick Start Guide for AI Agents

This is a quick reference guide for AI agents working with tf-version-bump. For comprehensive development documentation, see CLAUDE.md.

What is tf-version-bump?

A CLI tool written in Go that updates Terraform module versions, Terraform required_version, and provider versions across multiple files using glob patterns.

Key tech: Go 1.25+, HashiCorp HCL library, YAML configs

Quick Setup

# 1. Install dependencies
go mod download && go mod verify

# 2. Build
go build -o tf-version-bump

# 3. Test
go test -v -race -coverprofile=coverage.out -covermode=atomic ./...

# 4. Lint
golangci-lint run --timeout=5m

Project Structure

tf-version-bump/
├── main.go                  # Core logic
├── config.go                # YAML config handling
├── audit.go                 # -audit-file comparison
├── *_test.go                # Comprehensive tests (Go tests)
├── schema/config-schema.json # YAML validation schema
├── examples/                # Sample configs, .tf files, scenarios, and branch and Actions automation
├── scripts/                 # Lint launchers and the Actions example's release-pin updater
├── .github/workflows/       # CI/CD pipelines
└── docs/                    # Usage, configuration, automation, and release guides

Core Files

File Purpose
main.go CLI parsing, HCL processing, version updates
config.go YAML config loading and validation
audit.go Read-only -audit-file comparison of files with a config
Focused *_test.go files Contract owners by responsibility; see the detailed test layout in CLAUDE.md
config_test.go Config validation tests
schema/config-schema.json JSON Schema for YAML

Essential Commands

Building

go build -o tf-version-bump                              # Local build
GOOS=linux GOARCH=amd64 go build -o tf-version-bump     # Cross-compile

Testing

go test -v ./...                                          # Quick test
go test -v -race -coverprofile=coverage.out -covermode=atomic ./...  # Full CI test
make test-coverage                                        # With coverage report

Linting

golangci-lint run --timeout=5m                           # Run linter
golangci-lint run --fix                                   # Auto-fix issues
make actionlint                                           # Workflows, including the Actions example
make shellcheck                                           # Every tracked shell script

Running the Tool

# Single module update
./tf-version-bump -pattern "*.tf" -module "terraform-aws-modules/vpc/aws" -to "5.0.0"

# Batch update with config ('**' recurses to any depth; .terraform and .git are skipped)
./tf-version-bump -pattern "**/*.tf" -config examples/config-basic.yml

# Dry run
./tf-version-bump -pattern "*.tf" -module "..." -to "..." -dry-run

# CI check: 0 with no eligible update, 2 when updates are required, 1 on error
./tf-version-bump -pattern "**/*.tf" -config examples/config-basic.yml -check

# Update Terraform version
./tf-version-bump -pattern "*.tf" -terraform-version ">= 1.5"

# Update provider version
./tf-version-bump -pattern "*.tf" -provider aws -to "~> 5.0"

Key Architecture Patterns

HCL Processing (main.go)

main.go reads and writes every Terraform file through readTerraformFile and terraformFile.write; reuse them for any new update path.

// 1. Stat, read and parse; a file a failed write left untrusted is refused
file, err := readTerraformFile(filename)

// 2. Navigate and modify
for _, block := range file.hcl.Body().Blocks() {
    if block.Type() == "module" {
        block.Body().SetAttributeValue("version", cty.StringVal(targetVersion))
    }
}

// 3. Format and rewrite in place; a failed rewrite writes the original bytes back
err = file.write()

Config Loading (config.go)

// Strict YAML parsing with validation
config, err := loadConfig(filename)
// Custom FromVersions type accepts string or []string
// Whitespace trimming and validation

Code Conventions

  • Package: Single main package (flat structure)
  • Style: Standard Go formatting (go fmt)
  • Errors: Wrapped with fmt.Errorf("context: %w", err)
  • Tests: Table-driven tests with t.Run() subtests
  • Temp files: Always use t.TempDir() in tests

Testing Patterns

// Standard test structure
func TestExample(t *testing.T) {
    tmpDir := t.TempDir()
    testFile := filepath.Join(tmpDir, "test.tf")

    // Setup
    initial := `module "vpc" { source = "aws/vpc" version = "1.0.0" }`
    os.WriteFile(testFile, []byte(initial), 0644)

    // Execute (fromVersions, ignoreVersions, ignorePatterns, forceAdd, dryRun, verbose, outputFormat)
    updated, err := updateModuleVersion(testFile, "aws/vpc", "2.0.0", nil, nil, nil, false, false, false, "text")

    // Assert
    if err != nil {
        t.Fatalf("updateModuleVersion failed: %v", err)
    }
    if !updated {
        t.Error("Expected module to be updated")
    }

    // Verify
    result, _ := os.ReadFile(testFile)
    if !strings.Contains(string(result), `version = "2.0.0"`) {
        t.Errorf("Version not updated correctly, got: %s", result)
    }
}

Common Operations

Adding a Feature

  1. Update main.go or config.go
  2. Add CLI flag in parseFlags() if needed
  3. Add config field to Config struct if needed
  4. Update schema/config-schema.json for YAML validation
  5. Add tests in *_test.go
  6. Update the appropriate user guide under docs/ and keep the README quick start concise
  7. Run full validation: make test-coverage && golangci-lint run && make actionlint && make shellcheck

Debugging

go test -v -run TestSpecific                  # Run specific test
go test -coverprofile=coverage.out           # Generate coverage
go tool cover -html=coverage.out             # View coverage HTML
make test-coverage                            # Coverage with report

CI/CD

GitHub Actions runs on every push/PR:

  • Test: matrix of Go 1.25.14 (go.mod floor) and 1.26.8, race detection, coverage upload
  • Build: 6 platforms (Linux/macOS/Windows × amd64/arm64)
  • Lint: golangci-lint, then make actionlint and make shellcheck with pinned shellcheck
  • Documentation: make docs-check for Markdown, schema, maintained example, and documentation-test changes

Version Filtering Logic

Priority order across updateModuleBlockResult and shouldSkipModuleVersion in main.go:

  1. Source and locality: Require an exact source match; skip local sources
  2. Ignore patterns: If module name matches ignore_modules, skip; an entry may be branch-scoped as <branch>/<name> and resolved against -branch beforehand
  3. Missing version: Skip unless force-add is enabled and the source is a registry module
  4. Ignore versions: If current version is in ignore_versions, skip (takes precedence)
  5. From filter: If from is set and current version doesn't match, skip
  6. Update: If all checks pass, update the version

Quick Tips

  • ✓ Always read files before modifying - Use Read tool first

  • ✓ Run tests with race detector - go test -race

  • ✓ Use dry-run - Preview changes before applying

  • ✓ Check coverage - make test-coverage

  • ✓ Follow existing patterns - Match the codebase style

  • ✓ Update docs - Keep the README, detailed guides, and agent documentation in sync

  • ✓ Keep Markdown unwrapped - One line per paragraph; make docs-check enforces it

  • ✗ Never break HCL format - Use hclwrite API only

  • ✗ Don't skip linting - CI will fail

  • ✗ Don't change public API - CLI flags are user-facing

  • ✗ Don't commit binaries - They're gitignored

Dependencies

Direct dependencies — see go.mod for versions:

github.com/hashicorp/hcl/v2      # Official HCL parser
github.com/hashicorp/terraform-registry-address # Registry module source parser
github.com/zclconf/go-cty        # HCL type system
go.yaml.in/yaml/v3               # YAML parsing
github.com/bmatcuk/doublestar/v4 # Recursive '**' globbing

Resources


Need more details? See CLAUDE.md for comprehensive development documentation.