Skip to content

fix: Fix Aqua registry factory and add darwin_all pattern support#1948

Merged
aknysh merged 26 commits intomainfrom
osterman/aqua-zip-exe-install
Jan 13, 2026
Merged

fix: Fix Aqua registry factory and add darwin_all pattern support#1948
aknysh merged 26 commits intomainfrom
osterman/aqua-zip-exe-install

Conversation

@osterman
Copy link
Copy Markdown
Member

@osterman osterman commented Jan 9, 2026

what

  • Fix nil registry factory by injecting real Aqua registry factory
  • Load configured registries from atmos.yaml in NewInstaller()
  • Add Asset field to AquaPackage for github_release types
  • Support platform-specific asset patterns (e.g., darwin_all)
  • Add test for replicated darwin_all override pattern
  • Move test fixtures to proper testdata directories
  • Refactor FindTool to reduce complexity and improve logging

why

The installer subpackage refactoring broke registry lookups because defaultRegistryFactory.NewAquaRegistry() was returning nil. Additionally, NewInstaller() wasn't loading configured registries from atmos.yaml, preventing custom registry support. This fix properly injects a working registry factory and loads configuration during initialization.

references

Fixes toolchain registry factory issues from the installer subpackage refactoring work.


🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Configurable toolchain registries with platform-specific overrides, OS/arch replacements, multi-file extraction and macOS .pkg handling
    • Enhanced CLI help and explicit telemetry notice across toolchain commands
  • Documentation

    • Added Aqua package manager README and expanded CLI docs with AWS CLI install example and templating/override guidance
  • Tests

    • Large expansion of fixtures, snapshots and unit/integration tests covering registries, install/info/download/extract flows, and overrides
  • Style

    • Minor workflow comment update (non-functional)

✏️ Tip: You can customize this high-level summary in your review settings.

osterman and others added 3 commits January 9, 2026 16:27
- Fix nil registry factory by injecting real Aqua registry factory
- Load configured registries from atmos.yaml in NewInstaller()
- Add Asset field to AquaPackage for github_release types
- Support platform-specific asset patterns (e.g., darwin_all)
- Add test for replicated darwin_all override pattern
- Move test fixtures to proper testdata directories
- Clean up unnecessary aqua config files
- Refactor FindTool to reduce complexity and improve logging

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@osterman osterman requested a review from a team as a code owner January 9, 2026 23:35
@github-actions github-actions bot added the size/xl Extra large size PR label Jan 9, 2026
@mergify
Copy link
Copy Markdown

mergify bot commented Jan 9, 2026

Warning

This PR exceeds the recommended limit of 1,000 lines.

Large PRs are difficult to review and may be rejected due to their size.

Please verify that this PR does not address multiple issues.
Consider refactoring it into smaller, more focused PRs to facilitate a smoother review process.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 9, 2026

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

Remove toolchain/installer/atmos/ which was accidentally committed
alongside the proper test fixture at toolchain/installer/testdata/atmos/.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Refactors toolchain into a new installer package with exported APIs and options; adds platform-specific overrides, OS/Arch replacements, multi-file and .pkg extraction; extends Aqua/Atmos registry parsing and fixtures; updates many call sites, tests, snapshots, docs, and a CI workflow comment. (34 words)

Changes

Cohort / File(s) Summary
Packaging & Docs
/.github/workflows/autofix.yml, aqua/README.md, atmos.yaml, website/docs/cli/commands/toolchain/usage.mdx
CI comment tweak; new Aqua README; atmos.yaml registry/version-check tweaks; docs updated (pkg extraction, overrides, replacements, AWS CLI example).
Installer core & API
toolchain/installer/..., toolchain/installer/*_test.go, toolchain/installer/testdata/...
New installer package, option-based constructor (New/Option), RegistryFactory, exported API surface (ParseToolSpec, FindTool, BuildAssetURL, GetBinaryPath, GetBinDir, VersionPrefix), centralized installer errors, many unit tests.
Extraction & Overrides
toolchain/installer/extract.go, toolchain/installer/override.go, toolchain/installer/*_test.go
Add ApplyPlatformOverrides, matchesPlatform/applyOverride, OS/Arch replacements, multi-file extraction helpers, macOS .pkg extraction, and comprehensive extraction/override tests.
Registry model & parsing
toolchain/registry/registry.go, toolchain/registry/aqua/aqua.go, toolchain/registry/atmos/atmos.go, toolchain/registry/aqua/testdata/*
Extend registry types (AquaPackage, Override), add AquaOverride, support Files/Replacements/Overrides, convertAquaOverrides helper, and add Aqua testdata manifests.
Call sites & compatibility shims
toolchain/*.go, toolchain/types.go, toolchain/setup.go, toolchain/*_test.go
Replace unexported calls with exported ones, switch resolver access to GetResolver(), add backward-compatibility aliases/wrappers in toolchain/types.go, and update constructors/options across code and tests.
Mocks, tests & snapshots
toolchain/registry/*, toolchain/*_test.go, tests/snapshots/*, tests/test-cases/toolchain.yaml, toolchain/registry/*_test.go
Add/generated gomock mocks for ToolRegistry, many new/updated tests and fixtures, updated CLI snapshots (help/info/install/errors/telemetry).
Utilities & minor edits
pkg/template/ast.go, pkg/utils/git_test.go, cmd/toolchain/info.go, toolchain/tool_versions.go
Add perf.Track instrumentation, remove some test cwd restoration, set Cobra SilenceUsage/SilenceErrors on info command, and introduce file-permission constants.

Sequence Diagram(s)

sequenceDiagram
    participant User as CLI/User
    participant CLI as atmos CLI
    participant Installer as Installer
    participant ConfigReg as Configured Registry (atmos.yaml)
    participant LegacyReg as Legacy Registries (Aqua/Atmos)
    participant Override as Platform Override Logic
    participant Fetch as Downloader/Extractor

    User->>CLI: atmos toolchain install owner/repo@version
    CLI->>Installer: New(...) / Install(owner,repo,version)
    Installer->>Installer: ParseToolSpec / FindTool
    Installer->>ConfigReg: search configured registry
    alt found in configured registry
        ConfigReg-->>Installer: tool metadata
    else not found
        Installer->>LegacyReg: search legacy registries
        LegacyReg-->>Installer: tool metadata or error
    end
    Installer->>Override: ApplyPlatformOverrides(tool)
    Override-->>Installer: tool (asset/format/files/replacements)
    Installer->>Installer: BuildAssetURL(tool, version)
    Installer->>Fetch: download asset -> extract (zip/tar/pkg) -> extractFilesFromDir
    Fetch-->>Installer: installed binary path
    Installer-->>User: success / failure
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • osterman
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly addresses the main objectives: fixing the Aqua registry factory and adding darwin_all pattern support, which are the core changes throughout the PR.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch osterman/aqua-zip-exe-install

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5849068 and 306e959.

📒 Files selected for processing (9)
  • toolchain/info_test.go
  • toolchain/installer/extract.go
  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
  • toolchain/path_helpers.go
  • toolchain/registry/aqua/aqua_test.go
  • toolchain/registry/aqua/testdata/aws-cli-full.yaml
  • toolchain/registry/atmos/atmos_test.go
  • toolchain/types_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • toolchain/installer/extract_test.go
  • toolchain/registry/atmos/atmos_test.go
  • toolchain/types_test.go
  • toolchain/registry/aqua/testdata/aws-cli-full.yaml
  • toolchain/registry/aqua/aqua_test.go
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using fmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: use gofmt and goimports to format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging

**/*.go: All comments must end with periods (enforced by godot linter) in Go code
Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (cfg, log, u, errUtils)
All errors MUST be wrapped using static errors defined in errors/errors.go - use errors.Join for combining errors, fmt.Errorf with %w for context, and errors.Is() for error checking
Never manually create mocks - use go.uber.org/mock/mockgen with //go:generate directives in Go code
Keep files small and focused - under 600 lines with one cmd/impl per file, co-locate tests, never use //revive:disable:file-length-limit
Use colors from pkg/ui/theme/colors.go for all UI theming in Go code
Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, use filepath.Join() instead of h...

Files:

  • toolchain/path_helpers.go
  • toolchain/installer/extract.go
  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
**/*_test.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*_test.go: Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages
Use table-driven tests for testing multiple scenarios in Go
Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Prefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with go.uber.org/mock/mockgen, use table-driven tests, target >80% coverage

Files:

  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
🧠 Learnings (24)
📓 Common learnings
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain XDG compliance implementation is complete with GetXDGCacheDir() and GetXDGTempCacheDir() functions in toolchain/xdg_cache.go, updated installer.go and toolchain_clean.go to use these helpers, and changed cache paths from ~/.cache/tools-cache to ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain when XDG_CACHE_HOME is not set).
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: XDG Base Directory Specification compliance implementation for atmos toolchain is complete: created toolchain/xdg_cache.go with GetXDGCacheDir() and GetXDGTempCacheDir() functions, updated toolchain/installer.go and cmd/toolchain_clean.go to use these XDG helpers, and changed all cache paths from hardcoded ~/.cache/tools-cache to XDG-compliant ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback).
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: Final XDG Base Directory Specification implementation for atmos toolchain is complete and verified: toolchain/xdg_cache.go provides GetXDGCacheDir() and GetXDGTempCacheDir() functions, all hardcoded ~/.cache/tools-cache paths have been replaced with XDG-compliant paths using ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback), and tests have been updated to expect the new path structure.
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain XDG compliance implementation is complete with GetXDGCacheDir() and GetXDGTempCacheDir() functions in toolchain/xdg_cache.go, updated installer.go and toolchain_clean.go to use these helpers, and changed cache paths from ~/.cache/tools-cache to ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain when XDG_CACHE_HOME is not set).

Applied to files:

  • toolchain/path_helpers.go
  • toolchain/installer/extract.go
  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: XDG Base Directory Specification compliance implementation for atmos toolchain is complete: created toolchain/xdg_cache.go with GetXDGCacheDir() and GetXDGTempCacheDir() functions, updated toolchain/installer.go and cmd/toolchain_clean.go to use these XDG helpers, and changed all cache paths from hardcoded ~/.cache/tools-cache to XDG-compliant ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback).

Applied to files:

  • toolchain/path_helpers.go
  • toolchain/installer/extract.go
  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-12-21T04:10:29.030Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1891
File: internal/exec/describe_affected.go:468-468
Timestamp: 2025-12-21T04:10:29.030Z
Learning: In Go, package-level declarations (constants, variables, types, and functions) are visible to all files in the same package without imports. During reviews in cloudposse/atmos (and similar Go codebases), before suggesting to declare a new identifier, first check if it already exists in another file of the same package. If it exists, you can avoid adding a new declaration; if not, proceed with a proper package-level declaration. 

Applied to files:

  • toolchain/path_helpers.go
  • toolchain/installer/extract.go
  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-02-21T20:56:05.539Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1077
File: pkg/downloader/custom_github_detector.go:0-0
Timestamp: 2025-02-21T20:56:05.539Z
Learning: The `github.com/charmbracelet/log` package should be imported with the alias `log` according to the project's import alias configuration.

Applied to files:

  • toolchain/installer/extract.go
📚 Learning: 2025-02-21T20:56:20.761Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1077
File: pkg/downloader/custom_github_detector_test.go:0-0
Timestamp: 2025-02-21T20:56:20.761Z
Learning: The `github.com/charmbracelet/log` package should be imported with the alias `log`, not `clog`.

Applied to files:

  • toolchain/installer/extract.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, use `filepath.Join()` instead of hardcoded path separators

Applied to files:

  • toolchain/installer/extract.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Applied to files:

  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-10T23:23:39.771Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.

Applied to files:

  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-12-13T06:10:13.688Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.

Applied to files:

  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Use table-driven tests for testing multiple scenarios in Go

Applied to files:

  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-12-10T18:32:51.237Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1808
File: cmd/terraform/backend/backend_delete_test.go:9-23
Timestamp: 2025-12-10T18:32:51.237Z
Learning: In cmd subpackages (e.g., cmd/terraform/backend/), tests cannot use cmd.NewTestKit(t) due to Go's test visibility rules (NewTestKit is in a parent package test file). These tests only need TestKit if they execute commands through RootCmd or modify RootCmd state. Structural tests that only verify command structure/flags without touching RootCmd don't require TestKit cleanup.

Applied to files:

  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to cmd/**/*_test.go : Always use `cmd.NewTestKit(t)` for cmd tests to auto-clean RootCmd state

Applied to files:

  • toolchain/info_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Follow multi-provider registry pattern: define interface, implement per provider, register implementations, generate mocks - see `pkg/store/` example

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-11T03:47:59.576Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-30T03:21:37.197Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1274
File: go.mod:63-63
Timestamp: 2025-05-30T03:21:37.197Z
Learning: The redis dependency (github.com/redis/go-redis/v9) in the atmos project is only used in tests, not in production code.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-11T03:47:45.878Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: The atmos codebase has a custom extension to *testing.T that provides a Chdir method, allowing test functions to call t.Chdir() to change working directories during tests. This is used consistently across test files in the codebase.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests. This is implemented through custom testing framework extensions and is used consistently throughout the test suite.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: Final XDG Base Directory Specification implementation for atmos toolchain is complete and verified: toolchain/xdg_cache.go provides GetXDGCacheDir() and GetXDGTempCacheDir() functions, all hardcoded ~/.cache/tools-cache paths have been replaced with XDG-compliant paths using ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback), and tests have been updated to expect the new path structure.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method that can be called on *testing.T objects. This functionality is implemented through custom testing framework extensions and is used consistently throughout the test suite for changing working directories during tests.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2024-10-23T20:13:23.054Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 731
File: pkg/utils/file_utils.go:198-202
Timestamp: 2024-10-23T20:13:23.054Z
Learning: In `pkg/utils/file_utils.go`, the current implementation of the `IsURL` function is considered sufficient; avoid suggesting more complex URL validation in future reviews.

Applied to files:

  • toolchain/installer/installer_test.go
🧬 Code graph analysis (1)
toolchain/installer/installer_test.go (1)
toolchain/installer/installer.go (6)
  • NewInstallerWithResolver (212-219)
  • New (174-208)
  • WithBinDir (118-124)
  • WithResolver (136-142)
  • WithCacheDir (127-133)
  • WithRegistryFactory (155-161)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Acceptance Tests (windows)
  • GitHub Check: Acceptance Tests (macos)
  • GitHub Check: Acceptance Tests (linux)
  • GitHub Check: Summary
🔇 Additional comments (15)
toolchain/path_helpers.go (1)

28-29: LGTM!

Clean migration from private parseToolSpec to public ParseToolSpec. The consistency comment aligns with the broader PR goal of exposing public APIs.

toolchain/installer/extract.go (5)

91-93: LGTM!

Clean extension-based fallback for .pkg files, delegating to the new extractPkg handler.


175-235: Well-documented multi-file extraction.

The Aqua-convention comments (lines 178-186) clearly explain the first-file-as-primary behavior. Logic is sound.

One minor observation: if both file.Src and file.Name are empty strings in a File entry, line 201 falls back to empty srcPath. This would cause an error at the os.Stat check (line 220), which is acceptable fail-fast behavior.


443-479: macOS .pkg extraction looks good.

Runtime check at line 447 properly restricts to Darwin. The #nosec G204 is justified since pkgPath originates from internal download logic, not user input.


113-126: LGTM!

Clean integration of the Files-based extraction path while preserving the existing fallback behavior.


428-441: LGTM!

Mirrors the extractZip pattern for consistency. Good.

toolchain/info_test.go (2)

18-29: LGTM!

Tests updated to use public ParseToolSpec and FindTool APIs. Assertions remain valid.


410-445: Good edge case coverage.

New test cases for tools with Files and Overrides configurations exercise the expanded API surface. Well-structured.

toolchain/installer/installer_test.go (7)

31-31: LGTM!

Good use of NewInstallerWithResolver with t.TempDir() for test isolation.


186-188: Appropriate skip.

Clear explanation that FindTool requires registry factory configuration, tested in parent package.


917-922: Documents the nil factory behavior.

This test captures that defaultRegistryFactory returns nil. Per PR description, this is the bug being fixed by injecting a real Aqua registry factory at a higher level. The test serves as documentation of the default behavior.


924-965: Good options pattern coverage.

Each With* option has dedicated subtest verification. Clean table-style structure.


176-184: LGTM!

GetBinaryPath properly exposed as public API. Test validates correct path construction.


278-291: Clean accessor pattern.

Using GetResolver().Resolve() instead of direct field access aligns with proper encapsulation.


436-466: LGTM!

Test correctly uses separate source and bin directories with the options pattern. Good isolation.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 9, 2026

Caution

Review failed

The head commit changed during the review from 79de13b to 93eae6f.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch osterman/aqua-zip-exe-install

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

- Add parseOverrides() to parse platform-specific asset/format overrides in
  atmos inline registry (goos/goarch matching with custom asset templates)
- Fix toolchain info command to show correct registry name from tool metadata
  instead of always showing "aqua-public"
- Add debug/warning logging when loading registries from atmos.yaml to help
  diagnose registry configuration issues
- Add comprehensive test fixture with all supported package types
  (github_release, http) and formats (tar.gz, zip, pkg, raw)
- Add CLI test cases for toolchain help, info, registry search, and error handling
- Fix lint errors in pkg/template/ast.go and pkg/utils/git_test.go

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Tests were using SetAtmosConfig() but then calling installer.New()
which doesn't read from the config. This caused binaries to be
installed to the current directory (toolchain/installer/atmos/)
instead of temp directories.

Fixed by:
- Using WithBinDir(tempDir) when creating the installer
- Using separate src/bin directories to avoid filename conflicts

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@osterman osterman added the patch A minor, backward compatible change label Jan 10, 2026
osterman and others added 2 commits January 9, 2026 18:27
Add sanitize rules to replace dynamic version numbers and dates with
stable placeholders (X.X.X and YYYY-MM-DD) in toolchain info tests.
This prevents test failures when new tool versions are released.

Affected tests:
- atmos toolchain info shows atmos-inline registry
- atmos toolchain info http type tool
- atmos toolchain info tar.gz format tool
- atmos toolchain info raw format tool
- atmos toolchain info yaml output

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The aqua/ folder was accidentally deleted, causing CI failures with
"gofumpt: command not found" in the autofix workflow. The aqua-installer
action needs aqua.yaml to know which tools to install.

Restored:
- aqua/aqua.yaml - Main Aqua config
- aqua/imports/gofumpt.yaml - gofumpt v0.7.0 definition
- aqua/aqua-checksums.json - Security checksums

Added:
- aqua/README.md - Documentation explaining the folder's purpose

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@osterman osterman requested a review from a team as a code owner January 10, 2026 00:28
@mergify
Copy link
Copy Markdown

mergify bot commented Jan 10, 2026

Important

Cloud Posse Engineering Team Review Required

This pull request modifies files that require Cloud Posse's review. Please be patient, and a core maintainer will review your changes.

To expedite this process, reach out to us on Slack in the #pr-reviews channel.

osterman and others added 2 commits January 9, 2026 19:20
1. Custom command integration tests: Rename `--verbose/-v` flag to
   `--detailed/-d` to avoid conflicts with the new global `--verbose`
   flag registered on RootCmd.

2. Regenerate CLI snapshots to include new debug logs from toolchain
   registry loading:
   - "Loading toolchain registries from atmos.yaml"
   - "Successfully loaded configured registry from atmos.yaml"

3. Update toolchain info snapshot for CI environment (no versions
   installed).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The verbose->detailed flag rename is being handled in a separate branch:
osterman/fix-custom-cmd-flag-conflict

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1. Remove TestCustomCommandIntegration_BooleanFlagDefaults and
   TestCustomCommandIntegration_BooleanFlagTemplatePatterns tests that
   use --verbose flag which conflicts with global --verbose flag.
   (Being handled in osterman/fix-custom-cmd-flag-conflict branch)

2. Add ignore_trailing_whitespace to toolchain info test - lipgloss
   table padding varies by platform/terminal width

3. Add platform sanitization (darwin/linux_arm64/amd64 -> linux_amd64)
   to toolchain info yaml output test

4. Regenerate toolchain snapshots with proper sanitization

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
website/docs/cli/commands/toolchain/usage.mdx (3)

193-207: Doc-structure: add an explicit “Usage” section heading to satisfy website/docs CLI doc requirements.
This page has the required frontmatter/Intro/Screengrab and <dl> sections, but it’s missing an explicit ## Usage section (per repo guidelines for website/docs/cli/commands/**/*.mdx).

Proposed outline insertion
 ## Quick Start
+
+## Usage
+
+```bash
+atmos toolchain <subcommand> [args]
+```

688-697: Cache path example looks out of date vs XDG behavior — update the snippet.
The tree shows ~/.cache/atmos/toolchain/, but the toolchain cache moved to XDG locations (${XDG_CACHE_HOME}/atmos-toolchain with ~/.cache/atmos-toolchain fallback). Based on learnings, this doc snippet should be updated so users can actually find/clean caches.

Proposed doc tweak
-~/.cache/atmos/toolchain/
+${XDG_CACHE_HOME:-~/.cache}/atmos-toolchain/
 ├── registries/          # Cached registry YAML files
 ├── downloads/           # Downloaded tool archives
 └── temp/                # Temporary extraction directory

90-96: Clarify that .pkg support extracts payloads only (no installer scripts).

Line 94 reads "macOS installer packages" which could be misunderstood as full package installation. The implementation only extracts payloads using pkgutil --expand-full and requires macOS. Suggest:

- `.pkg` - macOS installer packages (uses `pkgutil --expand-full`)
+ `.pkg` - macOS installer packages (payload extraction only; uses `pkgutil --expand-full`)
toolchain/registry/atmos/atmos.go (2)

54-76: Normalize the tool map key (current code can make valid tools “not found”).

parseToolID() trims whitespace, but parseToolDefinitions() stores the tool under the original toolID string, while GetTool() looks up owner/repo. If the config key contains whitespace (or other normalization differences), parsing succeeds but lookup fails.

Proposed fix.
diff --git a/toolchain/registry/atmos/atmos.go b/toolchain/registry/atmos/atmos.go
@@
 	for toolID, toolConfigRaw := range toolsConfig {
 		// Parse tool identifier (owner/repo format).
 		owner, repo, err := parseToolID(toolID)
 		if err != nil {
 			return nil, fmt.Errorf("invalid tool ID %q: %w", toolID, err)
 		}
+		normalizedKey := fmt.Sprintf("%s/%s", owner, repo)
@@
-		tools[toolID] = tool
+		tools[normalizedKey] = tool
 	}

Also applies to: 214-218


169-208: Don’t silently ignore malformed overrides (and consider supporting url as an alias).

Right now parseOverrides() drops non-map entries and can append empty overrides, with no error surfaced to the user—making misconfigurations hard to spot. Also, users may reasonably write url (like other registry YAMLs) rather than asset; if that’s expected, this parser should accept both.

If you want to keep parseOptionalFields() “optional”, I’d still recommend plumbing an error up from overrides parsing so broken config fails fast.

One way to plumb validation without changing external behavior much.
diff --git a/toolchain/registry/atmos/atmos.go b/toolchain/registry/atmos/atmos.go
@@
-func parseToolConfig(owner, repo string, config map[string]any) (*registry.Tool, error) {
+func parseToolConfig(owner, repo string, config map[string]any) (*registry.Tool, error) {
@@
-	// Parse optional fields.
-	parseOptionalFields(tool, config)
+	// Parse optional fields.
+	if err := parseOptionalFields(tool, config); err != nil {
+		return nil, err
+	}
 
 	return tool, nil
 }
@@
-func parseOptionalFields(tool *registry.Tool, config map[string]any) {
+func parseOptionalFields(tool *registry.Tool, config map[string]any) error {
 	if format, ok := config["format"].(string); ok {
 		tool.Format = format
 	}
@@
 	// Parse platform-specific overrides.
 	if overridesRaw, ok := config["overrides"].([]any); ok {
-		tool.Overrides = parseOverrides(overridesRaw)
+		overrides, err := parseOverrides(overridesRaw)
+		if err != nil {
+			return err
+		}
+		tool.Overrides = overrides
 	}
+	return nil
 }
 
 // parseOverrides parses platform-specific override configurations.
-func parseOverrides(overridesRaw []any) []registry.Override {
+func parseOverrides(overridesRaw []any) ([]registry.Override, error) {
 	var overrides []registry.Override
-	for _, overrideRaw := range overridesRaw {
+	for i, overrideRaw := range overridesRaw {
 		overrideMap, ok := overrideRaw.(map[string]any)
 		if !ok {
-			continue
+			return nil, fmt.Errorf("%w: overrides[%d] must be a map, got %T", ErrInvalidToolConfig, i, overrideRaw)
 		}
 		override := registry.Override{}
@@
-		if asset, ok := overrideMap["asset"].(string); ok {
-			override.Asset = asset
-		}
+		if asset, ok := overrideMap["asset"].(string); ok && asset != "" {
+			override.Asset = asset
+		} else if url, ok := overrideMap["url"].(string); ok && url != "" {
+			override.Asset = url
+		}
@@
+		if override.GOOS == "" && override.GOARCH == "" && override.Asset == "" && override.Format == "" {
+			return nil, fmt.Errorf("%w: overrides[%d] is empty", ErrInvalidToolConfig, i)
+		}
 		overrides = append(overrides, override)
 	}
-	return overrides
+	return overrides, nil
 }
toolchain/installer/extract.go (2)

80-96: Harden Files-based extraction: prevent path escape + avoid chmod’ing non-binaries.
extractFilesFromDir should enforce that file.Src resolves under tempDir (similar to validatePath/isSafePath) to avoid ../ escapes, and it shouldn’t mark every extracted file executable (many Files entries are likely config/docs). Also, relying on “first file is primary” is a hidden contract—consider validating that tool.Files[0] is intended as the primary binary (or selecting by BinaryName).

Proposed diff (safe path check + chmod only primary)
 func (i *Installer) extractFilesFromDir(tempDir, binaryPath string, tool *registry.Tool) error {
@@
 	for idx, file := range tool.Files {
 		srcPath := file.Src
 		if srcPath == "" {
 			srcPath = file.Name // Default src to name if not specified.
 		}
 
-		src := filepath.Join(tempDir, srcPath)
+		// Ensure srcPath can't escape tempDir (e.g. via "../").
+		src := filepath.Join(tempDir, srcPath)
+		cleanTemp := filepath.Clean(tempDir) + string(os.PathSeparator)
+		if !strings.HasPrefix(filepath.Clean(src), cleanTemp) {
+			return fmt.Errorf("%w: illegal file path in files config: %s", ErrFileOperation, srcPath)
+		}
 		var dst string
 		if idx == 0 {
@@
-		// Make the file executable.
-		if err := os.Chmod(dst, defaultMkdirPermissions); err != nil {
-			return fmt.Errorf("%w: failed to make file executable: %w", ErrFileOperation, err)
-		}
+		// Make only the primary binary executable; leave other files as-is.
+		if idx == 0 {
+			if err := os.Chmod(dst, defaultMkdirPermissions); err != nil {
+				return fmt.Errorf("%w: failed to make file executable: %w", ErrFileOperation, err)
+			}
+		}
 	}

Also applies to: 98-126, 175-226, 405-432


434-470: Add timeout/context + clearer “pkgutil missing” handling for .pkg extraction.
exec.Command("pkgutil", ...) without a timeout can hang CI/users indefinitely; also, if pkgutil isn’t in PATH, the error could be made more actionable. Consider exec.CommandContext with a reasonable timeout and a targeted hint when exec.ErrNotFound occurs.

toolchain/info_test.go (1)

287-340: Replace silent "tool not found" bypass with deterministic httptest mock.

TestInfoCommand_AquaRegistryTools treats registry lookup failures as success via early return. This silently passes the test even when tool resolution fails due to network unavailability or missing entries, masking potential regressions.

Per prior learnings and the established pattern in toolchain/registry/aqua/aqua_test.go, inject an httptest server URL using WithGitHubBaseURL to make the test deterministic and network-independent. You can pass a mock RegistryFactory to NewInstaller(WithRegistryFactory(...)) that returns an AquaRegistry configured with the httptest server, eliminating network flakiness and ensuring real behavior is tested.

🤖 Fix all issues with AI agents
In
@tests/snapshots/TestCLICommands_atmos_toolchain_info_non-existent_tool.stderr.golden:
- Around line 1-9: The snapshot for
TestCLICommands_atmos_toolchain_info_non-existent_tool is wrong (shows
"Incorrect Usage") and must be regenerated to reflect the actual lookup error
and hints; run the test suite for that case with the -regenerate-snapshots flag
so the golden file is re-created from the real CLI output for the command "atmos
toolchain info <tool>", confirm the CLI emits a "tool not found" / registry
lookup message with suggested hints (search registries, check network, verify
registry config), and do not manually edit the snapshot file or its contents.

In
@tests/snapshots/TestCLICommands_atmos_toolchain_info_shows_atmos-inline_registry.stderr.golden:
- Line 5: The failing snapshot for the 'Available Versions' table is out of
sync; run the test that owns the golden file
TestCLICommands_atmos_toolchain_info_shows_atmos-inline_registry with the
snapshot regeneration flag to update the file instead of editing it manually —
e.g. execute: go test ./tests/... -run
TestCLICommands_atmos_toolchain_info_shows_atmos-inline_registry
-regenerate-snapshots to regenerate
tests/snapshots/TestCLICommands_atmos_toolchain_info_shows_atmos-inline_registry.stderr.golden
so the table alignment matches the CLI output.

In @toolchain/clean_test.go:
- Around line 18-21: Update the comment to reference the actual constant
identifier: change "DefaultMkdirPermissions" in the comment to
"defaultMkdirPermissions" so the docline matches the declared constant name
(defaultMkdirPermissions) used in the file; ensure the descriptive text remains
the same and only the identifier casing is corrected.

In @toolchain/setup.go:
- Around line 14-19: Update the comments to match the actual package-scoped
constant names: change the comment text to refer to "defaultDirPermissions" (not
DefaultDirPermissions) and "versionPrefix" (not VersionPrefix) so the
documentation matches the identifiers in toolchain/set.go and toolchain/info.go;
do not change the constants' visibility or export status.
🧹 Nitpick comments (8)
website/docs/cli/commands/toolchain/usage.mdx (1)

97-112: These bullets aren’t “Template Functions” — split into “Template Functions” vs “Advanced Resolution/Extraction”.
Lines 109-111 read like capabilities (platform overrides, arch mapping, nested extraction), not template functions; mixing them makes it harder to know what’s actually callable in templates vs what the installer does.

.github/workflows/autofix.yml (1)

40-40: Minor: Verify the aqua.yaml path in the comment.

The comment references "aqua/aqua.yaml" but the standard location for Aqua configuration is typically "aqua.yaml" in the repository root. Verify this path is accurate for your setup.

📝 Suggested correction if aqua.yaml is in root
-      # gofumpt (installed via aqua from aqua/aqua.yaml)
+      # gofumpt (installed via aqua from aqua.yaml)
pkg/template/ast.go (1)

20-24: Remove perf.Track from trivial string join.

FieldRef.String() only performs strings.Join(f.Path, ".") - a trivial O(1) operation. Per coding guidelines, perf.Track should NOT be added to trivial getters/setters where the tracking overhead exceeds the actual operation time.

♻️ Remove unnecessary perf.Track
 // String returns the dot-separated path of the field reference.
 func (f FieldRef) String() string {
-	defer perf.Track(nil, "template.FieldRef.String")()
-
 	return strings.Join(f.Path, ".")
 }

Based on coding guidelines for perf.Track exceptions.

toolchain/registry/atmos/atmos.go (1)

13-20: These error sentinels are atmos-specific—no canonical equivalents exist, but consider the registry pattern.

No existing canonical errors match ErrInvalidToolConfig or ErrMissingRequiredField in toolchain/registry/registry.go. However, compare this against the pattern used by other registries (aqua reuses canonical errors). If these errors are specific to atmos registry validation, keeping them local is fine—just document why they diverge from the canonical pattern. If they're fundamental to registry configuration validation more broadly, consider promoting them to toolchain/registry/registry.go and re-exporting in toolchain/errors.go for consistency with the architecture shown in the learning materials.

toolchain/info_test.go (1)

12-30: Good shift to exercising public APIs (ParseToolSpec/FindTool); tighten test isolation.
Swapping to installer.ParseToolSpec / installer.FindTool is the right direction for coverage of the exported surface. Consider setting SetAtmosConfig(&schema.AtmosConfiguration{}) consistently in tests that construct NewInstaller() to avoid any coupling to package-global config state.

Also applies to: 32-49, 51-68, 124-148, 191-199

tests/test-cases/toolchain.yaml (1)

1-224: Verify these snapshots are fully offline/deterministic (avoid GitHub/Aqua lookups during CI).
The sanitizers help, but if toolchain info/registry search triggers network calls in some environments, these will still be flaky. If any path is remote-dependent, consider adding a fixture-backed mock endpoint (or explicit skip/gating) so CI doesn’t depend on outbound access.

toolchain/installer/override_test.go (1)

1-321: Override tests look solid and align with “first match wins”.
Table-driven coverage of exact/wildcard mismatches plus ApplyPlatformOverrides behavior is on point. Optional: add t.Parallel() where safe to speed up the package test suite.

toolchain/installer/installer_test.go (1)

316-340: Consider using testify assertions consistently.

This test uses manual t.Errorf while other tests in this file use assert/require. Minor consistency nit.

♻️ Optional: Use testify assertions for consistency
 for _, tt := range tests {
     t.Run(tt.name, func(t *testing.T) {
         owner, repo, err := installer.GetResolver().Resolve(tt.toolName)

         if tt.wantErr {
-            if err == nil {
-                t.Errorf("resolveToolName() expected error but got none")
-            }
+            assert.Error(t, err, "resolveToolName() expected error")
             return
         }

-        if err != nil {
-            t.Errorf("resolveToolName() unexpected error: %v", err)
-            return
-        }
-
-        if owner != tt.wantOwner {
-            t.Errorf("resolveToolName() owner = %v, want %v", owner, tt.wantOwner)
-        }
-
-        if repo != tt.wantRepo {
-            t.Errorf("resolveToolName() repo = %v, want %v", repo, tt.wantRepo)
-        }
+        require.NoError(t, err)
+        assert.Equal(t, tt.wantOwner, owner)
+        assert.Equal(t, tt.wantRepo, repo)
     })
 }

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
toolchain/registry/registry.go (1)

260-271: Consider consolidating AquaOverride with Override.

Both AquaOverride and Override have nearly identical fields. The only difference is AquaOverride includes a URL field.

This duplication may lead to drift over time. Consider either:

  1. Embedding Override in AquaOverride and adding the URL field.
  2. Adding URL to Override if it's generally applicable.

If the separation is intentional for strict Aqua format parsing, the current approach is acceptable—just worth noting for future maintenance.

toolchain/types.go (1)

51-84: NewInstaller logic is sound, with one suggestion.

The function correctly:

  • Derives binDir from config.
  • Injects realRegistryFactory as default.
  • Loads configured registries from atmos.yaml.
  • Allows user options to override base options.

One minor point: the error from NewRegistry is logged but swallowed. This is reasonable for graceful degradation, but consider whether a debug log of the specific error would help troubleshooting.

💡 Optional: Include error details in debug log
 		if reg, err := NewRegistry(config); err != nil {
-			log.Warn("Failed to load configured registry from atmos.yaml", "error", err)
+			log.Warn("Failed to load configured registry from atmos.yaml",
+				"error", err,
+				"registryCount", len(config.Toolchain.Registries))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2cdf2b3 and 5ae9fb1.

📒 Files selected for processing (2)
  • toolchain/registry/registry.go
  • toolchain/types.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using fmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: use gofmt and goimports to format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging

**/*.go: All comments must end with periods (enforced by godot linter) in Go code
Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (cfg, log, u, errUtils)
All errors MUST be wrapped using static errors defined in errors/errors.go - use errors.Join for combining errors, fmt.Errorf with %w for context, and errors.Is() for error checking
Never manually create mocks - use go.uber.org/mock/mockgen with //go:generate directives in Go code
Keep files small and focused - under 600 lines with one cmd/impl per file, co-locate tests, never use //revive:disable:file-length-limit
Use colors from pkg/ui/theme/colors.go for all UI theming in Go code
Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, use filepath.Join() instead of h...

Files:

  • toolchain/types.go
  • toolchain/registry/registry.go
🧠 Learnings (19)
📓 Common learnings
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain XDG compliance implementation is complete with GetXDGCacheDir() and GetXDGTempCacheDir() functions in toolchain/xdg_cache.go, updated installer.go and toolchain_clean.go to use these helpers, and changed cache paths from ~/.cache/tools-cache to ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain when XDG_CACHE_HOME is not set).
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: XDG Base Directory Specification compliance implementation for atmos toolchain is complete: created toolchain/xdg_cache.go with GetXDGCacheDir() and GetXDGTempCacheDir() functions, updated toolchain/installer.go and cmd/toolchain_clean.go to use these XDG helpers, and changed all cache paths from hardcoded ~/.cache/tools-cache to XDG-compliant ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback).
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain XDG compliance implementation is complete with GetXDGCacheDir() and GetXDGTempCacheDir() functions in toolchain/xdg_cache.go, updated installer.go and toolchain_clean.go to use these helpers, and changed cache paths from ~/.cache/tools-cache to ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain when XDG_CACHE_HOME is not set).

Applied to files:

  • toolchain/types.go
  • toolchain/registry/registry.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: XDG Base Directory Specification compliance implementation for atmos toolchain is complete: created toolchain/xdg_cache.go with GetXDGCacheDir() and GetXDGTempCacheDir() functions, updated toolchain/installer.go and cmd/toolchain_clean.go to use these XDG helpers, and changed all cache paths from hardcoded ~/.cache/tools-cache to XDG-compliant ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback).

Applied to files:

  • toolchain/types.go
  • toolchain/registry/registry.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.

Applied to files:

  • toolchain/types.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Never manually create mocks - use `go.uber.org/mock/mockgen` with `//go:generate` directives in Go code

Applied to files:

  • toolchain/types.go
  • toolchain/registry/registry.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*_test.go : Prefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with `go.uber.org/mock/mockgen`, use table-driven tests, target >80% coverage

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations

Applied to files:

  • toolchain/types.go
📚 Learning: 2024-11-23T00:13:22.004Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 801
File: examples/quick-start-advanced/Dockerfile:9-9
Timestamp: 2024-11-23T00:13:22.004Z
Learning: When updating the `ATMOS_VERSION` in Dockerfiles, the team prefers to pin to the next future version when the PR merges, even if the version is not yet released.

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-12-13T06:10:13.688Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-01-08T19:01:56.978Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 899
File: examples/tests/test-vendor/vendor.yaml:39-43
Timestamp: 2025-01-08T19:01:56.978Z
Learning: In example code and test fixtures, using 'latest' version tag is acceptable as it's for demonstration purposes only. This is different from production code where specific version pinning is recommended.

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to {go.mod,go.sum} : Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies

Applied to files:

  • toolchain/types.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.

Applied to files:

  • toolchain/types.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : All comments must end with periods (enforced by `godot` linter) in Go code

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Document complex logic with inline comments in Go code

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-11-10T23:23:39.771Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.

Applied to files:

  • toolchain/types.go
  • toolchain/registry/registry.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Follow multi-provider registry pattern: define interface, implement per provider, register implementations, generate mocks - see `pkg/store/` example

Applied to files:

  • toolchain/types.go
📚 Learning: 2025-12-21T04:10:29.030Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1891
File: internal/exec/describe_affected.go:468-468
Timestamp: 2025-12-21T04:10:29.030Z
Learning: In Go, package-level declarations (constants, variables, types, and functions) are visible to all files in the same package without imports. During reviews in cloudposse/atmos (and similar Go codebases), before suggesting to declare a new identifier, first check if it already exists in another file of the same package. If it exists, you can avoid adding a new declaration; if not, proceed with a proper package-level declaration. 

Applied to files:

  • toolchain/types.go
  • toolchain/registry/registry.go
📚 Learning: 2025-09-29T02:20:11.636Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1540
File: internal/exec/validate_component.go:117-118
Timestamp: 2025-09-29T02:20:11.636Z
Learning: The ValidateComponent function in internal/exec/validate_component.go had its componentSection parameter type refined from `any` to `map[string]any` without adding new parameters. This is a type safety improvement, not a signature change requiring call site updates.

Applied to files:

  • toolchain/registry/registry.go
🧬 Code graph analysis (2)
toolchain/types.go (4)
toolchain/installer/installer.go (13)
  • Installer (98-107)
  • ToolResolver (44-46)
  • DefaultToolResolver (55-57)
  • RegistryFactory (110-112)
  • Option (115-115)
  • WithBinDir (118-124)
  • WithRegistryFactory (155-161)
  • WithConfiguredRegistry (145-152)
  • New (174-208)
  • NewInstallerWithResolver (212-219)
  • WithResolver (136-142)
  • WithCacheDir (127-133)
  • BuiltinAliases (50-52)
toolchain/registry/aqua/aqua.go (1)
  • NewAquaRegistry (84-113)
toolchain/registry/registry.go (1)
  • ToolRegistry (64-88)
toolchain/setup.go (2)
  • GetInstallPath (56-81)
  • GetAtmosConfig (37-41)
toolchain/registry/registry.go (1)
toolchain/types.go (1)
  • File (20-20)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (10)
toolchain/registry/registry.go (3)

59-60: LGTM on mockgen directive.

Correctly uses go.uber.org/mock/mockgen@v0.6.0 with go:generate per coding guidelines. Output destination and package are appropriate for test usage.


213-219: Override struct expansion looks solid.

Added Format and Replacements fields to support platform-specific format overrides and OS/Arch string mappings. YAML tags are consistent with the rest of the codebase.


239-251: AquaPackage enhancements are well-documented.

The new Asset, Files, Overrides, and Replacements fields align with Aqua registry semantics. Inline comments clarify each field's purpose.

toolchain/types.go (7)

3-4: Mockgen directive is correct.

Uses pinned version and follows the established pattern. Generates mock for ToolRegistry interface into test file.


5-13: Import organization follows guidelines.

Three groups properly separated: stdlib (path/filepath), third-party (charmbracelet/log), and atmos packages. Sorted alphabetically within groups.


24-39: Type aliases maintain backward compatibility.

Clean approach for exposing installer package types while allowing existing code to continue using toolchain imports.


41-49: realRegistryFactory injection resolves the nil registry issue.

This addresses the PR objective of fixing the broken registry lookups. The factory delegates to NewAquaRegistry() which properly constructs the registry with all dependencies.


86-96: Deprecation handled correctly.

Delegates to NewInstaller ensuring the realRegistryFactory injection happens. The deprecation comment guides users to the preferred approach.


98-131: Option wrappers are consistent.

Each wrapper includes perf.Track and delegates to the corresponding installer function. This maintains observability while providing backward compatibility.


133-142: BuiltinAliases and NewInstallerWithBinDir look good.

BuiltinAliases re-export is clean. NewInstallerWithBinDir is a sensible convenience wrapper for testing.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 13, 2026
@aknysh
Copy link
Copy Markdown
Member

aknysh commented Jan 13, 2026

@coderabbitai full review please

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 13, 2026

@aknysh, I'll perform a full review of all the changes in this PR.


🧠 Learnings used
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.

Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain XDG compliance implementation is complete with GetXDGCacheDir() and GetXDGTempCacheDir() functions in toolchain/xdg_cache.go, updated installer.go and toolchain_clean.go to use these helpers, and changed cache paths from ~/.cache/tools-cache to ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain when XDG_CACHE_HOME is not set).

Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.

Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: XDG Base Directory Specification compliance implementation for atmos toolchain is complete: created toolchain/xdg_cache.go with GetXDGCacheDir() and GetXDGTempCacheDir() functions, updated toolchain/installer.go and cmd/toolchain_clean.go to use these XDG helpers, and changed all cache paths from hardcoded ~/.cache/tools-cache to XDG-compliant ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback).
✅ Actions performed

Full review triggered.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
toolchain/installer/installer_test.go (1)

621-647: This test needs proper registry mocking—it's hitting external services without isolation.

FindTool without configuration defaults to real external registries (GitHub, HashiCorp releases), making the test non-deterministic. The skip handles missing tools gracefully, but doesn't prevent flakiness from registry behavior changes or network issues.

The codebase already has mockToolResolver in TestNewInstaller—use a similar pattern to inject a mock registry factory into New() or provide a way to override the registry configuration for testing. This aligns with how AquaRegistry tests should use dependency injection (WithGitHubBaseURL patterns) to make tests deterministic and reliable.

🧹 Nitpick comments (4)
toolchain/installer/installer_test.go (3)

94-118: Consider using testify assertions for consistency.

This test uses manual t.Errorf checks while the rest of the file uses testify. Mixing styles can make the codebase harder to maintain.

Optional refactor to use testify
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			owner, repo, err := installer.ParseToolSpec(tt.tool)

 			if tt.wantErr {
-				if err == nil {
-					t.Errorf("parseToolSpec() expected error but got none")
-				}
+				assert.Error(t, err, "parseToolSpec() expected error")
 				return
 			}

-			if err != nil {
-				t.Errorf("parseToolSpec() unexpected error: %v", err)
-				return
-			}
-
-			if owner != tt.wantOwner {
-				t.Errorf("parseToolSpec() owner = %v, want %v", owner, tt.wantOwner)
-			}
-
-			if repo != tt.wantRepo {
-				t.Errorf("parseToolSpec() repo = %v, want %v", repo, tt.wantRepo)
-			}
+			require.NoError(t, err)
+			assert.Equal(t, tt.wantOwner, owner)
+			assert.Equal(t, tt.wantRepo, repo)
 		})
 	}

203-247: Test bypasses the New() constructor.

This test creates an Installer struct directly instead of using New() or NewInstallerWithResolver(). While this works for testing specific fields, it doesn't exercise the actual constructor logic being tested.

Suggested fix to use the constructor
 func TestNewInstaller(t *testing.T) {
 	mockResolver := &mockToolResolver{
 		mapping: map[string][2]string{
 			"terraform": {"hashicorp", "terraform"},
 			"opentofu":  {"opentofu", "opentofu"},
 			"kubectl":   {"kubernetes", "kubectl"},
 			"helm":      {"helm", "helm"},
 			"helmfile":  {"helmfile", "helmfile"},
 		},
 	}
-	customRegistries := []string{"mock-remote-registry", "mock-local-registry"}
-	installer := &Installer{
-		binDir:     "./.tools/bin",
-		registries: customRegistries,
-		resolver:   mockResolver,
-	}
+	tmpDir := t.TempDir()
+	installer := New(
+		WithBinDir(tmpDir),
+		WithResolver(mockResolver),
+	)

-	if installer.binDir != "./.tools/bin" {
-		t.Errorf("New() binDir = %v, want ./.tools/bin", installer.binDir)
-	}
+	assert.Equal(t, tmpDir, installer.binDir, "binDir should match")
+	assert.NotNil(t, installer.resolver, "resolver should be set")

315-339: Same assertion style inconsistency.

Consider using testify assertions here as well for consistency with the rest of the file.

toolchain/installer/extract_test.go (1)

482-505: Invalid mode test uses negative value.

Line 500 uses Mode: -1 which tests invalid mode handling. This is a good edge case, though documenting why -1 is invalid could help future readers.

Add clarifying comment
 	t.Run("rejects invalid mode", func(t *testing.T) {
 		tmpDir := t.TempDir()
 		targetPath := filepath.Join(tmpDir, "newdir")

+		// Mode -1 is invalid as file mode bits must be non-negative.
 		header := &tar.Header{Mode: -1}
 		err := extractDir(targetPath, header)
 		assert.Error(t, err)
 		assert.ErrorIs(t, err, ErrFileOperation)
 	})
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5ae9fb1 and 5849068.

📒 Files selected for processing (5)
  • tests/snapshots/TestCLICommands_atmos_toolchain_--help.stdout.golden
  • tests/snapshots/TestCLICommands_atmos_toolchain_info_--help.stdout.golden
  • tests/snapshots/TestCLICommands_atmos_toolchain_install_--help.stdout.golden
  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
✅ Files skipped from review due to trivial changes (2)
  • tests/snapshots/TestCLICommands_atmos_toolchain_info_--help.stdout.golden
  • tests/snapshots/TestCLICommands_atmos_toolchain_--help.stdout.golden
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/snapshots/TestCLICommands_atmos_toolchain_install_--help.stdout.golden
🧰 Additional context used
📓 Path-based instructions (2)
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using fmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: use gofmt and goimports to format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging

**/*.go: All comments must end with periods (enforced by godot linter) in Go code
Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (cfg, log, u, errUtils)
All errors MUST be wrapped using static errors defined in errors/errors.go - use errors.Join for combining errors, fmt.Errorf with %w for context, and errors.Is() for error checking
Never manually create mocks - use go.uber.org/mock/mockgen with //go:generate directives in Go code
Keep files small and focused - under 600 lines with one cmd/impl per file, co-locate tests, never use //revive:disable:file-length-limit
Use colors from pkg/ui/theme/colors.go for all UI theming in Go code
Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, use filepath.Join() instead of h...

Files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
**/*_test.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*_test.go: Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages
Use table-driven tests for testing multiple scenarios in Go
Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Prefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with go.uber.org/mock/mockgen, use table-driven tests, target >80% coverage

Files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
🧠 Learnings (28)
📓 Common learnings
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain XDG compliance implementation is complete with GetXDGCacheDir() and GetXDGTempCacheDir() functions in toolchain/xdg_cache.go, updated installer.go and toolchain_clean.go to use these helpers, and changed cache paths from ~/.cache/tools-cache to ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain when XDG_CACHE_HOME is not set).
Learnt from: Listener430
Repo: cloudposse/atmos PR: 934
File: tests/fixtures/scenarios/docs-generate/README.md.gotmpl:99-118
Timestamp: 2025-01-25T03:51:57.689Z
Learning: For the cloudposse/atmos repository, changes to template contents should be handled in dedicated PRs and are typically considered out of scope for PRs focused on other objectives.
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:206-206
Timestamp: 2025-01-17T00:18:57.769Z
Learning: For indirect dependencies with license compliance issues in the cloudposse/atmos repository, the team prefers to handle them in follow-up PRs rather than blocking the current changes, as these issues often require deeper investigation of the dependency tree.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: XDG Base Directory Specification compliance implementation for atmos toolchain is complete: created toolchain/xdg_cache.go with GetXDGCacheDir() and GetXDGTempCacheDir() functions, updated toolchain/installer.go and cmd/toolchain_clean.go to use these XDG helpers, and changed all cache paths from hardcoded ~/.cache/tools-cache to XDG-compliant ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback).
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages

Applied to files:

  • toolchain/installer/extract_test.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain XDG compliance implementation is complete with GetXDGCacheDir() and GetXDGTempCacheDir() functions in toolchain/xdg_cache.go, updated installer.go and toolchain_clean.go to use these helpers, and changed cache paths from ~/.cache/tools-cache to ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain when XDG_CACHE_HOME is not set).

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: XDG Base Directory Specification compliance implementation for atmos toolchain is complete: created toolchain/xdg_cache.go with GetXDGCacheDir() and GetXDGTempCacheDir() functions, updated toolchain/installer.go and cmd/toolchain_clean.go to use these XDG helpers, and changed all cache paths from hardcoded ~/.cache/tools-cache to XDG-compliant ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback).

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*_test.go : Prefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with `go.uber.org/mock/mockgen`, use table-driven tests, target >80% coverage

Applied to files:

  • toolchain/installer/extract_test.go
📚 Learning: 2024-10-23T20:13:23.054Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 731
File: pkg/utils/file_utils.go:198-202
Timestamp: 2024-10-23T20:13:23.054Z
Learning: In `pkg/utils/file_utils.go`, the current implementation of the `IsURL` function is considered sufficient; avoid suggesting more complex URL validation in future reviews.

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.

Applied to files:

  • toolchain/installer/extract_test.go
📚 Learning: 2025-12-13T06:10:13.688Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: errors/errors.go:184-203
Timestamp: 2025-12-13T06:10:13.688Z
Learning: cloudposse/atmos: For toolchain work, duplicate/unused error sentinels in errors/errors.go should be cleaned up in a separate refactor PR and not block feature PRs; canonical toolchain sentinels live under toolchain/registry with re-exports in toolchain/errors.go.

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-04-04T02:03:23.676Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1185
File: internal/exec/yaml_func_store.go:26-26
Timestamp: 2025-04-04T02:03:23.676Z
Learning: The Atmos codebase currently uses `log.Fatal` for error handling in multiple places. The maintainers are aware this isn't an ideal pattern (should only be used in main() or init() functions) and plan to address it comprehensively in a separate PR. CodeRabbit should not flag these issues or push for immediate changes until that refactoring is complete.

Applied to files:

  • toolchain/installer/extract_test.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.

Applied to files:

  • toolchain/installer/extract_test.go
📚 Learning: 2025-04-26T15:54:10.506Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 1195
File: internal/exec/terraform_clean.go:99-99
Timestamp: 2025-04-26T15:54:10.506Z
Learning: The error variable `ErrRelPath` is defined in `internal/exec/terraform_clean_util.go` and is used across files in the `exec` package, including in `terraform_clean.go`. This is part of an approach to standardize error handling in the codebase.

Applied to files:

  • toolchain/installer/extract_test.go
📚 Learning: 2024-10-28T01:51:30.811Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:329-332
Timestamp: 2024-10-28T01:51:30.811Z
Learning: In the Atmos Go code, when deleting directories or handling file paths (e.g., in `terraform_clean.go`), always resolve the absolute path using `filepath.Abs` and use the logger `u.LogWarning` for logging messages instead of using `fmt.Printf`.

Applied to files:

  • toolchain/installer/extract_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: The atmos codebase has a custom extension to *testing.T that provides a Chdir method, allowing test functions to call t.Chdir() to change working directories during tests. This is used consistently across test files in the codebase.

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Use table-driven tests for testing multiple scenarios in Go

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-12-21T04:10:29.030Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1891
File: internal/exec/describe_affected.go:468-468
Timestamp: 2025-12-21T04:10:29.030Z
Learning: In Go, package-level declarations (constants, variables, types, and functions) are visible to all files in the same package without imports. During reviews in cloudposse/atmos (and similar Go codebases), before suggesting to declare a new identifier, first check if it already exists in another file of the same package. If it exists, you can avoid adding a new declaration; if not, proceed with a proper package-level declaration. 

Applied to files:

  • toolchain/installer/extract_test.go
  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-10T23:23:39.771Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Follow multi-provider registry pattern: define interface, implement per provider, register implementations, generate mocks - see `pkg/store/` example

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-11T03:47:59.576Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-30T03:21:37.197Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1274
File: go.mod:63-63
Timestamp: 2025-05-30T03:21:37.197Z
Learning: The redis dependency (github.com/redis/go-redis/v9) in the atmos project is only used in tests, not in production code.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-11-11T03:47:45.878Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to cmd/**/*_test.go : Always use `cmd.NewTestKit(t)` for cmd tests to auto-clean RootCmd state

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-12-10T18:32:51.237Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1808
File: cmd/terraform/backend/backend_delete_test.go:9-23
Timestamp: 2025-12-10T18:32:51.237Z
Learning: In cmd subpackages (e.g., cmd/terraform/backend/), tests cannot use cmd.NewTestKit(t) due to Go's test visibility rules (NewTestKit is in a parent package test file). These tests only need TestKit if they execute commands through RootCmd or modify RootCmd state. Structural tests that only verify command structure/flags without touching RootCmd don't require TestKit cleanup.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests. This is implemented through custom testing framework extensions and is used consistently throughout the test suite.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: Final XDG Base Directory Specification implementation for atmos toolchain is complete and verified: toolchain/xdg_cache.go provides GetXDGCacheDir() and GetXDGTempCacheDir() functions, all hardcoded ~/.cache/tools-cache paths have been replaced with XDG-compliant paths using ${XDG_CACHE_HOME}/atmos-toolchain (or ~/.cache/atmos-toolchain fallback), and tests have been updated to expect the new path structure.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method that can be called on *testing.T objects. This functionality is implemented through custom testing framework extensions and is used consistently throughout the test suite for changing working directories during tests.

Applied to files:

  • toolchain/installer/installer_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests.

Applied to files:

  • toolchain/installer/installer_test.go
🧬 Code graph analysis (2)
toolchain/installer/extract_test.go (2)
toolchain/registry/registry.go (4)
  • Tool (185-203)
  • ErrToolNotFound (14-14)
  • ErrFileOperation (38-38)
  • File (206-209)
toolchain/installer/extract.go (3)
  • Unzip (230-247)
  • MoveFile (474-492)
  • ExtractTarGz (319-349)
toolchain/installer/installer_test.go (1)
toolchain/installer/installer.go (6)
  • NewInstallerWithResolver (212-219)
  • New (174-208)
  • WithBinDir (118-124)
  • WithCacheDir (127-133)
  • WithResolver (136-142)
  • WithRegistryFactory (155-161)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Acceptance Tests (macos)
  • GitHub Check: Acceptance Tests (linux)
  • GitHub Check: Acceptance Tests (windows)
  • GitHub Check: Summary
🔇 Additional comments (14)
toolchain/installer/installer_test.go (4)

1-16: Imports look clean and properly organized.

Good separation with stdlib, third-party (testify), and internal packages (schema, registry). Aligns with the coding guidelines for import organization.


199-201: Skipped test with clear reasoning.

Good practice to document why tests are skipped rather than deleting them. This indicates the functionality exists elsewhere.


484-514: Good test setup with separate directories.

Using separate srcDir and binDir prevents path conflicts and makes the test more robust.


964-1013: Solid test coverage for the options pattern.

These tests verify all the constructor options work correctly. Good coverage of the functional options pattern introduced in the refactor.

toolchain/installer/extract_test.go (10)

1-16: Imports well-organized for test file.

All necessary packages for comprehensive extraction testing are included.


18-77: Good MIME type testing approach.

Creating actual gzip content to test detection is the right approach. This ensures the test validates real behavior.


233-272: Path traversal security testing included.

Good coverage of the path validation function with traversal attack scenarios. This is critical for secure archive extraction.


309-350: Comprehensive binary discovery tests.

Tests cover root directory, subdirectories, and Windows .exe extension handling. Error case for missing binary also covered.


439-480: Size limit test validates decompression bomb protection.

Testing the limit enforcement is important for security. The error message check at line 478 validates proper error reporting.


507-550: Test helpers properly use t.Helper().

Both createTestZipArchive and createTestTarGzArchive correctly call t.Helper() for accurate error line reporting.


623-672: Tests cover both simple and Files config extraction.

The second subtest at line 646 validates the Files configuration feature for extracting specific files from archives. This aligns with the PR's support for multi-file extraction.


822-889: Magic bytes detection tests are thorough.

Testing extraction by magic bytes (without relying on file extension) ensures robust file type handling. Good coverage of zip, gzip, and binary octet-stream types.


891-945: Error cases properly tested.

Tests include empty Files slice and missing source file scenarios. This validates the function fails gracefully with clear errors.


149-191: The test setup is necessary. The isTarGzFile function at line 77 of toolchain/installer/extract.go actively calls mime.Is("application/x-tar") as a fallback check. Passing nil as suggested would cause a nil pointer dereference panic. The MIME detection serves a real purpose beyond filename extension checking.

Likely an incorrect or invalid review comment.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
toolchain/install_test.go (1)

226-256: Test will hit real GitHub when resolving "latest" version.

RunInstall("terraform@latest", ...) triggers resolveLatestVersionWithSpinnerregistry.GetLatestVersion() → real HTTP calls to GitHub API. The AquaRegistry supports WithGitHubBaseURL() option to inject an httptest server, but the test currently doesn't use it. Either:

  • Refactor resolveLatestVersionWithSpinner to accept a registry parameter and update test to inject a mocked registry via httptest and WithGitHubBaseURL, or
  • Mark this as an integration test and skip in CI when network is unavailable.
toolchain/info_test.go (1)

287-339: Use httptest to mock GitHub requests in AquaRegistry test.

The test calls installer.FindTool() which accesses a real GitHub endpoint via the AquaRegistry client. Per PR 1686 learnings, inject an httptest server using WithGitHubBaseURL() like the pattern in toolchain/registry/aqua/aqua_test.go to ensure the test is deterministic and doesn't depend on network availability.

Consider refactoring NewAquaRegistry() in toolchain/aqua_registry.go to accept RegistryOption parameters so tests can inject the mock URL through the existing wrapper.

toolchain/installer/installer_test.go (1)

621-647: Mock the registry factory instead of relying on skip fallback.

The test calls New() without options, which initializes a real defaultRegistryFactory that makes HTTP calls to https://raw.githubusercontent.com/aquaproj/aqua-registry/main/pkgs. While t.Skipf prevents test failure if the tool isn't found, it doesn't prevent network calls when the tool exists. Use WithRegistryFactory with a mock that injects an httptest server (like aqua_test.go demonstrates) to ensure the test stays isolated and deterministic.

🤖 Fix all issues with AI agents
In @toolchain/registry/aqua/aqua_test.go:
- Around line 844-864: Consolidate the multiple
TestAquaRegistry_parseRegistryFile_* functions into one table-driven test named
e.g. TestAquaRegistry_parseRegistryFile: create a slice of scenarios each with
fields (name, inputFile, expected values such as ExpectedName,
ExpectedRepoOwner, ExpectedRepoName,
ExpectedFiles/ExpectedReplacements/ExpectedOverrides), loop over scenarios and
call t.Run(s.name, func(t *testing.T){ ar := NewAquaRegistry(); data :=
os.ReadFile(s.inputFile); tool, err := ar.parseRegistryFile(data);
assert/require checks using s.expected values }), and remove the individual test
functions; keep references to parseRegistryFile and NewAquaRegistry to locate
logic and reuse existing testdata files (e.g., "testdata/aws-cli-files.yaml").

In @toolchain/registry/aqua/testdata/aws-cli-full.yaml:
- Around line 1-23: The download URLs use the v-prefixed template variable
{{.Version}} which yields values like "v2.13.0" but AWS CLI expects a bare
semver; update both url entries (the top-level url and the darwin override url
in the package definition) to use either {{.SemVer}} or {{trimV .Version}}
instead of {{.Version}} so the generated URLs omit the leading "v".
🧹 Nitpick comments (18)
aqua/README.md (1)

38-42: Consider clarifying the working directory context.

The command aqua install is documented here, but it's unclear whether users should run it from the repository root or the aqua/ directory. If Aqua requires configuration in the repo root or specific path flags, consider adding a note about the expected working directory.

pkg/template/ast.go (1)

20-23: Consider removing perf tracking from this trivial accessor.

FieldRef.String() just calls strings.Join on a small slice. The tracking overhead may exceed the operation time itself. Based on learnings, trivial accessors are typically exempt from perf tracking.

That said, if repository linting enforces perf.Track on all public functions for consistency, this is fine to keep.

toolchain/uninstall_test.go (1)

240-256: FakeInstaller appears underutilized.

The FakeInstaller struct has several fields (CalledParseToolSpec, UninstallCalled, UninstallErr, etc.) that are never set during tests. The test at line 294 checks tc.installer.UninstallCalled but the actual RunUninstall call doesn't inject this fake installer.

Consider either completing the dependency injection to use this fake, or removing the unused fields to avoid confusion.

toolchain/path_helpers.go (1)

28-29: Stale comment references unexported method name.

The comment on line 28 still says parseToolSpec but the code now calls the exported ParseToolSpec.

📝 Suggested fix
-		// Resolve tool name to owner/repo using parseToolSpec for consistency.
+		// Resolve tool name to owner/repo using ParseToolSpec for consistency.
 		owner, repo, err := installer.ParseToolSpec(toolName)
toolchain/registry/atmos/atmos.go (1)

184-208: Silent skip of malformed overrides could hide config issues.

When overrideRaw isn't a map[string]any, the code silently skips it (line 190). This might mask configuration errors. Consider logging a warning for visibility.

Also, an override without goos or goarch set is essentially a no-op platform selector. Worth validating?

🔧 Optional: Add warning for skipped/empty overrides
 func parseOverrides(overridesRaw []any) []registry.Override {
 	var overrides []registry.Override
 	for _, overrideRaw := range overridesRaw {
 		overrideMap, ok := overrideRaw.(map[string]any)
 		if !ok {
+			// Consider: log.Debug("skipping malformed override entry", "type", fmt.Sprintf("%T", overrideRaw))
 			continue
 		}
 		override := registry.Override{}
 		if goos, ok := overrideMap["goos"].(string); ok {
 			override.GOOS = goos
 		}
 		if goarch, ok := overrideMap["goarch"].(string); ok {
 			override.GOARCH = goarch
 		}
+		// Skip overrides with no platform selector
+		if override.GOOS == "" && override.GOARCH == "" {
+			continue
+		}
 		if asset, ok := overrideMap["asset"].(string); ok {
 			override.Asset = asset
 		}
 		if format, ok := overrideMap["format"].(string); ok {
 			override.Format = format
 		}
 		overrides = append(overrides, override)
 	}
 	return overrides
 }
toolchain/registry/atmos/atmos_test.go (1)

479-564: Consider adding a negative test for malformed overrides.

The happy paths are well covered. You might add a subtest for invalid override format (e.g., override entry is not a map) to ensure proper error handling.

💡 Optional: Example negative test case
t.Run("invalid override format returns error", func(t *testing.T) {
    toolsConfig := map[string]any{
        "owner/tool": map[string]any{
            "type":      "github_release",
            "asset":     "tool-{{.OS}}-{{.Arch}}.tar.gz",
            "overrides": []any{"not-a-map"},
        },
    }

    _, err := NewAtmosRegistry(toolsConfig)
    assert.Error(t, err)
})
toolchain/types_test.go (1)

17-37: Consider asserting the configured values.

Tests confirm installer is non-nil, but don't verify the options actually took effect. Adding assertions on installer.GetBinDir() or similar accessors would strengthen coverage.

Example assertion
 	t.Run("creates installer with custom binDir", func(t *testing.T) {
 		tmpDir := t.TempDir()
 		installer := NewInstaller(WithBinDir(tmpDir))
 		assert.NotNil(t, installer)
+		assert.Equal(t, tmpDir, installer.GetBinDir())
 	})
toolchain/installer/mock_resolver_test.go (1)

22-25: Potential confusion with no-op SetAtmosConfig.

This test helper has the same name as toolchain.SetAtmosConfig but is a no-op. If test code imports both packages, or if production code accidentally imports this test file's package, the no-op could silently break configuration.

Consider either:

  1. Renaming to something like SetAtmosConfigNoop to make intent explicit, or
  2. Documenting why the installer package doesn't need this configuration.
toolchain/installer/download_test.go (1)

257-279: Consider testing the actual implementation rather than duplicating logic.

This test duplicates the version prefix logic inline rather than calling the actual function that implements this behavior. If the implementation changes, these tests won't catch regressions.

♻️ Suggested approach

Consider testing via a function that actually implements this logic (if one exists), or at minimum add a comment explaining this tests the expected algorithm for documentation purposes.

toolchain/installer/asset_test.go (1)

347-390: Type assertions on template functions could be fragile.

These assertions (e.g., funcs["trimV"].(func(string) string)) will panic if the function signature changes. Consider using require with type checks or helper functions.

♻️ Safer approach
 t.Run("trimV removes v prefix", func(t *testing.T) {
-    fn := funcs["trimV"].(func(string) string)
+    fn, ok := funcs["trimV"].(func(string) string)
+    require.True(t, ok, "trimV should be func(string) string")
     assert.Equal(t, "1.2.3", fn("v1.2.3"))
     assert.Equal(t, "1.2.3", fn("1.2.3"))
 })
toolchain/info_test.go (2)

188-189: Empty test function.

TestInfoCommand_TableOutputFormat has no assertions. Either implement the test or remove it to avoid giving false confidence in test coverage.


413-437: Commented-out test code.

These commented-out test cases for "tool with files" and "tool with overrides" are dead code. Either implement them or remove them.

toolchain/installer/extract.go (1)

175-226: Multi-file extraction implementation.

Solid implementation. A few observations:

  • Line 197-203: First file in tool.Files is treated as the primary binary. This is implicit - consider adding a comment documenting this convention.
  • Line 220: Using defaultMkdirPermissions (0755) for all extracted files is appropriate for executables.
📝 Suggested documentation
 // extractFilesFromDir extracts files from the extracted archive using the Files config.
 // The binaryPath parameter specifies the destination for the primary binary.
 // For multiple files, additional binaries are placed in the same directory.
+// Note: The first entry in tool.Files is treated as the primary binary.
 func (i *Installer) extractFilesFromDir(tempDir, binaryPath string, tool *registry.Tool) error {
toolchain/installer/extract_test.go (1)

149-191: Test creates misleading temp file but likely works.

The test writes plain text to tmpFile then detects its MIME, but isTarGzFile appears to check filename extension (tt.filename), not the temp file's actual content. The temp file MIME detection seems unused in the assertion. Consider simplifying or clarifying intent.

toolchain/types.go (1)

98-131: Option wrappers with perf.Track may be noisy.

Every option wrapper calls perf.Track. These are typically called during initialization and the overhead is minimal, but consider if this granularity is needed for options that just delegate to installer.* functions.

toolchain/installer/installer_test.go (1)

187-197: TestGetBinaryPath needs consistent assertions.

Consider using assert.Equal from testify for consistency with other tests in this file.

Suggestion
 func TestGetBinaryPath(t *testing.T) {
 	binDir := t.TempDir()
 	installer := New(WithBinDir(binDir))

 	path := installer.GetBinaryPath("suzuki-shunsuke", "github-comment", "v6.3.4", "")
 	expected := filepath.Join(binDir, "suzuki-shunsuke", "github-comment", "v6.3.4", "github-comment")

-	if path != expected {
-		t.Errorf("GetBinaryPath() = %v, want %v", path, expected)
-	}
+	assert.Equal(t, expected, path, "GetBinaryPath() mismatch")
 }
toolchain/installer/installer.go (2)

210-219: Deprecation notice is helpful.

Consider adding a // Deprecated: comment that tools like staticcheck can detect.

Suggestion for better tooling support
-// NewInstallerWithResolver allows injecting a custom ToolResolver (for tests).
-// Deprecated: Use New() with WithResolver() option instead.
+// NewInstallerWithResolver allows injecting a custom ToolResolver (for tests).
+//
+// Deprecated: Use New() with WithResolver() option instead.
 func NewInstallerWithResolver(resolver ToolResolver, binDir string) *Installer {

488-529: Uninstall has potential race condition on directory cleanup.

The loop removing parent directories (lines 514-525) could race with concurrent installs. In practice, this is unlikely to cause issues since the dirs would just not be empty, but worth noting.

@aknysh aknysh merged commit fd590f4 into main Jan 13, 2026
92 of 93 checks passed
@aknysh aknysh deleted the osterman/aqua-zip-exe-install branch January 13, 2026 20:54
@mergify mergify bot removed the needs-cloudposse Needs Cloud Posse assistance label Jan 13, 2026
osterman added a commit that referenced this pull request Jan 13, 2026
This fixes `atmos bootstrap` failures for multiple tools after PR #1948 by
aligning version prefix handling with Aqua's actual behavior.

Root causes fixed:
1. buildTemplateData was unconditionally adding "v" prefix when version_prefix
   was empty, breaking tools like AWS CLI (expects AWSCLIV2-2.32.31 not v2.32.31)
2. buildDownloadError wasn't including ErrHTTP404 for 404 responses, breaking
   the version fallback mechanism
3. Spinner was suppressing debug log output when running

Changes:
- Remove unconditional "v" prefix default in buildTemplateData (asset.go)
- Same fix in resolveVersionStrings (aqua.go)
- Include ErrHTTP404 in 404 errors for fallback detection (download.go)
- Disable spinner when debug logging is enabled (install_helpers.go)
- Update tests to encode correct Aqua behavior
- Add regression tests for AWS CLI, jq, gum, and HTTP type tools

Fixes failures for: aws/aws-cli@2.32.31, jqlang/jq@1.7.1,
charmbracelet/gum@0.17.0, replicatedhq/replicated@0.124.1

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

These changes were released in v1.204.0-rc.4.

aknysh added a commit that referenced this pull request Jan 14, 2026
…ws (#1958)

* fix: Fix toolchain bootstrap by removing unconditional version prefix

This fixes `atmos bootstrap` failures for multiple tools after PR #1948 by
aligning version prefix handling with Aqua's actual behavior.

Root causes fixed:
1. buildTemplateData was unconditionally adding "v" prefix when version_prefix
   was empty, breaking tools like AWS CLI (expects AWSCLIV2-2.32.31 not v2.32.31)
2. buildDownloadError wasn't including ErrHTTP404 for 404 responses, breaking
   the version fallback mechanism
3. Spinner was suppressing debug log output when running

Changes:
- Remove unconditional "v" prefix default in buildTemplateData (asset.go)
- Same fix in resolveVersionStrings (aqua.go)
- Include ErrHTTP404 in 404 errors for fallback detection (download.go)
- Disable spinner when debug logging is enabled (install_helpers.go)
- Update tests to encode correct Aqua behavior
- Add regression tests for AWS CLI, jq, gum, and HTTP type tools

Fixes failures for: aws/aws-cli@2.32.31, jqlang/jq@1.7.1,
charmbracelet/gum@0.17.0, replicatedhq/replicated@0.124.1

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

* fix: Preserve all fields in resolveVersionOverrides for version-specific configs

The resolveVersionOverrides function was creating a new Tool struct but only
copying a subset of fields from the registry package definition. This caused
critical fields like VersionPrefix, Replacements, Overrides, and Files to be
lost when version overrides were resolved.

Additionally, the versionOverride struct was missing Replacements and Overrides
fields, so version-specific replacements (like jq's darwin->macos mapping for
versions < 1.8.0) were not being parsed or applied.

Fixes:
- registryPackage struct now includes all tool fields (Asset, Format, BinaryName,
  VersionPrefix, Replacements, Overrides, Files)
- versionOverride struct now includes Replacements and Overrides fields
- resolveVersionOverrides copies all fields from registryPackage to Tool
- applyVersionOverride applies replacements and overrides from version overrides

Adds regression tests that verify:
- Base fields are preserved when no version override matches
- Version override replacements are correctly applied
- Version override platform overrides are correctly applied
- Real-world patterns (jq, gum, opentofu) work correctly

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Fix template expansion in files.src and pkgutil extraction

This commit fixes two tool installation issues discovered during
bootstrap testing:

1. helm: The `files.src` field from Aqua registry contains templates
   like `{{.OS}}-{{.Arch}}/helm` that weren't being expanded, causing
   "file not found in archive" errors. Added `expandFileSrcTemplate`
   to process these templates using the same data as asset URLs.

2. aws-cli: macOS `pkgutil --expand-full` requires the target directory
   to NOT exist (it creates it). Changed to create a parent temp dir,
   then use a non-existent subdirectory as the target.

Also:
- Set tool.Version before extraction so template functions have
  access to version data
- Added regression tests for template expansion
- Added testdata fixture for integration testing with tools not
  in Aqua registry (like replicated)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: Add files/replacements/version_prefix parsing to atmos inline registry

The inline atmos registry now supports all fields needed for complete
tool definitions:

- `files`: Array of file extraction configurations with name and src
- `replacements`: Map for OS/arch name substitutions (e.g., darwin->macos)
- `version_prefix`: Prefix for version tags (e.g., "v", "jq-")

This enables defining tools like replicated (not in Aqua registry) with
full control over extraction and platform-specific patterns.

Example:
```yaml
toolchain:
  registries:
    - name: custom
      type: atmos
      priority: 100
      tools:
        replicatedhq/replicated:
          type: github_release
          asset: 'replicated_{{trimV .Version}}_{{.OS}}_{{.Arch}}.tar.gz'
          format: tar.gz
          files:
            - name: replicated
              src: replicated
```

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: Add automatic toolchain integration for custom commands and workflows

Custom commands and workflows now automatically have access to tools defined
in .tool-versions. Previously, tools were only available when explicitly
declared in the dependencies section.

Changes:
- Add LoadToolVersionsDependencies() to resolve .tool-versions as dependencies
- Update executeCustomCommand() to merge .tool-versions with command deps
- Add ensureToolchainDependencies() to workflow executor
- Document toolchain integration for both custom commands and workflows

Tools from .tool-versions serve as project-wide defaults. Per-command or
per-workflow dependencies override these defaults when specified.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: Add toolchain integration examples for custom commands and workflows

Add working examples demonstrating how toolchain-managed tools from
.tool-versions are automatically available in custom commands and workflows.

Changes:
- Add .tool-versions with jq and yq for demonstration
- Add custom commands (toolchain-demo) using which/version commands
- Add workflow file (toolchain-demo.yaml) with shell steps
- Update README with comprehensive documentation
- Update .gitignore to allow example .tool-versions file

Examples show:
- Using `which` to verify toolchain binaries are in PATH
- Tool version checking
- JSON/YAML processing with jq/yq
- Both implicit (.tool-versions) and explicit (dependencies) usage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: Improve toolchain examples to show distinct features

Replace redundant examples with four distinct patterns:
1. which - Implicit dependencies from .tool-versions
2. pinned - Exact version pinning (overrides .tool-versions)
3. constrained - SemVer constraints (^1.7.0, ~> 4.40.0)
4. convert - Multi-tool pipeline (yq | jq)

Each example now demonstrates a different toolchain capability
instead of repeating the same which/version checks.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test: Increase test coverage for toolchain integration

Add DependencyProvider interface to workflow executor for testability:
- New DependencyProvider interface abstracts toolchain dependency operations
- DefaultDependencyProvider implements interface using real dependencies package
- Executor.WithDependencyProvider() allows injecting mock for testing
- Refactored ensureToolchainDependencies() to use interface

New tests for pkg/workflow/executor.go (ensureToolchainDependencies):
- No dependencies case (early return)
- Tool-versions only case
- Workflow deps only case
- Merged dependencies (workflow overrides tool-versions)
- Error paths: load, resolve, merge, install, PATH update

New tests for toolchain/installer/extract.go:
- extractGzipOrTarGz: tar.gz and single gzip binary paths
- extractByMimeType: tar MIME type handling
- extractEntry: directory type, path traversal, unknown type warning
- extractFile: invalid mode handling
- extractPkg: non-Darwin unsupported format

New tests for pkg/dependencies/resolver.go:
- Directory at .tool-versions path returns error (not NotExist)

Coverage improvements:
- ensureToolchainDependencies: ~19% → 100%
- extractGzipOrTarGz: 66.7% → 100%
- extractEntry: 50% → 100%
- LoadToolVersionsDependencies: 91.7% → 100%

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Pass AtmosConfig to resolver for toolchain alias resolution

The toolchain alias resolution wasn't working for custom commands because
the DefaultToolResolver was created without AtmosConfig. This fix:

- Add WithAtmosConfig option to pass AtmosConfig to the DefaultToolResolver
- NewInstaller() now passes AtmosConfig when available for alias resolution
- Update toolchain examples to use jqlang/jq (project moved from stedolan)
- Add aliases section to example atmos.yaml mapping short names to full specs
- Fix jq registry definition with version_prefix and darwin→macos override

Tested: `atmos demo which` now correctly resolves jq→jqlang/jq via aliases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat: Add toolchain integration to workflow execution

The workflow command now automatically:
- Loads tools from .tool-versions file
- Resolves workflow-specific dependencies from workflow definitions
- Merges dependencies (workflow deps override .tool-versions)
- Installs missing tools before step execution
- Prepends toolchain PATH to step environment

This ensures shell and atmos steps have access to the correct tool versions
without requiring manual PATH configuration.

Tested: `atmos workflow which` and `atmos workflow convert` now correctly
use tools from .tool-versions when running in the examples/toolchain directory.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* refactor: Simplify toolchain example structure

- Remove custom-registry folder (use inline registry only)
- Move workflows from stacks/workflows/ to workflows/
- Add base_path: "." to make example self-contained
- Add minimal stacks/_placeholder.yaml to satisfy requirements
- Update README with simplified registry examples
- Update command examples to use "atmos demo" prefix

The toolchain example now works standalone without requiring stacks
configuration. Tools are defined inline in atmos.yaml.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Make workflow stack processing conditional on --stack flag

Workflows no longer require stacks configuration when --stack flag is not
provided. Previously, running `atmos workflow <name>` would fail with
"stack base path must be provided" even for simple shell workflows that
don't use stacks.

Changes:
- Check --stack flag before calling InitCliConfig in workflow.go
- Pass processStacks=true only when --stack is explicitly provided
- Add tests for workflow conditional stack processing
- Enhance toolchain example with component-level tool dependencies
- Add mock component demonstrating opentofu ^1.10.0 constraint
- Remove placeholder stack file (no longer needed)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Improve toolchain error messages with proper hints and terminology

- Change "tool name" to "tool alias" in error messages (correct terminology)
- Add actionable hints to toolchain installation errors:
  - Check registry availability
  - Verify network connectivity
  - Link to toolchain docs (https://atmos.tools/cli/commands/toolchain/)
- Use error builder pattern for proper hint formatting in cmd_utils.go
- Distinguish explanation (what failed) from hints (what to do about it)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Add hint for missing tool alias when tool not found

When a tool without "/" is not found in aliases or registries, the error
now suggests configuring a tool alias in atmos.yaml and links to the
toolchain aliases documentation.

Example error output:
  Tool `aws-cli` is not a valid tool specification
  💡 Add an alias in atmos.yaml: toolchain.aliases.aws-cli: owner/repo
  💡 Or use full format: owner/repo (e.g., hashicorp/terraform)
  💡 See https://atmos.tools/cli/commands/toolchain/aliases for configuring aliases

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Use Files[0].Name for binary name resolution in toolchain installer

When tools specify a `files` configuration with a custom binary name
(e.g., aws-cli uses `files: [{name: aws, src: aws/dist/aws}]`), the
installer now correctly uses Files[0].Name as the binary name instead
of falling back to the repo name.

Priority order: BinaryName > Files[0].Name > Name > RepoName

This matches Aqua's behavior where the first file's name represents
the primary binary.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Suppress PATH hint for dependency installs

When tools are installed as dependencies for workflows or custom commands
via EnsureTools(), the PATH export hint is now suppressed. The hint is
only shown when running `atmos toolchain install` directly.

Added `showHint` parameter to RunInstall() and related functions to
control whether the PATH hint is displayed. Refactored InstallSingleTool
to use an InstallOptions struct to avoid exceeding the argument limit.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Add alias configuration hint when tool not found in registry

When a tool without "/" is not found in the Aqua registry, the error
now suggests adding an alias in atmos.yaml with the exact syntax:
  toolchain.aliases.<tool>: owner/repo

This helps developers understand that they can either:
1. Add an alias in atmos.yaml
2. Use the full owner/repo format
3. Search the registry for available tools

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Preserve error hints through error chain propagation

The ErrorBuilder.WithCause() was using fmt.Errorf("%w: %w", ...) which
breaks hint propagation in cockroachdb/errors.GetAllHints(). Changed to
use errors.Wrap() which properly preserves hints from cause errors.

Also removed generic hints from outer error wrappers (cmd_utils.go,
workflow/executor.go, dependencies/installer.go) that were duplicating
or overriding specific hints from inner errors. Now the specific
actionable hints like "Add an alias in atmos.yaml" bubble up properly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Add showProgressBar param to RunInstall for custom command installs

The progress bar wasn't showing when automatic toolchain dependency
installs ran during custom commands. Fixed by making showProgressBar
an explicit parameter rather than hardcoding to true.

Also fixed test assertions to use cockroachdb/errors.Is() instead of
standard errors.Is(), since our error builder uses Mark() which only
works with the cockroachdb implementation.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Progress bar display in toolchain install

The progress bar was stacking up vertically with each status message
instead of staying at the bottom and updating in place. Fixed by:

1. Calling resetLine() before printing status messages to clear the
   progress bar line (same pattern as uninstall which works correctly)
2. Using printProgressBar() helper instead of raw ui.Writef()

Also addresses CodeRabbit review comments:
- Add version_prefix: "v" to yq registry entry (mikefarah/yq uses v-prefixed tags)
- Add language identifier to README code block
- Fix comment about caret constraint (allows >=1.10.0, <2.0.0 not just 1.10.x)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Prevent .tool-versions duplication and enable spinner during batch install

- Add SkipToolVersionsUpdate option to InstallOptions to prevent duplicate
  entries when both InstallSingleTool and updateToolVersionsFile are called
- Enable ShowProgressBar for batch installs so spinner animates during downloads
- Add BatchInstallFunc to dependencies.Installer for batch tool installation
- Extract install result status strings to named constants
- Clean up duplicate entries in examples/toolchain/.tool-versions

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Reduce batch install message noise by separating spinner from details

Add ShowInstallDetails option to InstallOptions to control verbose messages
independently from spinner animation. In batch mode, only the simple
"Installed X" message from showProgress is shown, while single-tool mode
retains verbose output (path, size, registered).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Guard against nil error in ErrorBuilder.WithCause

Add nil check for b.err before calling b.err.Error() to prevent panic.
If b.err is nil, use the cause directly as the stored error.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Restore std errors.Is() compatibility in ErrorBuilder.WithCause

The previous change from fmt.Errorf("%w: %w") to errors.Wrap() broke
403 test assertions because cockroachdb/errors.Mark() only works with
cockroachdb's errors.Is(), not the standard library's errors.Is()
which is used by testify's assert.ErrorIs().

This fix:
- Reverts to fmt.Errorf("%w: %w") for std errors.Is() compatibility
- Extracts hints from cause via GetAllHints() before wrapping
- Preserves hints by adding them to b.hints for later application

This maintains both standard errors.Is() compatibility (tests pass)
and hint propagation from cause errors (original goal).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* chore: Regenerate snapshots with improved error hints

The ErrorBuilder.WithCause() fix now properly propagates hints from
inner errors, resulting in more specific and actionable error messages.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Add Windows support for binary name test

On Windows, executables need .exe extension to be recognized.
Updated TestIsToolInstalled_DifferentBinaryName to create the
binary with proper extension on Windows.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: Add Windows executable detection in GetBinaryPath

On Windows, executable permission bits don't apply - executables are
identified by .exe extension instead. Updated GetBinaryPath to check
for .exe suffix on Windows when auto-detecting binaries.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* add tests, address comments

* add tests, address comments

* add tests, address comments

* add tests, address comments

---------

Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com>
Co-authored-by: Andriy Knysh <aknysh@users.noreply.github.com>
Co-authored-by: aknysh <andriy.knysh@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

patch A minor, backward compatible change size/xl Extra large size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants