Skip to content

Conversation

cmharden
Copy link

@cmharden cmharden commented Aug 22, 2025

What

This PR enhances Atmos's OCI vendoring capabilities by implementing authentication support for private registries and robust layer extraction for multiple OCI artifact formats. The changes resolve the 401 Unauthorized error when pulling from private registries and support both TAR and ZIP layer formats.

Why

Previously, Atmos's OCI vendoring was limited to:

  • Only GitHub Container Registry (ghcr.io) authentication via GITHUB_TOKEN
  • Single artifact type support (application/vnd.atmos.component.terraform.v1+tar+gzip)
  • TAR-only layer extraction, causing failures with ZIP-based OCI artifacts

This resulted in:

  • 401 Unauthorized errors when pulling from private registries
  • Failed vendoring for OpenTofu modules and other ZIP-based artifacts
  • Limited compatibility with enterprise OCI registries

References

Solution Overview

1. Multi-Source Authentication Framework

Implemented a comprehensive authentication system that attempts credentials from multiple sources in order of preference:

  1. GitHub Container Registry - Uses GITHUB_TOKEN environment variable
  2. Docker Credential Store - Integrates with ~/.docker/config.json and credential helpers
  3. Environment Variables - Supports custom registry credentials via REGISTRY_NAME_USERNAME/PASSWORD
  4. Cloud Provider Auth - Placeholder framework for AWS ECR, Azure ACR, Google GCR

2. Enhanced Layer Extraction

Added support for multiple OCI layer formats:

  • TAR extraction - Original method for .tar and .tar.gz layers
  • ZIP extraction - New support for archive/zip media types
  • Raw data fallback - Robust fallback for unknown formats

3. Extended Artifact Type Support

Added support for additional OCI artifact types:

  • application/vnd.atmos.component.terraform.v1+tar+gzip (original)
  • application/vnd.opentofu.modulepkg (OpenTofu modules)
  • application/vnd.terraform.module.v1+tar+gzip (Terraform modules)

Files Changed

Core Implementation

  • internal/exec/oci_utils.go - Enhanced authentication and extraction logic

Test Coverage

  • internal/exec/oci_utils_test.go - Comprehensive test suite (new file)

Key Changes

Authentication Enhancements

New Functions Added:

  • getRegistryAuth() - Centralized authentication logic
  • getDockerAuth() - Docker config file integration
  • getCredentialStoreAuth() - Docker credential helper support
  • decodeDockerAuth() - Base64 auth string decoding
  • getECRAuth(), getACRAuth(), getGCRAuth() - Cloud provider placeholders

Enhanced pullImage() Function:

// Before: Only ghcr.io support
if strings.EqualFold(registry, "ghcr.io") {
    // GitHub token logic only
}

// After: Multi-source authentication
auth, err := getRegistryAuth(registry)
if err != nil {
    opts = append(opts, remote.WithAuth(authn.Anonymous))
} else {
    opts = append(opts, remote.WithAuth(auth))
}

Layer Extraction Improvements

New Functions Added:

  • extractZipFile() - ZIP archive extraction
  • extractRawData() - Raw data fallback extraction

Enhanced processLayer() Function:

// Before: TAR-only extraction
if err := extractTarball(uncompressed, destDir); err != nil {
    return fmt.Errorf("tarball extraction error: %w", err)
}

// After: Format-aware extraction
mediaTypeStr := string(mediaType)
if strings.Contains(mediaTypeStr, "zip") {
    extractionErr = extractZipFile(uncompressed, destDir)
} else {
    extractionErr = extractTarball(uncompressed, destDir)
}

Artifact Type Support

Enhanced checkArtifactType() Function:

// Before: Single artifact type
if manifest.ArtifactType != targetArtifactType {
    log.Warn("OCI image does not match the target artifact type")
}

// After: Multiple supported types
supportedTypes := []string{
    targetArtifactType,
    opentofuArtifactType,
    terraformArtifactType,
}

Test Coverage

Created comprehensive test suite covering:

Authentication Tests

  • GitHub Container Registry (with/without token)
  • Custom registry environment variables
  • Docker config with direct auth
  • Docker credential store integration
  • Cloud provider authentication placeholders

Extraction Tests

  • ZIP file extraction with multiple files/subdirectories
  • Raw data extraction fallback
  • Error handling for invalid formats

Integration Tests

  • Full OCI image processing flow
  • Error handling for invalid image references
  • Performance benchmarks

Test Results

=== RUN   TestGetRegistryAuth (0.49s) - PASS
=== RUN   TestExtractZipFile (0.00s) - PASS  
=== RUN   TestExtractRawData (0.00s) - PASS
=== RUN   TestCheckArtifactType (0.00s) - PASS
=== RUN   TestDecodeDockerAuth (0.00s) - PASS
=== RUN   TestGetCredentialStoreAuth (0.00s) - PASS
=== RUN   TestCloudProviderAuth (0.00s) - PASS
=== RUN   TestProcessOciImageIntegration (1.40s) - PASS

Performance Benchmarks

BenchmarkExtractZipFile-14           148           8185153 ns/op
BenchmarkGetRegistryAuth-14     22547790                54.36 ns/op

Usage Examples

Environment Variable Authentication

# For registry: my-registry.com
export MY_REGISTRY_COM_USERNAME="username"
export MY_REGISTRY_COM_PASSWORD="password"

Docker Credential Store

# Uses existing Docker credentials from ~/.docker/config.json
docker login my-registry.com

GitHub Container Registry

# Uses existing GITHUB_TOKEN
export GITHUB_TOKEN="ghp_..."

Security Considerations

  • No hardcoded credentials - All authentication is external
  • Docker credential integration - Leverages existing secure credential storage
  • Environment variable support - Follows security best practices
  • Fallback to anonymous - Graceful degradation when no auth available

Testing Instructions

Manual Testing

  1. Private Registry Test:

    # Set up credentials
    export MY_REGISTRY_COM_USERNAME="testuser"
    export MY_REGISTRY_COM_PASSWORD="testpass"
    
    # Test vendoring
    atmos vendor pull
  2. Docker Credential Test:

    # Login to registry
    docker login my-registry.com
    
    # Test vendoring (should use Docker credentials)
    atmos vendor pull
  3. OpenTofu Module Test:

    # Vendor an OpenTofu module (ZIP format)
    atmos vendor pull oci://registry.defenseunicorns.com/navy-pbme/terraform-aws-uds-s3irsa:v1.0.0

Automated Testing

# Run OCI-specific tests
go test ./internal/exec -v -run "TestGetRegistryAuth|TestExtractZipFile|TestExtractRawData|TestCheckArtifactType"

# Run performance benchmarks
go test ./internal/exec -bench "BenchmarkExtractZipFile|BenchmarkGetRegistryAuth" -run "^$"

Future Enhancements

The authentication framework is designed for easy extension:

  1. AWS ECR Integration - Ready for AWS SDK integration
  2. Azure ACR Integration - Ready for Azure SDK integration
  3. Google GCR Integration - Ready for Google Cloud SDK integration
  4. Additional Artifact Types - Easy to add new OCI artifact formats

Breaking Changes

None - This is a purely additive enhancement that maintains backward compatibility.

Checklist

  • Enhanced OCI authentication for private registries
  • Added ZIP layer extraction support
  • Extended artifact type compatibility
  • Comprehensive test coverage
  • Performance benchmarks
  • Documentation and usage examples
  • Security considerations addressed
  • Backward compatibility maintained
  • Error handling and logging improved

Result

This PR transforms Atmos's OCI vendoring from a basic GitHub-only feature into a robust, enterprise-ready solution that supports:

  • Any private OCI registry with flexible authentication
  • Multiple artifact formats (Atmos, OpenTofu, Terraform)
  • Multiple layer formats (TAR, ZIP, raw data)
  • Comprehensive error handling and fallback mechanisms
  • Full test coverage ensuring reliability

Summary by CodeRabbit

  • New Features

    • Multi-source OCI registry authentication (GitHub, Docker config, env vars, AWS ECR, Azure ACR, Google GCR) with anonymous fallback; new OCI settings added to configuration.
  • Improvements

    • More resilient OCI image processing with per-layer reporting and tolerance for partial failures; hardened ZIP extraction with path‑traversal and size protections; expanded artifact-type recognition (Terraform/OpenTofu).
  • Tests

    • Broad test coverage for registry auth paths, Docker helpers, cloud provider auth, ZIP/raw extraction, manifest parsing, and OCI processing.
  • Chores

    • Dependency upgrades and new environment-variable bindings for OCI credentials.

…e need to allow authentication to private registries.

Signed-off-by: Chris Harden <[email protected]>
@cmharden cmharden requested a review from a team as a code owner August 22, 2025 15:07
@mergify mergify bot added the triage Needs triage label Aug 22, 2025
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

📝 Walkthrough

Walkthrough

Adds multi-source OCI registry authentication (GHCR, Docker, env, ECR, ACR, GCR), threads OCI settings into the pull flow, implements per-layer extraction with ZIP/TAR dispatch and hardened ZIP protections, extends schema/config for OCI settings, bumps AWS SDK deps, and adds extensive auth/extraction tests.

Changes

Cohort / File(s) Summary
Core orchestration & extraction
internal/exec/oci_utils.go
Integrates atmosConfig into pull flow; adds getRegistryAuth, bindEnv, per-layer processing, media-type dispatch (ZIP/TAR), extractRawData, ZIP helpers/guards, structured logging, handleExtractionError, and non-fatal partial-layer handling.
Provider auth resolvers
internal/exec/oci_github.go, internal/exec/oci_google.go, internal/exec/oci_aws.go, internal/exec/oci_azure.go, internal/exec/oci_docker.go
New provider auth helpers: GHCR (env/atmos), GCR (ADC), ECR (AWS SDK token), ACR (service-principal & default credential token-exchange), and Docker (config.json + credential helpers). Adds public DockerConfig type.
ZIP extraction helpers & tests
internal/exec/oci_utils.go, internal/exec/oci_extraction_test.go
Adds ZIP extraction flow: extractZipFile, entry validation/resolution, path traversal guards, maxZipSize, and unit tests covering valid/malicious ZIPs and raw-data extraction.
Auth & processing tests
internal/exec/oci_auth_test.go, internal/exec/oci_github_test.go, internal/exec/oci_google_test.go, internal/exec/oci_aws_test.go, internal/exec/oci_azure_test.go, internal/exec/oci_docker_test.go, internal/exec/oci_processing_test.go
Adds table-driven/unit tests for registry auth selection/preference, credential decoding, registry detection, ECR/ACR parsing/token flows, and processOciImage/pullImage behaviors.
Config & schema
pkg/config/load.go, pkg/schema/schema.go
Adds OCI env bindings in setEnv and new exported OCI OCISettings field (Github/Azure tokens and DockerConfig) in AtmosSettings.
Deps
go.mod
Bumps aws-sdk-go-v2 modules and adds service/ecr dependency.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor Caller
  participant OCI as pullImage/processOciImage
  participant Auth as getRegistryAuth
  participant Env as Viper/Env
  participant Docker as Docker config/creds
  participant ECR as AWS ECR
  participant ACR as Azure ACR
  participant GCR as Google GCR
  Caller->>OCI: pullImage(imageRef, atmosConfig)
  OCI->>Auth: getRegistryAuth(registry, atmosConfig)
  Auth->>Env: check GHCR env/atmos vars
  Auth->>Docker: check docker config.json & cred helpers
  Auth->>ECR: attempt ECR auth (AWS SDK)
  Auth->>ACR: attempt ACR auth (service-principal/default)
  Auth->>GCR: attempt GCR (ADC)
  Auth-->>OCI: authenticator or error
  alt authenticator returned
    OCI->>Registry: pull with auth
  else
    OCI->>Registry: pull anonymously
  end
Loading
sequenceDiagram
  autonumber
  participant OCI as processOciImage
  participant Layer as image layer
  participant ZIP as extractZipFile
  participant TAR as tar extractor
  participant RAW as extractRawData
  OCI->>Layer: iterate layers
  alt mediaType contains "zip"
    OCI->>ZIP: extractZipFile(reader, dest)
    alt success
      ZIP-->>OCI: files extracted
    else
      ZIP-->>OCI: error
      OCI->>RAW: extractRawData(reader, dest, idx)
    end
  else
    OCI->>TAR: extract tar
    alt success
      TAR-->>OCI: files extracted
    else
      TAR-->>OCI: error
      OCI->>RAW: extractRawData(reader, dest, idx)
    end
  end
  OCI-->>OCI: log per-layer outcomes and summary
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Assessment against linked issues

Objective Addressed Explanation
Support fetching Terraform modules from private registries via Terraform credentials file ~/.terraform.d/credentials.tfrc.json (#369) The PR implements container registry auth, but there is no code reading or using ~/.terraform.d/credentials.tfrc.json.
Support TF_TOKEN_* environment variable-based Terraform registry auth (#369) No handling of TF_TOKEN_* or Terraform registry token mapping is present.
Enable Atmos to fetch from private Terraform registries over HTTPS (#369) The PR adds OCI container auth which may help OCI-backed module sources, but does not implement Terraform-specific credential resolution (tfrc/TF_TOKEN).

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
ACR OAuth token-exchange implementation (internal/exec/oci_azure.go) Adds container ACR OAuth flows for OCI images; unrelated to Terraform credential file or TF_TOKEN_* handling requested in #369.
ECR auth and AWS SDK usage (internal/exec/oci_aws.go) Implements AWS ECR auth for OCI registries via AWS SDK; unrelated to Terraform registry credential formats.
Docker credential-helper invocation (internal/exec/oci_docker.go) Resolves Docker credentials for OCI pulls from Docker config/credential helpers; does not implement Terraform registry token semantics or tfrc parsing.

Possibly related PRs

Suggested labels

minor

Suggested reviewers

  • osterman
  • aknysh
  • milldr
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link
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 (9)
internal/exec/oci_utils.go (4)

329-345: Cloud provider auth functions need implementation

The ECR authentication placeholder returns an error indicating incomplete implementation. Consider implementing or clearly documenting this as a limitation.

Would you like me to help implement the AWS ECR authentication using the AWS SDK? I can open an issue to track this enhancement.


348-356: ACR auth needs implementation

Azure Container Registry authentication is not fully implemented.

Would you like me to help implement the Azure ACR authentication? I can create an issue to track this.


359-367: GCR auth needs implementation

Google Container Registry authentication is not fully implemented.

Would you like me to help implement the Google GCR authentication? I can create an issue to track this.


450-468: Consider adding file permissions to raw data extraction

The raw data extraction creates files with default permissions. Consider setting appropriate permissions.

 func extractRawData(reader io.Reader, destDir string, layerIndex int) error {
 	// Create a temporary file to store the raw data
 	tempFile := filepath.Join(destDir, fmt.Sprintf("layer_%d_raw", layerIndex))
 
 	file, err := os.Create(tempFile)
 	if err != nil {
 		return fmt.Errorf("failed to create temp file: %w", err)
 	}
 	defer file.Close()
 
 	// Copy the raw data
 	_, err = io.Copy(file, reader)
 	if err != nil {
 		return fmt.Errorf("failed to copy raw data: %w", err)
 	}
 
+	// Set appropriate permissions
+	if err := os.Chmod(tempFile, 0644); err != nil {
+		log.Warn("Failed to set permissions on raw data file", "file", tempFile, "error", err)
+	}
+
 	log.Debug("Extracted raw data to temp file", "file", tempFile)
 	return nil
 }
internal/exec/oci_utils_test.go (5)

146-148: Test modifies global HOME environment variable

The test modifies the HOME environment variable which could affect parallel test execution.

Consider using t.Setenv for automatic cleanup:

-				originalHome := os.Getenv("HOME")
-				os.Setenv("HOME", tempDir)
-				defer os.Setenv("HOME", originalHome)
+				t.Setenv("HOME", tempDir)

35-37: Use t.Setenv for environment variable management

Multiple test functions manually set and unset environment variables. Use t.Setenv for automatic cleanup.

Replace all os.Setenv and os.Unsetenv calls with t.Setenv:

 			setupEnv: func() {
-				os.Setenv("GITHUB_TOKEN", "test-token")
+				t.Setenv("GITHUB_TOKEN", "test-token")
 			},

Also applies to: 44-46, 54-56, 64-66, 101-102, 110-111, 119-120


238-288: Consider capturing log output for artifact type validation

The test acknowledges that checkArtifactType only logs. Consider capturing logs to verify behavior.

You could use a log capture mechanism to verify the function's behavior instead of just calling it without assertions.


450-475: Integration test needs enhancement

The integration test only validates error cases. Consider adding mock registry interactions for positive test cases.

Consider using a mock OCI registry or the go-containerregistry test helpers to create more comprehensive integration tests.


511-520: Benchmark should reset environment after test

The benchmark sets an environment variable but doesn't clean it up.

 func BenchmarkGetRegistryAuth(b *testing.B) {
 	// Setup test environment
-	os.Setenv("GITHUB_TOKEN", "test-token")
+	b.Setenv("GITHUB_TOKEN", "test-token")
 
 	b.ResetTimer()
 
 	for i := 0; i < b.N; i++ {
 		_, _ = getRegistryAuth("ghcr.io")
 	}
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 ba8288a and 07f9a7e.

📒 Files selected for processing (2)
  • internal/exec/oci_utils.go (7 hunks)
  • internal/exec/oci_utils_test.go (1 hunks)
🧰 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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Files:

  • internal/exec/oci_utils_test.go
  • internal/exec/oci_utils.go
**/*_test.go

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

**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for testing multiple scenarios
Include integration tests for command flows
Test CLI end-to-end when possible
Use test fixtures for complex inputs
Consider using testify/mock for creating mock implementations

Files:

  • internal/exec/oci_utils_test.go
🧠 Learnings (1)
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*_test.go : Include integration tests for command flows

Applied to files:

  • internal/exec/oci_utils_test.go
🧬 Code graph analysis (1)
internal/exec/oci_utils_test.go (1)
pkg/schema/schema.go (1)
  • AtmosConfiguration (25-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (11)
internal/exec/oci_utils.go (8)

106-113: Authentication fallback logic looks good

The fallback to anonymous authentication when no credentials are found is a sensible approach for public registries.


124-189: Comprehensive authentication orchestration

The multi-source authentication flow is well-structured with clear precedence order and appropriate logging at each step.


76-93: Good error resilience in layer processing

The approach to continue processing other layers when one fails improves robustness. The summary logging provides useful debugging information.


403-412: Smart format detection for layer extraction

The media type-based extraction with ZIP detection is a good approach for handling different artifact formats.


414-447: Robust fallback extraction strategy

The multi-level fallback (ZIP → TAR → raw data) with special handling for metadata layers shows good defensive programming.


471-523: ZIP extraction implementation is solid

The ZIP extraction properly handles directory creation and file extraction. Good error handling throughout.


533-552: Artifact type checking is comprehensive

Good support for multiple artifact types with clear logging of supported vs unsupported types.


277-309: Potential command injection vulnerability in credential store execution

The registry parameter is directly concatenated into the command execution without validation. A malicious registry name could potentially inject commands.

Add input validation:

 func getCredentialStoreAuth(registry, credsStore string) (authn.Authenticator, error) {
+	// Validate registry to prevent command injection
+	if strings.ContainsAny(registry, ";&|`$(){}[]<>'\"\n\r") {
+		return nil, fmt.Errorf("invalid registry name: %s", registry)
+	}
+	
+	// Validate credsStore to prevent command injection
+	if strings.ContainsAny(credsStore, ";&|`$(){}[]<>/\\") {
+		return nil, fmt.Errorf("invalid credential store name: %s", credsStore)
+	}
+
 	// For Docker Desktop on macOS, the credential store is typically "desktop"
 	// We need to use the docker-credential-desktop helper to get credentials
 
 	// Try to execute the credential helper
 	cmd := exec.Command("docker-credential-"+credsStore, "get")

Likely an incorrect or invalid review comment.

internal/exec/oci_utils_test.go (3)

23-170: Comprehensive authentication test coverage

The table-driven tests cover all authentication sources well, including edge cases and placeholder implementations.


172-216: ZIP extraction test is thorough

Good test coverage including subdirectories and content verification.


477-509: Benchmark tests are valuable

Good addition of benchmarks for performance-critical operations.

coderabbitai[bot]
coderabbitai bot previously approved these changes Aug 22, 2025
@mergify mergify bot removed the triage Needs triage label Aug 22, 2025
…on, test environment cleanup, and enhanced test coverage

Signed-off-by: Chris Harden <[email protected]>
Copy link
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: 4

🧹 Nitpick comments (2)
internal/exec/oci_utils.go (2)

165-170: Incomplete ECR authentication implementation

The ECR authentication returns an error indicating it's not fully implemented. This should either be completed or clearly documented as a limitation.

Would you like me to help implement the complete ECR authentication using the AWS SDK, or should we open an issue to track this?


483-486: Memory concern with ReadAll for ZIP extraction

Reading entire ZIP files into memory could cause issues with large archives.

Consider streaming extraction for large files or add a size limit check:

+const maxZipSize = 500 * 1024 * 1024 // 500MB limit
+
+// Check layer size first if available
+if size, err := layer.Size(); err == nil && size > maxZipSize {
+    return fmt.Errorf("ZIP file too large: %d bytes (max: %d)", size, maxZipSize)
+}
+
 zipData, err := io.ReadAll(reader)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 07f9a7e and e5a903e.

📒 Files selected for processing (2)
  • internal/exec/oci_utils.go (7 hunks)
  • internal/exec/oci_utils_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/exec/oci_utils_test.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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Files:

  • internal/exec/oci_utils.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (4)
internal/exec/oci_utils.go (4)

76-83: Good defensive approach to layer processing

Tracking successful layers and continuing despite failures improves resilience when dealing with problematic OCI images.


106-113: Solid fallback to anonymous auth

The authentication flow properly defaults to anonymous access when credentials aren't found, maintaining backward compatibility.


280-287: Good command injection prevention

The validation checks for potentially dangerous characters prevent command injection attacks when interfacing with external credential helpers.


152-155: Potential security concern with environment variable naming

The current implementation allows dots in registry names to be replaced with underscores, which could lead to naming collisions. For example, both my.registry.com and my-registry.com would map to the same environment variables MY_REGISTRY_COM_USERNAME and MY_REGISTRY_COM_PASSWORD.

Consider using a more unique separator or hash-based approach:

-registryEnvName := strings.ToUpper(strings.ReplaceAll(registry, ".", "_"))
+// Replace dots and hyphens with double underscores to avoid collisions
+registryEnvName := strings.ToUpper(strings.ReplaceAll(strings.ReplaceAll(registry, ".", "__"), "-", "___"))
⛔ Skipped due to learnings
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use snake_case for environment variables

Copy link
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: 4

♻️ Duplicate comments (1)
internal/exec/oci_utils.go (1)

295-303: Add timeout to docker-credential helper execution to avoid hangs

This was raised previously and remains. Add a 5s timeout and return a clear error when exceeded.

-	cmd := exec.Command("docker-credential-"+credsStore, "get")
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "docker-credential-"+credsStore, "get")
 	cmd.Stdin = strings.NewReader(registry)
 
-	output, err := cmd.Output()
+	output, err := cmd.Output()
 	if err != nil {
-		return nil, fmt.Errorf("failed to get credentials from store %s: %w", credsStore, err)
+		if ctx.Err() == context.DeadlineExceeded {
+			return nil, fmt.Errorf("credential helper %q timed out", credsStore)
+		}
+		return nil, fmt.Errorf("failed to get credentials from store %s: %w", credsStore, err)
 	}
🧹 Nitpick comments (3)
internal/exec/oci_utils.go (3)

3-21: Unify error handling (drop github.com/pkg/errors) and add time import for helper timeouts

You’re already using fmt.Errorf with %w everywhere. Keeping github.com/pkg/errors only for errors.New is unnecessary and can confuse linters. Also, we’ll need time for credential-helper timeouts below.

Apply this diff to the imports:

 import (
 	"archive/zip"
 	"bytes"
 	"context"
 	"encoding/base64"
 	"encoding/json"
 	"fmt"
 	"io"
 	"os"
 	"os/exec"
 	"path/filepath"
 	"strings"

 	log "github.com/charmbracelet/log" // Charmbracelet structured logger
-	"github.com/pkg/errors"
+	"errors"
+	"time"

 	"github.com/aws/aws-sdk-go-v2/config"
 	"github.com/aws/aws-sdk-go-v2/service/ecr"
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
 	"github.com/google/uuid"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"

 	"github.com/cloudposse/atmos/pkg/schema"
 )

No other code changes required besides the import swap; var ErrNoLayers will now bind to the stdlib errors package, which is desired.


468-477: Detect ZIP by magic bytes as well, not just mediaType substring

Layer media types are often tar+gzip. Some registries may store ZIP data under generic/custom types. Lightweight sniffing reduces false negatives.

-	// Check if it's a ZIP file
-	mediaTypeStr := string(mediaType)
-	if strings.Contains(mediaTypeStr, "zip") {
+	mediaTypeStr := string(mediaType)
+	// Peek first 4 bytes to detect ZIP magic (PK\x03\x04)
+	header := make([]byte, 4)
+	n, _ := io.ReadFull(uncompressed, header)
+	if n > 0 {
+		uncompressed = io.NopCloser(io.MultiReader(bytes.NewReader(header[:n]), uncompressed))
+	}
+	if (n == 4 && bytes.Equal(header[:4], []byte("PK\x03\x04"))) || strings.Contains(mediaTypeStr, "zip") {
 		log.Debug("Detected ZIP layer, extracting as ZIP", "index", index, "digest", layerDesc, "mediaType", mediaTypeStr)
 		extractionErr = extractZipFile(uncompressed, destDir)
 	} else {
 		// Default to tar extraction
 		log.Debug("Extracting as TAR", "index", index, "digest", layerDesc, "mediaType", mediaTypeStr)
 		extractionErr = extractTarball(uncompressed, destDir)
 	}

109-116: Differentiate “no creds” from “error reading creds”

Right now any error from getRegistryAuth is treated as “no auth found”. Consider a sentinel error (e.g., ErrNoRegistryAuth) to distinguish “not found” from real failures (e.g., unreadable $HOME or malformed Docker config) and log accordingly.

Optional small refactor:

  • getRegistryAuth returns (authn.Authenticator, error) where error is ErrNoRegistryAuth for the “not found” case.
  • Here, if errors.Is(err, ErrNoRegistryAuth) then fall back to anonymous; otherwise log a warning.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 e5a903e and 6eaab89.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • go.mod (1 hunks)
  • internal/exec/oci_utils.go (7 hunks)
  • internal/exec/oci_utils_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/exec/oci_utils_test.go
🧰 Additional context used
📓 Path-based instructions (2)
go.mod

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

Manage dependencies with Go modules

Files:

  • go.mod
**/*.go

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

**/*.go: Use Viper for managing configuration, environment variables, and flags
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Files:

  • internal/exec/oci_utils.go
🧠 Learnings (1)
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to go.mod : Manage dependencies with Go modules

Applied to files:

  • go.mod
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (4)
go.mod (2)

24-24: ECR SDK dependency addition looks good

Adding github.com/aws/aws-sdk-go-v2/service/ecr v1.49.2 aligns with the new getECRAuth implementation in internal/exec/oci_utils.go. No concerns from me here.


3-3: Pin minimum Go version and lock patch release
The go directive in go.mod may include a patch version to express the minimum required Go version (e.g., go 1.24.5), but if you need to suggest or enforce a specific patch release for reproducible builds, you should use the toolchain directive.

Locations to update:

  • go.mod: line 3

Proposed diff:

-go 1.24.5
+go 1.24
+toolchain go1.24.5

Please confirm that your CI/build environment is using Go 1.21 or newer—so it supports the toolchain directive. If it does not yet, you can either keep go 1.24.5 (allowed as a minimum version) or drop the patch in the Go directive (go 1.24) without adding a toolchain line.

internal/exec/oci_utils.go (2)

485-495: Good fix: close old reader before resetting

Closing uncompressed before reassigning avoids leaks. This addresses prior concerns.


606-625: Artifact-type check expansion LGTM

SupportedTypes list and logging behavior look good and match the PR scope.

@aknysh aknysh added no-release Do not create a new release (wait for additional code changes) and removed triage Needs triage labels Aug 24, 2025
Copy link

@github-advanced-security github-advanced-security bot left a comment

Choose a reason for hiding this comment

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

golangci-lint found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

Copy link
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: 7

Caution

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

⚠️ Outside diff range comments (1)
go.mod (1)

3-3: Invalid Go version directive; use major.minor and optionally pin toolchain

The go directive must be major.minor (e.g., go 1.24), not a patch version. Consider adding a separate toolchain directive if you want to pin 1.24.5.

Apply this diff:

-go 1.24.5
+go 1.24
+toolchain go1.24.5
♻️ Duplicate comments (1)
internal/exec/oci_utils.go (1)

296-301: Add timeout to credential helper exec to avoid hangs

- cmd := exec.Command("docker-credential-"+credsStore, "get")
- cmd.Stdin = strings.NewReader(registry)
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ defer cancel()
+ cmd := exec.CommandContext(ctx, "docker-credential-"+credsStore, "get")
+ cmd.Stdin = strings.NewReader(registry)

Also add time to imports.

 import (
   "archive/zip"
   "bytes"
   "context"
   "encoding/base64"
   "encoding/json"
   "fmt"
   "io"
   "os"
   "os/exec"
   "path/filepath"
   "strings"
+  "time"
 )
🧹 Nitpick comments (4)
internal/exec/oci_utils.go (4)

101-116: Add request timeout when pulling images

Guard against network hangs by using a context with timeout for remote.Get.

-func pullImage(ref name.Reference) (*remote.Descriptor, error) {
-  var opts []remote.Option
+func pullImage(ref name.Reference) (*remote.Descriptor, error) {
+  var opts []remote.Option
+  ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
+  defer cancel()
@@
-  descriptor, err := remote.Get(ref, opts...)
+  opts = append(opts, remote.WithContext(ctx))
+  descriptor, err := remote.Get(ref, opts...)

79-86: Post-extraction checks may miscount due to pre-existing files

If destDir already has files, fileCount isn’t the number of extracted files. Consider ensuring destDir exists and capturing pre/post counts.

   successfulLayers++
 }
 
 // Check if any files were actually extracted
-files, err := os.ReadDir(destDir)
+_ = os.MkdirAll(destDir, 0o755)
+files, err := os.ReadDir(destDir)

Also applies to: 88-96


540-551: Avoid loading entire ZIP into memory; spool to temp file

Reading all data with io.ReadAll can blow memory on large layers. Stream to a temp file and use zip.OpenReader.

-  // Read the entire ZIP data into memory
-  zipData, err := io.ReadAll(reader)
-  if err != nil {
-    return fmt.Errorf("failed to read ZIP data: %w", err)
-  }
-
-  // Create a ZIP reader
-  zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
+  tmpf, err := os.CreateTemp("", "atmos-zip-*")
+  if err != nil {
+    return fmt.Errorf("failed to create temp file for ZIP: %w", err)
+  }
+  // Ensure temp file is removed
+  tmpName := tmpf.Name()
+  defer func() { tmpf.Close(); os.Remove(tmpName) }()
+  if _, err := io.Copy(tmpf, reader); err != nil {
+    return fmt.Errorf("failed to buffer ZIP data: %w", err)
+  }
+  if _, err := tmpf.Seek(0, io.SeekStart); err != nil {
+    return fmt.Errorf("failed to rewind ZIP temp file: %w", err)
+  }
+  zr, err := zip.OpenReader(tmpName)
   if err != nil {
     return fmt.Errorf("failed to create ZIP reader: %w", err)
   }
+  defer zr.Close()

And iterate for _, file := range zr.File { ... }.


341-410: Cloud-provider auth stubs: document and feature-flag to avoid confusing logs

ACR/GCR helpers intentionally return errors. Consider adding TODOs with a short rationale and feature flags or log.Info to avoid implying partial success.

I can add TODOs and a simple config flag (e.g., atmos.vendor.allowCloudAuth) wired via Viper to guard these paths. Want a patch?

Also applies to: 412-432

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 6eaab89 and da3ffd6.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • go.mod (2 hunks)
  • internal/exec/oci_utils.go (7 hunks)
  • internal/exec/oci_utils_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/exec/oci_utils_test.go
🧰 Additional context used
📓 Path-based instructions (2)
go.mod

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

Manage dependencies with Go modules

Files:

  • go.mod
**/*.go

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

**/*.go: Use Viper for managing configuration, environment variables, and flags
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Files:

  • internal/exec/oci_utils.go
🧠 Learnings (1)
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to go.mod : Manage dependencies with Go modules

Applied to files:

  • go.mod
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
go.mod (2)

133-134: Sync internal AWS modules with core SDK

Bumping internal/configsources and internal/endpoints/v2 is good to keep parity with the updated core SDK. No action needed beyond ensuring CI passes with these versions.


17-22: Verification successful: AWS ECR dependency cleanly integrated

The go mod tidy and go build commands exited without errors, confirming that:

  • The direct dependency on github.com/aws/aws-sdk-go-v2/service/ecr v1.49.2 is correctly resolved.
  • All aws-sdk-go-v2 module versions remain consistent and no transitive conflicts were introduced.

Everything is good to merge.

internal/exec/oci_utils.go (1)

598-626: Artifact type checks look good

Recognition of multiple Terraform/OpenTofu artifact types with clear logging is on point.

osterman and others added 7 commits August 25, 2025 10:16
Docker Config: ~/.docker/config.json + DOCKER_CONFIG env var
Environment Variables: Registry-specific auth vars
AWS ECR: Full implementation with region extraction
Azure ACR: Service principal + Azure CLI support
Terraform: TF_TOKEN_* env vars + credentials.tfrc.json
GitHub Container Registry: GITHUB_TOKEN support

Signed-off-by: Chris Harden <[email protected]>
Docker cred helper
AWS ECR optimizations

Signed-off-by: Chris Harden <[email protected]>
…mes for hyphenated registries

Signed-off-by: Chris Harden <[email protected]>
…egion, and prefer matching ProxyEndpoint

Signed-off-by: Chris Harden <[email protected]>
Copy link
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

♻️ Duplicate comments (1)
internal/exec/oci_utils.go (1)

335-341: Add a timeout for docker credential helper execution

External helpers can hang and block pulls. Use CommandContext with a sane timeout.

Apply this diff:

-	cmd := exec.Command("docker-credential-"+credsStore, "get")
-	cmd.Stdin = strings.NewReader(registry)
+	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+	defer cancel()
+	cmd := exec.CommandContext(ctx, "docker-credential-"+credsStore, "get")
+	cmd.Stdin = strings.NewReader(registry)

And ensure the import is present:

// at top
import "time"
🧹 Nitpick comments (7)
internal/exec/oci_utils.go (7)

595-632: Avoid double-closing the layer reader and simplify fallback flow

You defer Close() on the initial reader, then close it manually and add a second defer after reassigning. While usually harmless, it's brittle and can mask errors.

Refactor to a small helper that obtains a fresh Uncompressed() per attempt and closes it exactly once per attempt:

extract := func(fn func(io.Reader) error) error {
    r, err := layer.Uncompressed()
    if err != nil { return fmt.Errorf("layer decompression error: %w", err) }
    defer r.Close()
    return fn(r)
}

if strings.Contains(mediaTypeStr, "zip") {
    extractionErr = extract(func(r io.Reader) error { return extractZipFile(r, destDir) })
} else {
    extractionErr = extract(func(r io.Reader) error { return extractTarball(r, destDir) })
}

if extractionErr != nil {
    if err := extract(func(r io.Reader) error { return extractRawData(r, destDir, index) }); err != nil {
        if index == 0 { log.Warn("..."); return nil }
        return fmt.Errorf("all extraction methods failed: %w", err)
    }
}

678-687: Don’t load entire ZIP into memory; spool to a temp file or stream

Reading all ZIP bytes can OOM on large layers. Prefer spooling to disk and using zip.OpenReader.

Apply this diff to spool to a temp file (and clean it up):

-	// Read the entire ZIP data into memory
-	zipData, err := io.ReadAll(reader)
-	if err != nil {
-		return fmt.Errorf("failed to read ZIP data: %w", err)
-	}
-
-	// Create a ZIP reader
-	zipReader, err := zip.NewReader(bytes.NewReader(zipData), int64(len(zipData)))
-	if err != nil {
-		return fmt.Errorf("failed to create ZIP reader: %w", err)
-	}
+	// Spool ZIP to a temp file to avoid unbounded memory usage
+	tmpf, err := os.CreateTemp("", "atmos_zip_*")
+	if err != nil {
+	return fmt.Errorf("failed to create temp ZIP file: %w", err)
+	}
+	tmpName := tmpf.Name()
+	defer func() { tmpf.Close(); os.Remove(tmpName) }()
+	if _, err := io.Copy(tmpf, reader); err != nil {
+		return fmt.Errorf("failed to buffer ZIP data: %w", err)
+	}
+	if _, err := tmpf.Seek(0, io.SeekStart); err != nil {
+		return fmt.Errorf("failed to rewind temp ZIP file: %w", err)
+	}
+	zipReader, err := zip.OpenReader(tmpName)
+	if err != nil {
+		return fmt.Errorf("failed to open ZIP: %w", err)
+	}
+	defer zipReader.Close()

Follow-ups:

  • Consider a size cap and reject overly large archives early.

101-116: Remote pull lacks a context; add a timeout to avoid indefinite hangs

Network pulls can stall. Pass remote.WithContext with a timeout.

Apply this diff:

 func pullImage(ref name.Reference) (*remote.Descriptor, error) {
-	var opts []remote.Option
+	var opts []remote.Option
+	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
+	defer cancel()
+	opts = append(opts, remote.WithContext(ctx))

Optionally make the timeout configurable (flags/env/config via Viper).

Also applies to: 118-125


136-142: Prefer Viper over direct os.Getenv for env-backed configuration

Atmos prefers Viper-managed env access for consistency and testability. This applies to GITHUB_TOKEN, DOCKER_CONFIG, AZURE_, AZURE_CLI_AUTH, TF_TOKEN_, and GCP envs.

I’m leveraging our prior learning about avoiding direct os.Getenv. A minimal migration pattern:

import "github.com/spf13/viper"

func init() {
    viper.AutomaticEnv()
    viper.BindEnv("github_token", githubTokenEnv)
    viper.BindEnv("docker_config", "DOCKER_CONFIG")
    // ... bind other keys as needed ...
}

// usage
if token := viper.GetString("github_token"); token != "" { ... }
configDir := viper.GetString("docker_config")

This keeps precedence consistent (flags > env > config > defaults) and eases testing.

Also applies to: 145-151, 160-167, 207-217, 462-466, 470-473, 563-566, 801-804


43-48: Remove unused tempDir

tempDir is created and removed but never used.

Apply this diff:

-	tempDir, err := os.MkdirTemp("", uuid.New().String())
-	if err != nil {
-		return fmt.Errorf("failed to create temp directory: %w", err)
-	}
-	defer removeTempDir(tempDir)
+	// no temp directory needed here

757-761: Doc comment tweak for parseOCIManifest

The function reads from an io.Reader, not necessarily a “JSON file”.

Apply this diff:

-// ParseOCIManifest reads and decodes an OCI manifest from a JSON file.
+// parseOCIManifest reads and decodes an OCI manifest from a JSON stream.

Also applies to: 787-795


319-361: Abstract external dependencies to interfaces for better testability

Direct calls to exec.Command and AWS SDK make unit testing harder. Thin interfaces would let tests inject fakes.

Example:

type CredHelper interface {
	Get(registry, helper string, timeout time.Duration) ([]byte, error)
}
type ECRClient interface {
	GetAuthorizationToken(ctx context.Context, in *ecr.GetAuthorizationTokenInput) (*ecr.GetAuthorizationTokenOutput, error)
}

Provide default implementations in production; use fakes in tests. This will also simplify injecting timeouts.

Also applies to: 397-407, 503-505, 521-526

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 0b43b70 and 630925e.

📒 Files selected for processing (2)
  • internal/exec/oci_utils.go (8 hunks)
  • internal/exec/oci_utils_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/exec/oci_utils_test.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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Files:

  • internal/exec/oci_utils.go
🧠 Learnings (5)
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
PR: cloudposse/atmos#727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-31T07:09:31.983Z
Learnt from: Cerebrovinny
PR: cloudposse/atmos#737
File: internal/exec/vendor_utils.go:181-182
Timestamp: 2024-10-31T07:09:31.983Z
Learning: In `internal/exec/vendor_utils.go`, the variables `mergedSources` and `mergedImports` are declared and used later in the code. Do not suggest removing them as unused variables.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use snake_case for environment variables

Applied to files:

  • internal/exec/oci_utils.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
internal/exec/oci_utils.go (3)

702-719: Zip Slip protection looks solid

Good defense-in-depth: absolute path checks, Windows drive handling, clean/join, and prefix validation. Skipping symlinks is also prudent.


766-784: Supported artifact type check is clear and appropriately lenient

Good: logs supported types and doesn’t hard-fail. This will help users diagnose unexpected media types without breaking workflows.


127-201: Confirm intended auth precedence (Terraform vs Docker vs env)

Current order: GHCR token → Terraform (TF_TOKEN_* / tfrc) → Docker config/cred helpers → REGISTRY_NAME_{USERNAME,PASSWORD} → ECR/ACR/GCR. The PR summary previously listed Docker/env ahead of Terraform. Please confirm the intended precedence and update docs/tests accordingly.

If Terraform should come after Docker and env, reorder here and adjust tests. If this is the new desired order, please note it in the README/CHANGELOG.

@cmharden
Copy link
Author

cmharden commented Sep 5, 2025

Please make sure to run golanglint-ci locally on the changes and gofumpt for formatting. Make sure all tests are passing, at least in our CI.

Pushed

Copy link
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: 6

🧹 Nitpick comments (18)
internal/exec/oci_azure_test.go (5)

21-24: DRY the E2E skip checks.

Replace repeated env checks with a helper to keep tests tidy.

@@
-func TestACRAuthDirect(t *testing.T) {
-  if os.Getenv("ATMOS_AZURE_E2E") == "" {
-    t.Skip("Skipping Azure integration test (set ATMOS_AZURE_E2E=1 to run)")
-  }
+func TestACRAuthDirect(t *testing.T) {
+  azureSkipIfNotE2E(t)
@@
-func TestGetACRAuthViaServicePrincipalDirect(t *testing.T) {
-  if os.Getenv("ATMOS_AZURE_E2E") == "" {
-    t.Skip("Skipping Azure integration test (set ATMOS_AZURE_E2E=1 to run)")
-  }
+func TestGetACRAuthViaServicePrincipalDirect(t *testing.T) {
+  azureSkipIfNotE2E(t)
@@
-func TestGetACRAuthViaDefaultCredentialDirect(t *testing.T) {
-  if os.Getenv("ATMOS_AZURE_E2E") == "" {
-    t.Skip("Skipping Azure integration test (set ATMOS_AZURE_E2E=1 to run)")
-  }
+func TestGetACRAuthViaDefaultCredentialDirect(t *testing.T) {
+  azureSkipIfNotE2E(t)
@@
-func TestExchangeAADForACRRefreshTokenDirect(t *testing.T) {
-  if os.Getenv("ATMOS_AZURE_E2E") == "" {
-    t.Skip("Skipping Azure integration test (set ATMOS_AZURE_E2E=1 to run)")
-  }
+func TestExchangeAADForACRRefreshTokenDirect(t *testing.T) {
+  azureSkipIfNotE2E(t)
+
+}
+
+func azureSkipIfNotE2E(t *testing.T) {
+  if os.Getenv("ATMOS_AZURE_E2E") == "" {
+    t.Skip("Skipping Azure integration test (set ATMOS_AZURE_E2E=1 to run)")
+  }
 }

Also applies to: 87-90, 161-164, 213-216


299-333: Assert request details in the stubbed HTTP flow.

Validate method/host/headers to catch regressions without hitting the network.

@@
-  mockClient := &http.Client{
-    Timeout: 30 * time.Second,
-    Transport: &mockTransport{
-      response: &http.Response{
-        StatusCode: http.StatusOK,
-        Header:     http.Header{"Content-Type": []string{"application/json"}},
-        Body:       io.NopCloser(strings.NewReader(`{"refresh_token": "mock-refresh-token", "access_token": "mock-access-token"}`)),
-      },
-    },
-  }
+  mockClient := &http.Client{
+    Timeout: 10 * time.Second,
+    Transport: roundTripFunc(func(req *http.Request) (*http.Response, error) {
+      assert.Equal(t, http.MethodPost, req.Method)
+      assert.Equal(t, "test.azurecr.io", req.URL.Host)
+      assert.Equal(t, "application/json", req.Header.Get("Content-Type"))
+      assert.Equal(t, "Bearer test-token", req.Header.Get("Authorization"))
+      return &http.Response{
+        StatusCode: http.StatusOK,
+        Header:     http.Header{"Content-Type": []string{"application/json"}},
+        Body:       io.NopCloser(strings.NewReader(`{"refresh_token": "mock-refresh-token", "access_token": "mock-access-token"}`)),
+      }, nil
+    }),
+  }
@@
-// mockTransport is a simple mock transport for testing
-type mockTransport struct {
-  response *http.Response
-}
-
-func (m *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
-  return m.response, nil
-}
+// roundTripFunc helps inline stub HTTP behavior in tests.
+type roundTripFunc func(*http.Request) (*http.Response, error)
+
+func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
+  return f(req)
+}

Also applies to: 335-342


344-412: Parallelize pure JWT parsing tests.

Safe speed-up; avoids coupling with E2E tests.

 func TestExtractTenantIDFromTokenDirect(t *testing.T) {
+  t.Parallel()
   tests := []struct {
@@
-  for _, tt := range tests {
-    t.Run(tt.name, func(t *testing.T) {
+  for _, tt := range tests {
+    tt := tt
+    t.Run(tt.name, func(t *testing.T) {
+      t.Parallel()
       tenantID, err := extractTenantIDFromToken(tt.token)

85-85: Finish comments with periods.

Minor doc polish per repo guidelines.

-// TestGetACRAuthViaServicePrincipalDirect tests Azure Service Principal authentication directly
+// TestGetACRAuthViaServicePrincipalDirect tests Azure Service Principal authentication directly.
@@
-// mockTransport is a simple mock transport for testing
+// mockTransport is a simple mock transport for testing.

Also applies to: 335-335


132-140: Prefer explicit input validation for missing client ID.

A deterministic validation error is better than bubbling up SDK errors.

-      expectError:  true,
-      errorMsg:     "failed to get Azure token",
+      expectError:  true,
+      errorMsg:     "client ID is required",

If you adopt this, ensure getACRAuthViaServicePrincipal validates inputs and returns wrapped errors like:
fmt.Errorf("azure service principal auth: client ID is required").

internal/exec/oci_google.go (3)

42-44: Fix outdated comment to match implementation.

The comment still mentions "_dcg" username; code uses "oauth2accesstoken".

Apply this diff:

-// For GCR, we use the token as the password with "_dcg" as username
-// This is the standard pattern for GCR authentication
+// For GCR/Artifact Registry, use an OAuth2 access token as the password with
+// the username "oauth2accesstoken". This is the standard pattern for GCR/AR authentication.

20-48: Prefer google.Keychain for ADC/gcloud and per-registry auth.

Keychain handles *.gcr.io and *.pkg.dev seamlessly (ADC, gcloud), and avoids manual token plumbing.

Apply this diff:

 import (
-	"context"
-	"errors"
-	"fmt"
-
-	log "github.com/charmbracelet/log"
-	"github.com/google/go-containerregistry/pkg/authn"
-	"golang.org/x/oauth2/google"
+	"context"
+	"errors"
+	"fmt"
+
+	log "github.com/charmbracelet/log"
+	"github.com/google/go-containerregistry/pkg/authn"
+	gck "github.com/google/go-containerregistry/pkg/authn/google"
+	"github.com/google/go-containerregistry/pkg/name"
 )
@@
 // getGCRAuth attempts to get Google Container Registry authentication.
 func getGCRAuth(registry string) (authn.Authenticator, error) {
-	// Use Google Cloud Application Default Credentials
-	ctx := context.Background()
-	creds, err := google.FindDefaultCredentials(ctx, "https://www.googleapis.com/auth/cloud-platform")
-	if err != nil {
-		log.Debug("Failed to find Google Cloud credentials", logFieldRegistry, registry, "error", err)
-		return nil, fmt.Errorf("%w: %w", errFailedToFindGoogleCloudCredentials, err)
-	}
-
-	if creds == nil || creds.TokenSource == nil {
-		log.Debug("No Google Cloud credentials found", logFieldRegistry, registry)
-		return nil, fmt.Errorf("%w %s", errNoGoogleCloudCredentialsFound, registry)
-	}
-
-	// Get a token from the credentials
-	token, err := creds.TokenSource.Token()
-	if err != nil {
-		log.Debug("Failed to get Google Cloud token", logFieldRegistry, registry, "error", err)
-		return nil, fmt.Errorf("%w: %w", errFailedToGetGoogleCloudToken, err)
-	}
-
-	// For GCR, we use the token as the password with "_dcg" as username
-	// This is the standard pattern for GCR authentication
-	log.Debug("Successfully obtained Google Cloud credentials", logFieldRegistry, registry)
-	return &authn.Basic{
-		Username: "oauth2accesstoken",
-		Password: token.AccessToken,
-	}, nil
+	if registry == "" {
+		return nil, fmt.Errorf("empty registry")
+	}
+	r, err := name.NewRegistry(registry, name.WeakValidation)
+	if err != nil {
+		return nil, fmt.Errorf("invalid registry %q: %w", registry, err)
+	}
+	// Let google.Keychain resolve ADC/gcloud for this registry.
+	auth, err := gck.Keychain.Resolve(r)
+	if err != nil {
+		log.Debug("No Google credentials resolved", logFieldRegistry, registry, "error", err)
+		return nil, fmt.Errorf("%w: %w", errNoGoogleCloudCredentialsFound, err)
+	}
+	log.Debug("Successfully resolved Google credentials", logFieldRegistry, registry)
+	return auth, nil
 }

22-39: Add a short timeout to ADC token discovery.

Prevents hangs in misconfigured environments.

Apply this diff (if keeping the current ADC flow):

-	ctx := context.Background()
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()

And add:

 import (
 	"context"
+	"time"
internal/exec/oci_aws_test.go (2)

10-46: LGTM on negative cases; add t.Parallel.

Parallelize tests and subtests for speed and isolation.

Apply this diff:

 func TestECRAuthDirect(t *testing.T) {
+	t.Parallel()
@@
-	for _, tt := range tests {
+	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
 			_, err := getECRAuth(tt.registry)

48-93: Exercise the actual parser instead of string heuristics.

Use parseECRRegistry to validate account/region extraction across cases.

Apply this diff:

-// TestECRRegistryParsing tests the ECR registry parsing logic.
+// TestECRRegistryParsing tests the ECR registry parsing logic.
 func TestECRRegistryParsing(t *testing.T) {
-	tests := []struct {
-		name     string
-		registry string
-		isECR    bool
-	}{
-		{
-			name:     "Standard ECR",
-			registry: "123456789012.dkr.ecr.us-west-2.amazonaws.com",
-			isECR:    true,
-		},
-		{
-			name:     "ECR FIPS",
-			registry: "123456789012.dkr.ecr-fips.us-west-2.amazonaws.com",
-			isECR:    true,
-		},
-		{
-			name:     "ECR China",
-			registry: "123456789012.dkr.ecr.cn-northwest-1.amazonaws.com.cn",
-			isECR:    true,
-		},
-		{
-			name:     "ECR FIPS China",
-			registry: "123456789012.dkr.ecr-fips.cn-northwest-1.amazonaws.com.cn",
-			isECR:    true,
-		},
-		{
-			name:     "Non-ECR registry",
-			registry: "docker.io",
-			isECR:    false,
-		},
-		{
-			name:     "Invalid format",
-			registry: "invalid-registry.com",
-			isECR:    false,
-		},
-	}
+	t.Parallel()
+	tests := []struct {
+		name        string
+		registry    string
+		wantAcct    string
+		wantRegion  string
+		expectError bool
+	}{
+		{"Standard ECR", "123456789012.dkr.ecr.us-west-2.amazonaws.com", "123456789012", "us-west-2", false},
+		{"ECR FIPS", "123456789012.dkr.ecr-fips.us-west-2.amazonaws.com", "123456789012", "us-west-2", false},
+		{"ECR China", "123456789012.dkr.ecr.cn-northwest-1.amazonaws.com.cn", "123456789012", "cn-northwest-1", false},
+		{"ECR FIPS China", "123456789012.dkr.ecr-fips.cn-northwest-1.amazonaws.com.cn", "123456789012", "cn-northwest-1", false},
+		{"Non-ECR registry", "docker.io", "", "", true},
+		{"Invalid format", "invalid-registry.com", "", "", true},
+	}
@@
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			isECR := strings.Contains(tt.registry, "dkr.ecr") && strings.Contains(tt.registry, "amazonaws.com")
-			assert.Equal(t, tt.isECR, isECR)
-		})
-	}
+	for _, tt := range tests {
+		tt := tt
+		t.Run(tt.name, func(t *testing.T) {
+			t.Parallel()
+			acct, region, err := parseECRRegistry(tt.registry)
+			if tt.expectError {
+				assert.Error(t, err)
+				return
+			}
+			assert.NoError(t, err)
+			assert.Equal(t, tt.wantAcct, acct)
+			assert.Equal(t, tt.wantRegion, region)
+		})
+	}

Remove the now-unused strings import.

internal/exec/oci_aws.go (3)

58-61: Include registry context when no auth data is returned.

Improve diagnosability by wrapping with context.

Apply this diff:

-	if len(authTokenOutput.AuthorizationData) == 0 {
-		return nil, errNoECRAuthorizationData
-	}
+	if len(authTokenOutput.AuthorizationData) == 0 {
+		return nil, fmt.Errorf("%w for %s", errNoECRAuthorizationData, ecrClient.EndpointResolver.(*ecr.EndpointResolverV2).PartitionID())
+	}

Alternatively, if the client/partition is not easily available here, use the input registry string instead:

-		return nil, errNoECRAuthorizationData
+		return nil, fmt.Errorf("%w for %s", errNoECRAuthorizationData, "<registry>")

31-47: Compile the ECR regex once to avoid per-call recompile.

Minor perf/readability tweak.

Apply this diff:

 var (
@@
 	errFailedToLoadAWSConfig      = errors.New("failed to load AWS config")
 )
 
+// Precompiled: supports ecr and ecr-fips across partitions (incl. .cn).
+var ecrRegistryRe = regexp.MustCompile(`^(?P<acct>\d{12})\.dkr\.(?P<svc>ecr(?:-fips)?)\.(?P<region>[a-z0-9-]+)\.amazonaws\.com(?:\.cn)?$`)
+
 // parseECRRegistry parses ECR registry string and extracts account ID and region.
 func parseECRRegistry(registry string) (accountID, region string, err error) {
-	re := regexp.MustCompile(`^(?P<acct>\d{12})\.dkr\.(?P<svc>ecr(?:-fips)?)\.(?P<region>[a-z0-9-]+)\.amazonaws\.com(?:\.cn)?$`)
-	m := re.FindStringSubmatch(registry)
+	m := ecrRegistryRe.FindStringSubmatch(registry)
 	if m == nil {
 		return "", "", fmt.Errorf("%w: %s", errInvalidECRRegistryFormat, registry)
 	}
 
-	accountID = m[re.SubexpIndex("acct")]
-	region = m[re.SubexpIndex("region")]
+	accountID = m[ecrRegistryRe.SubexpIndex("acct")]
+	region = m[ecrRegistryRe.SubexpIndex("region")]

86-103: Optional: add public ECR (public.ecr.aws) path later.

If needed, wire ecr-public API and a separate regex.

internal/exec/oci_docker.go (1)

223-226: Harden helper name validation with an allowlist.

Denylisting special chars still permits odd cases. Prefer a tight allowlist like ^[A-Za-z0-9_-]+$ for credsStore.

-	// Validate credsStore to prevent command injection
-	if strings.ContainsAny(credsStore, ";&|`$(){}[]<>/\\") {
+	// Validate credsStore using an allowlist (letters, digits, underscore, hyphen).
+	if !regexp.MustCompile(`^[A-Za-z0-9_-]+$`).MatchString(credsStore) {
 		return nil, fmt.Errorf("%w: %s", errInvalidCredentialStoreName, credsStore)
 	}
internal/exec/oci_utils.go (4)

178-181: Support ATMOS_ variants for registry env creds.

Per our env guidelines, bind ATMOS_USERNAME/PASSWORD in addition to *.

-	bindEnv(v, usernameKey, fmt.Sprintf("%s_USERNAME", registryEnvName))
-	bindEnv(v, passwordKey, fmt.Sprintf("%s_PASSWORD", registryEnvName))
+	bindEnv(v, usernameKey,
+		fmt.Sprintf("%s_USERNAME", registryEnvName),
+		fmt.Sprintf("ATMOS_%s_USERNAME", registryEnvName),
+	)
+	bindEnv(v, passwordKey,
+		fmt.Sprintf("%s_PASSWORD", registryEnvName),
+		fmt.Sprintf("ATMOS_%s_PASSWORD", registryEnvName),
+	)

235-241: Punctuate list comments per guideline.

Ensure each bullet ends with a period.

 // It checks multiple sources in order of preference:
-// 1. GitHub Container Registry (ghcr.io) with GITHUB_TOKEN
-// 2. Docker credential helpers (from ~/.docker/config.json)
-// 3. Environment variables for specific registries
-// 4. AWS ECR authentication (if AWS credentials are available)
+// 1. GitHub Container Registry (ghcr.io) with GITHUB_TOKEN.
+// 2. Docker credential helpers (from ~/.docker/config.json).
+// 3. Environment variables for specific registries.
+// 4. AWS ECR authentication (if AWS credentials are available).

357-358: Polish doc comments (periods and naming).

  • End sentences with periods.
  • Start unexported func comment with its name for godoc clarity.
-// extractRawData attempts to extract raw data from the layer as a fallback
+// extractRawData attempts to extract raw data from the layer as a fallback.
@@
-// extractZipFile extracts a ZIP file from an io.Reader into the destination directory
+// extractZipFile extracts a ZIP file from an io.Reader into the destination directory.
@@
-// ParseOCIManifest reads and decodes an OCI manifest from a JSON file.
+// parseOCIManifest reads and decodes an OCI manifest from a JSON file.

Also applies to: 490-491, 560-564


563-565: Wrap manifest parse error with context.

Preserve error chain consistently.

-	if err := json.NewDecoder(manifestBytes).Decode(&manifest); err != nil {
-		return nil, err
-	}
+	if err := json.NewDecoder(manifestBytes).Decode(&manifest); err != nil {
+		return nil, fmt.Errorf("parse OCI manifest: %w", err)
+	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 9a9fec4 and 86bf1b4.

📒 Files selected for processing (15)
  • internal/exec/oci_auth_test.go (1 hunks)
  • internal/exec/oci_aws.go (1 hunks)
  • internal/exec/oci_aws_test.go (1 hunks)
  • internal/exec/oci_azure.go (1 hunks)
  • internal/exec/oci_azure_test.go (1 hunks)
  • internal/exec/oci_docker.go (1 hunks)
  • internal/exec/oci_docker_test.go (1 hunks)
  • internal/exec/oci_extraction_test.go (1 hunks)
  • internal/exec/oci_github.go (1 hunks)
  • internal/exec/oci_github_test.go (1 hunks)
  • internal/exec/oci_google.go (1 hunks)
  • internal/exec/oci_google_test.go (1 hunks)
  • internal/exec/oci_processing_test.go (1 hunks)
  • internal/exec/oci_utils.go (4 hunks)
  • internal/exec/oci_utils_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (9)
  • internal/exec/oci_github.go
  • internal/exec/oci_utils_test.go
  • internal/exec/oci_processing_test.go
  • internal/exec/oci_google_test.go
  • internal/exec/oci_docker_test.go
  • internal/exec/oci_azure.go
  • internal/exec/oci_extraction_test.go
  • internal/exec/oci_auth_test.go
  • internal/exec/oci_github_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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All comments must end with periods (complete sentences).
Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.
Target >80% coverage on new/changed lines and include comprehensive unit tests for new features.
Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.
Use logging only for system/debug info; structured logging without string interpolation; follow logging hierarchy per docs/logging.md.
Most text UI must go to stderr; only data/results to stdout for piping compatibility.
Prefer SDKs over shelling out to binaries; use filepath.Join, os.PathSeparator/ListSeparator; gate OS-specific logic with runtime.GOOS when unavoidable.
For non-standard execution paths, capture telemetry via pkg/telemetry (CaptureCmd or CaptureCmdString) without collecting user data.

Files:

  • internal/exec/oci_aws_test.go
  • internal/exec/oci_azure_test.go
  • internal/exec/oci_docker.go
  • internal/exec/oci_aws.go
  • internal/exec/oci_google.go
  • internal/exec/oci_utils.go
**/*_test.go

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

**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for testing multiple scenarios
Include integration tests for command flows
Test CLI end-to-end when possible
Use test fixtures for complex inputs
Consider using testify/mock for creating mock implementations

**/*_test.go: Use table-driven unit tests for pure functions.
Co-locate tests alongside implementation files (use *_test.go).

Files:

  • internal/exec/oci_aws_test.go
  • internal/exec/oci_azure_test.go
🧠 Learnings (13)
📚 Learning: 2025-09-02T14:44:18.360Z
Learnt from: CR
PR: cloudposse/atmos#0
File: AGENTS.md:0-0
Timestamp: 2025-09-02T14:44:18.360Z
Learning: Always run `go test ./...` after changes; if it fails due to network restrictions, note that in the PR description

Applied to files:

  • internal/exec/oci_azure_test.go
📚 Learning: 2025-09-02T19:59:52.093Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.093Z
Learning: Applies to pkg/utils/file_utils.go : Reuse file operation helpers from pkg/utils/file_utils.go instead of duplicating.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
PR: cloudposse/atmos#727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-31T07:09:31.983Z
Learnt from: Cerebrovinny
PR: cloudposse/atmos#737
File: internal/exec/vendor_utils.go:181-182
Timestamp: 2024-10-31T07:09:31.983Z
Learning: In `internal/exec/vendor_utils.go`, the variables `mergedSources` and `mergedImports` are declared and used later in the code. Do not suggest removing them as unused variables.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-09-02T19:59:52.093Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.093Z
Learning: Applies to **/*.go : Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use Viper for managing configuration, environment variables, and flags

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-27T16:59:26.187Z
Learnt from: osterman
PR: cloudposse/atmos#729
File: internal/exec/help.go:48-51
Timestamp: 2024-10-27T16:59:26.187Z
Learning: In the Atmos CLI help messages, when providing examples that include the version number, use the actual version variable (e.g., `version.Version`) instead of placeholders like `<version>`.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use snake_case for environment variables

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-02-20T18:33:16.567Z
Learnt from: samtholiya
PR: cloudposse/atmos#1077
File: pkg/downloader/custom_github_detector.go:47-68
Timestamp: 2025-02-20T18:33:16.567Z
Learning: Go doesn't have a built-in secure string type. For sensitive data like tokens, using environment variables (which are protected by the OS) is a common and secure practice.

Applied to files:

  • internal/exec/oci_utils.go
🧬 Code graph analysis (3)
internal/exec/oci_azure_test.go (1)
pkg/schema/schema.go (1)
  • AtmosConfiguration (25-60)
internal/exec/oci_docker.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Settings (846-850)
internal/exec/oci_utils.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Context (382-397)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (9)
internal/exec/oci_azure_test.go (4)

21-24: LGTM: E2E guard in place.

Skipping Azure networked tests by default is the right call.


47-52: LGTM: Sovereign cloud (.cn) covered.

Good addition for Azure China domains.


71-80: All asserted error substrings are defined in internal/exec/oci_azure.go. Tests remain stable.


25-67: No Germany sovereign-cloud test needed.
Azure uses the global *.azurecr.io in German regions; there is no separate azurecr.de suffix.

internal/exec/oci_google.go (1)

20-33: No action needed for missing Google credentials. getGCRAuth errors (including errNoGoogleCloudCredentialsFound) propagate through getRegistryAuth and are handled by the caller’s anonymous fallback.

internal/exec/oci_aws.go (1)

83-123: Solid ECR auth flow.

Good parsing, timeout, error wrapping, and Basic auth construction.

internal/exec/oci_docker.go (1)

75-95: Robust Docker config loader.

Good existence check, error wrapping, and JSON parsing. LGTM.

internal/exec/oci_utils.go (2)

147-158: GHCR path looks good.

Delegating to getGitHubAuth keeps concerns separated. Nice.


53-59: bindEnv helper aligns with project standards.

Good use of viper.BindEnv with fallback and structured debug.

Copy link
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: 3

♻️ Duplicate comments (1)
internal/exec/oci_docker.go (1)

54-73: Honor precedence: env > config for Docker config path.

Make env override config, then fall back to default.

- // Resolve Docker config path
- configDir := atmosConfig.Settings.OCI.DockerConfig
- if configDir == "" {
-   configDir = v.GetString("docker_config")
- }
+ // Resolve Docker config path (env has precedence).
+ configDir := v.GetString("docker_config")
+ if configDir == "" {
+   configDir = atmosConfig.Settings.OCI.DockerConfig
+ }
🧹 Nitpick comments (5)
internal/exec/oci_azure.go (3)

51-60: Harden ACR name extraction (case, :port, more suffixes).

Handle mixed case and optional port; consider azurecr.de if needed.

-func extractACRName(registry string) (string, error) {
-  // Expected formats: <acr-name>.azurecr.{io|cn|us}
-  for _, suf := range []string{".azurecr.io", ".azurecr.us", ".azurecr.cn"} {
-    if strings.HasSuffix(registry, suf) {
-      return strings.TrimSuffix(registry, suf), nil
-    }
-  }
-  return "", fmt.Errorf("%w: %s (expected <name>.azurecr.{io|us|cn})", errInvalidACRRegistryFormat, registry)
-}
+func extractACRName(registry string) (string, error) {
+  host := strings.ToLower(registry)
+  if i := strings.IndexByte(host, ':'); i > -1 {
+    host = host[:i] // strip :port if present.
+  }
+  for _, suf := range []string{".azurecr.io", ".azurecr.us", ".azurecr.cn"} { // add ".azurecr.de" if required.
+    if strings.HasSuffix(host, suf) {
+      name := strings.TrimSuffix(host, suf)
+      if name != "" {
+        return name, nil
+      }
+    }
+  }
+  return "", fmt.Errorf("%w: %s (expected <name>.azurecr.{io|us|cn})", errInvalidACRRegistryFormat, registry)
+}

211-216: Add timeouts to Azure token requests.

Avoid indefinite waits from Default/ClientSecret credential flows.

-ctx := context.Background()
+ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+defer cancel()

Also applies to: 241-245


24-49: Minor: end comments with periods.

Several comments lack trailing periods (e.g., Lines 24, 62, 65, 70, 75, 97, 116, 121, 130). Please tidy per guidelines.

Also applies to: 51-261

internal/exec/oci_docker.go (2)

152-177: Optional: add Docker Hub aliases to direct auth search.

Many configs store auth under https://index.docker.io/v1/ or registry-1.docker.io.

- registryVariants := []string{
+ registryVariants := []string{
   registry,
   "https://" + registry,
   "http://" + registry,
+  // Docker Hub common keys.
+  "https://index.docker.io/v1/",
+  "https://registry-1.docker.io",
 }

23-52: Minor: end comments with periods.

A few comments miss trailing periods (e.g., Lines 54, 76, 97, 113, 137, 152, 179, 214, 267). Please tidy.

Also applies to: 54-73, 75-283

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 86bf1b4 and 840056d.

📒 Files selected for processing (6)
  • internal/exec/oci_azure.go (1 hunks)
  • internal/exec/oci_docker.go (1 hunks)
  • internal/exec/oci_docker_test.go (1 hunks)
  • internal/exec/oci_github.go (1 hunks)
  • internal/exec/oci_github_test.go (1 hunks)
  • pkg/config/load.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/exec/oci_docker_test.go
  • internal/exec/oci_github.go
  • internal/exec/oci_github_test.go
  • pkg/config/load.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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All comments must end with periods (complete sentences).
Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.
Target >80% coverage on new/changed lines and include comprehensive unit tests for new features.
Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.
Use logging only for system/debug info; structured logging without string interpolation; follow logging hierarchy per docs/logging.md.
Most text UI must go to stderr; only data/results to stdout for piping compatibility.
Prefer SDKs over shelling out to binaries; use filepath.Join, os.PathSeparator/ListSeparator; gate OS-specific logic with runtime.GOOS when unavoidable.
For non-standard execution paths, capture telemetry via pkg/telemetry (CaptureCmd or CaptureCmdString) without collecting user data.

Files:

  • internal/exec/oci_azure.go
  • internal/exec/oci_docker.go
🧠 Learnings (7)
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Follow the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-09-02T19:59:52.093Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.093Z
Learning: Applies to **/*.go : Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.

Applied to files:

  • internal/exec/oci_docker.go
🧬 Code graph analysis (1)
internal/exec/oci_azure.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Settings (846-850)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary

Copy link
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

♻️ Duplicate comments (2)
internal/exec/oci_utils.go (2)

498-508: Fix ZIP size-limit check to avoid false positives.

Read up to maxZipSize+1 and reject only when exceeded.

-  limitedReader := io.LimitReader(reader, maxZipSize)
+  limitedReader := io.LimitReader(reader, int64(maxZipSize)+1)
   zipData, err := io.ReadAll(limitedReader)
   if err != nil {
     return fmt.Errorf("%w: %w", errFailedToReadZipData, err)
   }

-  // Check if we hit the size limit (indicates potential decompression bomb)
-  if len(zipData) == maxZipSize {
-    return fmt.Errorf("%w: %d bytes", errZipSizeExceeded, maxZipSize)
-  }
+  // Reject if ZIP exceeds configured max size.
+  if len(zipData) > maxZipSize {
+    return fmt.Errorf("%w: %d bytes", errZipSizeExceeded, maxZipSize)
+  }

129-136: Only fall back to anonymous on “no auth found”.

Current logic hides real auth errors (e.g., unreadable docker config). Gate anonymous fallback on a sentinel error.

-  auth, err := getRegistryAuth(registry, atmosConfig)
-  if err != nil {
-    log.Debug("No authentication found, using anonymous", logFieldRegistry, registry)
-    opts = append(opts, remote.WithAuth(authn.Anonymous))
-  } else {
+  auth, err := getRegistryAuth(registry, atmosConfig)
+  if err != nil {
+    if errors.Is(err, errNoAuthenticationFound) {
+      log.Debug("No authentication found, using anonymous.", logFieldRegistry, registry)
+      opts = append(opts, remote.WithAuth(authn.Anonymous))
+    } else {
+      log.Error("Registry auth error.", logFieldRegistry, registry, logFieldError, err)
+      return nil, fmt.Errorf("resolve registry auth: %w", err)
+    }
+  } else {
     opts = append(opts, remote.WithAuth(auth))
     log.Debug("Using authentication for registry", logFieldRegistry, registry)
   }
🧹 Nitpick comments (1)
internal/exec/oci_utils.go (1)

396-399: Avoid over-restrictive “..” filename check.

strings.Contains(fileName, "..") blocks legitimate names. You already enforce path containment in resolveZipFilePath; rely on that instead.

-  // Check for path traversal patterns
-  if strings.Contains(fileName, "..") {
-    return fmt.Errorf("%w: path traversal not allowed: %s", errIllegalZipFilePath, fileName)
-  }
+  // Path traversal is prevented by resolveZipFilePath; no generic ".." substring check here.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 840056d and dd5bdcb.

📒 Files selected for processing (6)
  • internal/exec/oci_aws.go (1 hunks)
  • internal/exec/oci_aws_test.go (1 hunks)
  • internal/exec/oci_azure_test.go (1 hunks)
  • internal/exec/oci_docker.go (1 hunks)
  • internal/exec/oci_google.go (1 hunks)
  • internal/exec/oci_utils.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • internal/exec/oci_azure_test.go
  • internal/exec/oci_aws_test.go
  • internal/exec/oci_google.go
  • internal/exec/oci_docker.go
  • internal/exec/oci_aws.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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All comments must end with periods (complete sentences).
Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.
Target >80% coverage on new/changed lines and include comprehensive unit tests for new features.
Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.
Use logging only for system/debug info; structured logging without string interpolation; follow logging hierarchy per docs/logging.md.
Most text UI must go to stderr; only data/results to stdout for piping compatibility.
Prefer SDKs over shelling out to binaries; use filepath.Join, os.PathSeparator/ListSeparator; gate OS-specific logic with runtime.GOOS when unavoidable.
For non-standard execution paths, capture telemetry via pkg/telemetry (CaptureCmd or CaptureCmdString) without collecting user data.

Files:

  • internal/exec/oci_utils.go
🧠 Learnings (12)
📚 Learning: 2025-09-02T19:59:52.093Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.093Z
Learning: Applies to pkg/utils/file_utils.go : Reuse file operation helpers from pkg/utils/file_utils.go instead of duplicating.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
PR: cloudposse/atmos#727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-31T07:09:31.983Z
Learnt from: Cerebrovinny
PR: cloudposse/atmos#737
File: internal/exec/vendor_utils.go:181-182
Timestamp: 2024-10-31T07:09:31.983Z
Learning: In `internal/exec/vendor_utils.go`, the variables `mergedSources` and `mergedImports` are declared and used later in the code. Do not suggest removing them as unused variables.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-09-02T19:59:52.093Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.093Z
Learning: Applies to **/*.go : Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use Viper for managing configuration, environment variables, and flags

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-27T16:59:26.187Z
Learnt from: osterman
PR: cloudposse/atmos#729
File: internal/exec/help.go:48-51
Timestamp: 2024-10-27T16:59:26.187Z
Learning: In the Atmos CLI help messages, when providing examples that include the version number, use the actual version variable (e.g., `version.Version`) instead of placeholders like `<version>`.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use snake_case for environment variables

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-02-20T18:33:16.567Z
Learnt from: samtholiya
PR: cloudposse/atmos#1077
File: pkg/downloader/custom_github_detector.go:47-68
Timestamp: 2025-02-20T18:33:16.567Z
Learning: Go doesn't have a built-in secure string type. For sensitive data like tokens, using environment variables (which are protected by the OS) is a common and secure practice.

Applied to files:

  • internal/exec/oci_utils.go
🧬 Code graph analysis (1)
internal/exec/oci_utils.go (1)
pkg/schema/schema.go (1)
  • AtmosConfiguration (25-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (1)
internal/exec/oci_utils.go (1)

53-58: Nice: Viper binding with ATMOS_ fallbacks.

bindEnv wrapper and registry-specific env handling with ATMOS_ alternatives align with project guidelines.

Also applies to: 171-200

autofix-ci bot and others added 2 commits September 5, 2025 13:48
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Copy link
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: 1

♻️ Duplicate comments (2)
internal/exec/oci_docker.go (2)

103-110: Verify logFieldRegistry constant is defined.

Previous review flagged it; ensure a package-level const exists (e.g., in oci_utils.go).

Also applies to: 143-149, 189-189


68-69: Fix multiple %w usages (vet/golangci-lint).

Use a single %w per fmt.Errorf.

-      return "", fmt.Errorf("%w: %w", errFailedToGetUserHomeDir, err)
+      return "", fmt.Errorf("%w: %v", errFailedToGetUserHomeDir, err)
-    return DockerConfig{}, fmt.Errorf("%w: %w", errFailedToReadDockerConfigFile, err)
+    return DockerConfig{}, fmt.Errorf("%w: %v", errFailedToReadDockerConfigFile, err)
-    return DockerConfig{}, fmt.Errorf("%w: %w", errFailedToParseDockerConfigJSON, err)
+    return DockerConfig{}, fmt.Errorf("%w: %v", errFailedToParseDockerConfigJSON, err)
-        return nil, fmt.Errorf("%w %s: %w", errFailedToDecodeAuthForRegistry, reg, err)
+        return nil, fmt.Errorf("%w %s: %v", errFailedToDecodeAuthForRegistry, reg, err)
-    return nil, fmt.Errorf("%w %s: %w", errCredentialHelperNotFound, helperCmd, err)
+    return nil, fmt.Errorf("%w %s: %v", errCredentialHelperNotFound, helperCmd, err)
-    return nil, fmt.Errorf("%w %s: %w", errFailedToGetCredentialsFromStore, credsStore, err)
+    return nil, fmt.Errorf("%w %s: %v", errFailedToGetCredentialsFromStore, credsStore, err)
-    return nil, fmt.Errorf("%w: %w", errFailedToParseCredentialStoreOutput, err)
+    return nil, fmt.Errorf("%w: %v", errFailedToParseCredentialStoreOutput, err)
-    return "", "", fmt.Errorf("%w: %w", errFailedToDecodeBase64AuthString, err)
+    return "", "", fmt.Errorf("%w: %v", errFailedToDecodeBase64AuthString, err)

Also applies to: 85-86, 91-92, 168-169, 235-236, 245-246, 255-256, 273-274

🧹 Nitpick comments (5)
internal/exec/oci_docker.go (5)

126-133: Also try http:// variant for credHelpers.

Some configs store helper keys with http://. Low-risk to include.

   // Try with https:// prefix
   httpsRegistry := "https://" + registry
   if helper, ok := credHelpers[httpsRegistry]; ok {
     if auth, err := tryCredentialHelper(registry, httpsRegistry, helper); err == nil {
       return auth, nil
     }
   }
+
+  // Try with http:// prefix.
+  httpRegistry := "http://" + registry
+  if helper, ok := credHelpers[httpRegistry]; ok {
+    if auth, err := tryCredentialHelper(registry, httpRegistry, helper); err == nil {
+      return auth, nil
+    }
+  }

137-150: Increase hit rate: query credsStore with https/http variants.

Global stores often index by full server URL. Try common variants before failing.

 func tryGlobalCredentialStore(registry, credsStore string) (authn.Authenticator, error) {
   if credsStore == "" {
     return nil, errNoGlobalCredentialStore
   }
 
-  if auth, err := getCredentialStoreAuth(registry, credsStore); err == nil {
-    log.Debug("Using global credential store authentication", logFieldRegistry, registry, "store", credsStore)
-    return auth, nil
-  } else {
-    log.Debug("Global credential store authentication failed", logFieldRegistry, registry, "store", credsStore, "error", err)
-    return nil, err
-  }
+  candidates := []string{registry, "https://" + registry, "http://" + registry}
+  var lastErr error
+  for _, serverURL := range candidates {
+    if auth, err := getCredentialStoreAuth(serverURL, credsStore); err == nil {
+      log.Debug("Using global credential store authentication", logFieldRegistry, serverURL, "store", credsStore)
+      return auth, nil
+    } else {
+      lastErr = err
+    }
+  }
+  log.Debug("Global credential store authentication failed", logFieldRegistry, registry, "store", credsStore, "error", lastErr)
+  return nil, lastErr
 }

232-246: Configurable helper timeout via env.

Let users tune slow helpers; default remains 5s. Honors precedence with Viper.

-  ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+  // Allow tuning via ATMOS_OCI_DOCKER_HELPER_TIMEOUT (e.g., "10s"), default 5s.
+  timeout := 5 * time.Second
+  v := viper.New()
+  bindEnv(v, "docker_helper_timeout", "ATMOS_OCI_DOCKER_HELPER_TIMEOUT")
+  if s := v.GetString("docker_helper_timeout"); s != "" {
+    if d, err := time.ParseDuration(s); err == nil && d > 0 && d <= 30*time.Second {
+      timeout = d
+    }
+  }
+  ctx, cancel := context.WithTimeout(context.Background(), timeout)
   defer cancel()
-  cmd := exec.CommandContext(ctx, helperCmd, "get")
+  cmd := exec.CommandContext(ctx, helperCmd, "get")

232-241: Improve testability by abstracting exec calls.

Small indirection lets tests stub helpers without spawning processes.

+// For testability.
+var (
+  lookPathFn      = exec.LookPath
+  commandContextFn = exec.CommandContext
+)
...
-  if _, err := exec.LookPath(helperCmd); err != nil {
+  if _, err := lookPathFn(helperCmd); err != nil {
     return nil, fmt.Errorf("%w %s: %v", errCredentialHelperNotFound, helperCmd, err)
   }
...
-  cmd := exec.CommandContext(ctx, helperCmd, "get")
+  cmd := commandContextFn(ctx, helperCmd, "get")

33-33: Nit: end comments with periods.

Keeps docs consistent with project rules.

- // Static errors for Docker authentication
+ // Static errors for Docker authentication.
- // Try exact registry match first
+ // Try exact registry match first.
- // Try with https:// prefix
+ // Try with https:// prefix.
- // For Docker Desktop on macOS, the credential store is typically "desktop"
- // We need to use the docker-credential-desktop helper to get credentials
+ // For Docker Desktop on macOS, the credential store is typically "desktop".
+ // We need to use the docker-credential-desktop helper to get credentials.

Also applies to: 119-119, 126-127, 229-231

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 addac53 and 556639c.

📒 Files selected for processing (1)
  • internal/exec/oci_docker.go (1 hunks)
🧰 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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All comments must end with periods (complete sentences).
Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.
Target >80% coverage on new/changed lines and include comprehensive unit tests for new features.
Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.
Use logging only for system/debug info; structured logging without string interpolation; follow logging hierarchy per docs/logging.md.
Most text UI must go to stderr; only data/results to stdout for piping compatibility.
Prefer SDKs over shelling out to binaries; use filepath.Join, os.PathSeparator/ListSeparator; gate OS-specific logic with runtime.GOOS when unavoidable.
For non-standard execution paths, capture telemetry via pkg/telemetry (CaptureCmd or CaptureCmdString) without collecting user data.

Files:

  • internal/exec/oci_docker.go
🧠 Learnings (11)
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Follow the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-09-02T19:59:52.093Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.093Z
Learning: Applies to **/*.go : Always bind environment variables with viper.BindEnv and ensure an ATMOS_ alternative exists for every env var.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-09-02T19:59:52.093Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-02T19:59:52.093Z
Learning: Applies to **/*.go : Always wrap errors with a static error first using fmt.Errorf and %w to preserve the error chain.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Wrap errors with context using `fmt.Errorf("context: %w", err)`

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Provide clear error messages to users

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use meaningful error messages

Applied to files:

  • internal/exec/oci_docker.go
🧬 Code graph analysis (1)
internal/exec/oci_docker.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Settings (846-850)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
internal/exec/oci_docker.go (3)

54-63: Confirm bindEnv availability.

This file relies on bindEnv. If not defined in package exec, replace with explicit Viper bindings.

-  v := viper.New()
-  bindEnv(v, "docker_config", "ATMOS_OCI_DOCKER_CONFIG", "DOCKER_CONFIG")
+  v := viper.New()
+  // If bindEnv is unavailable here, explicitly bind and read the preferred one.
+  // v.BindEnv("docker_config", "ATMOS_OCI_DOCKER_CONFIG") // preferred
+  // if v.GetString("docker_config") == "" { v.BindEnv("docker_config", "DOCKER_CONFIG") }

23-31: Struct shape and tags look good.

DockerConfig mirrors config.json (auths/credsStore/credHelpers) cleanly.


180-213: Auth source ordering is sensible with clear fallbacks.

Per-registry helpers → global store → direct auth is a solid priority. Final error is meaningful.

Signed-off-by: Chris Harden <[email protected]>
@cmharden cmharden requested a review from osterman September 5, 2025 14:48
Copy link
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

♻️ Duplicate comments (1)
internal/exec/oci_docker.go (1)

167-167: Fix multiple %w in fmt.Errorf (vet failure).

Use a single %w and format the inner error with %v.

-				return nil, fmt.Errorf("%w %s: %w", errFailedToDecodeAuthForRegistry, reg, err)
+				return nil, fmt.Errorf("%w %s: %v", errFailedToDecodeAuthForRegistry, reg, err)

-		return nil, fmt.Errorf("%w %s: %w", errCredentialHelperNotFound, helperCmd, err)
+		return nil, fmt.Errorf("%w %s: %v", errCredentialHelperNotFound, helperCmd, err)

-		return nil, fmt.Errorf("%w %s: %w", errFailedToGetCredentialsFromStore, credsStore, err)
+		return nil, fmt.Errorf("%w %s: %v", errFailedToGetCredentialsFromStore, credsStore, err)

Also applies to: 234-235, 244-245

🧹 Nitpick comments (7)
internal/exec/oci_aws.go (3)

96-105: Add missing periods to comments to satisfy godot.

A few comments in this block don’t end with periods.

-	// Create context with timeout to prevent hanging AWS API calls
+	// Create context with timeout to prevent hanging AWS API calls.

-	// Load AWS config for the target region
+	// Load AWS config for the target region.

85-88: Doc comments should end with periods.

Minor style nits per golangci-lint godot.

-// getECRAuth attempts to get AWS ECR authentication using AWS credentials
-// Supports SSO/role providers by not gating on environment variables
+// getECRAuth attempts to get AWS ECR authentication using AWS credentials.
+// Supports SSO/role providers by not gating on environment variables.

51-64: Optionally prefer matching ProxyEndpoint entry.

ECR can return multiple AuthorizationData entries; choosing the one whose ProxyEndpoint matches the target registry improves robustness. Low-risk refactor.

-func getECRAuthToken(ctx context.Context, ecrClient *ecr.Client, accountID string) (*types.AuthorizationData, error) {
+func getECRAuthToken(ctx context.Context, ecrClient *ecr.Client, accountID string, registry string) (*types.AuthorizationData, error) {
@@
-	if len(authTokenOutput.AuthorizationData) == 0 {
+	if len(authTokenOutput.AuthorizationData) == 0 {
 		return nil, fmt.Errorf("%w for account %s", errNoECRAuthorizationData, accountID)
 	}
-	return &authTokenOutput.AuthorizationData[0], nil
+	// Prefer the entry whose ProxyEndpoint matches the target registry.
+	for i := range authTokenOutput.AuthorizationData {
+		ad := &authTokenOutput.AuthorizationData[i]
+		if ad.ProxyEndpoint != nil && strings.Contains(*ad.ProxyEndpoint, registry) {
+			return ad, nil
+		}
+	}
+	return &authTokenOutput.AuthorizationData[0], nil

And update caller:

-	authData, err := getECRAuthToken(ctx, ecrClient, accountID)
+	authData, err := getECRAuthToken(ctx, ecrClient, accountID, registry)
internal/exec/oci_docker.go (1)

216-221: Precompile regex to avoid recompilation overhead.

Minor perf/readability tweak.

-	// Validate registry using an allowlist approach
-	// Registry may only include letters, digits, dots, colons, slashes, and hyphens
-	validRegistry := regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
-	if !validRegistry.MatchString(registry) {
+	// Validate registry using an allowlist approach.
+	if !validRegistryRe.MatchString(registry) {
 		return nil, fmt.Errorf("%w: %s", errInvalidRegistryName, registry)
 	}

Add once near other vars:

 var (
+	validRegistryRe = regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
 	// Static errors for Docker authentication
internal/exec/oci_utils.go (3)

63-68: Remove unused tempDir to avoid unnecessary I/O and imports.

tempDir is created and removed but never used; drop it and the uuid import.

-import (
+import (
 	"github.com/google/go-containerregistry/pkg/authn"
 	"github.com/google/go-containerregistry/pkg/name"
 	v1 "github.com/google/go-containerregistry/pkg/v1"
 	"github.com/google/go-containerregistry/pkg/v1/remote"
-	"github.com/google/uuid"
 	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
 )
@@
-	tempDir, err := os.MkdirTemp("", uuid.New().String())
-	if err != nil {
-		return fmt.Errorf("failed to create temp directory: %w", err)
-	}
-	defer removeTempDir(tempDir)
+	// No temp workspace required for extraction; write directly to destDir.

Also applies to: 21-21, 67-67


124-151: Optional: add a request timeout to remote.Get.

Prevents hangs on slow registries; matches the time-bounding used elsewhere.

-import (
+import (
+	"context"
 	"archive/zip"
@@
 func pullImage(ref name.Reference, atmosConfig *schema.AtmosConfiguration) (*remote.Descriptor, error) {
 	var opts []remote.Option
@@
-	descriptor, err := remote.Get(ref, opts...)
+	// Add a conservative timeout for registry interactions.
+	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+	defer cancel()
+	opts = append(opts, remote.WithContext(ctx))
+	descriptor, err := remote.Get(ref, opts...)

Also applies to: 3-3


41-51: Minor: ensure comment sentences end with periods.

One-line comments like “Additional supported artifact types” should end with periods per godot.

-	// Additional supported artifact types
+	// Additional supported artifact types.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 556639c and 431510b.

📒 Files selected for processing (5)
  • internal/exec/oci_aws.go (1 hunks)
  • internal/exec/oci_azure.go (1 hunks)
  • internal/exec/oci_docker.go (1 hunks)
  • internal/exec/oci_google.go (1 hunks)
  • internal/exec/oci_utils.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/exec/oci_azure.go
  • internal/exec/oci_google.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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All Go comments must be complete sentences ending with periods (enforced by golangci-lint godot).
Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.
Bind environment variables with viper.BindEnv() and provide an ATMOS_ alternative for every env var.
Distinguish UI output (stderr) from structured logging; never use logging for UI prompts/errors; data/results go to stdout for piping.
Use structured logging without message interpolation and respect logging level hierarchy; logging must not affect execution.
Most text UI must go to stderr (e.g., utils.PrintfMessageToTUI or fmt.Fprintf(os.Stderr,...)); only data/results go to stdout.
For non-standard execution paths, capture telemetry with telemetry.CaptureCmd(cmd, err) or telemetry.CaptureCmdString("name", err); never capture user data.

Files:

  • internal/exec/oci_aws.go
  • internal/exec/oci_docker.go
  • internal/exec/oci_utils.go
🧠 Learnings (19)
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
  • internal/exec/oci_utils.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Follow the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_docker.go
  • internal/exec/oci_utils.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_docker.go
  • internal/exec/oci_utils.go
📚 Learning: 2025-09-05T14:40:05.107Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T14:40:05.107Z
Learning: Applies to **/*.go : Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Wrap errors with context using `fmt.Errorf("context: %w", err)`

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Provide clear error messages to users

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use meaningful error messages

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
PR: cloudposse/atmos#727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-31T07:09:31.983Z
Learnt from: Cerebrovinny
PR: cloudposse/atmos#737
File: internal/exec/vendor_utils.go:181-182
Timestamp: 2024-10-31T07:09:31.983Z
Learning: In `internal/exec/vendor_utils.go`, the variables `mergedSources` and `mergedImports` are declared and used later in the code. Do not suggest removing them as unused variables.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-09-05T14:40:05.107Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T14:40:05.107Z
Learning: Applies to **/*.go : Bind environment variables with viper.BindEnv() and provide an ATMOS_ alternative for every env var.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use Viper for managing configuration, environment variables, and flags

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-27T16:59:26.187Z
Learnt from: osterman
PR: cloudposse/atmos#729
File: internal/exec/help.go:48-51
Timestamp: 2024-10-27T16:59:26.187Z
Learning: In the Atmos CLI help messages, when providing examples that include the version number, use the actual version variable (e.g., `version.Version`) instead of placeholders like `<version>`.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use snake_case for environment variables

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2025-02-20T18:33:16.567Z
Learnt from: samtholiya
PR: cloudposse/atmos#1077
File: pkg/downloader/custom_github_detector.go:47-68
Timestamp: 2025-02-20T18:33:16.567Z
Learning: Go doesn't have a built-in secure string type. For sensitive data like tokens, using environment variables (which are protected by the OS) is a common and secure practice.

Applied to files:

  • internal/exec/oci_utils.go
📚 Learning: 2024-10-28T01:51:30.811Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_utils.go
📚 Learning: 2025-04-04T02:03:23.676Z
Learnt from: aknysh
PR: cloudposse/atmos#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:

  • internal/exec/oci_utils.go
🧬 Code graph analysis (2)
internal/exec/oci_docker.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Settings (846-850)
internal/exec/oci_utils.go (1)
pkg/schema/schema.go (1)
  • AtmosConfiguration (25-60)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (4)
internal/exec/oci_aws.go (1)

31-49: Regex-based ECR parsing is solid.

Covers ecr/ecr-fips and .cn; clear error paths. Nice separation via parseECRRegistry.

internal/exec/oci_docker.go (1)

214-226: Allowlist validation and helper pre-checks look good.

Good defense-in-depth on registry/store inputs and helper existence.

internal/exec/oci_utils.go (2)

129-141: Anonymous fallback gated correctly.

Only falling back on errNoAuthenticationFound is the right behavior.


503-539: ZIP extraction hardening looks solid.

Zip Slip guards, size cap, and symlink skipping are well-implemented.

coderabbitai[bot]
coderabbitai bot previously approved these changes Sep 5, 2025
Copy link
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

♻️ Duplicate comments (2)
internal/exec/oci_docker.go (2)

105-109: Verify logFieldRegistry is defined.

This identifier isn’t declared in this file. Ensure it’s defined once for the package (e.g., in oci_utils.go).

#!/bin/bash
# Confirm logFieldRegistry exists in this package.
rg -nP --type=go -C1 'const\s+logFieldRegistry\b|var\s+logFieldRegistry\b'

Also applies to: 144-147


168-169: Fix multiple %w wrappers (vet/golangci-lint error).

Only one %w is allowed per fmt.Errorf call.

-				return nil, fmt.Errorf("%w %s: %w", errFailedToDecodeAuthForRegistry, reg, err)
+				return nil, fmt.Errorf("%w %s: %v", errFailedToDecodeAuthForRegistry, reg, err)

-		return nil, fmt.Errorf("%w %s: %w", errCredentialHelperNotFound, helperCmd, err)
+		return nil, fmt.Errorf("%w %s: %v", errCredentialHelperNotFound, helperCmd, err)

-		return nil, fmt.Errorf("%w %s: %w", errFailedToGetCredentialsFromStore, credsStore, err)
+		return nil, fmt.Errorf("%w %s: %v", errFailedToGetCredentialsFromStore, credsStore, err)

Also applies to: 235-236, 245-246

🧹 Nitpick comments (7)
internal/exec/oci_docker.go (7)

55-55: Fix godot: end comments with periods.

Lint will fail (godot). Apply tidy punctuation.

-// Create a Viper instance for environment variable access
+// Create a Viper instance for environment variable access.

-// Resolve Docker config path (env has precedence).
+// Resolve Docker config path (env has precedence).

-// Check if Docker config file exists
+// Check if Docker config file exists.

-// Read Docker config file
+// Read Docker config file.

-// Parse Docker config JSON
+// Parse Docker config JSON.

-// For Docker Desktop on macOS, the credential store is typically "desktop"
-// We need to use the docker-credential-desktop helper to get credentials
+// For Docker Desktop on macOS, the credential store is typically "desktop".
+// We need to use the docker-credential-desktop helper to get credentials.

Also applies to: 59-59, 76-76, 81-81, 87-87, 229-230


113-135: Try http:// helper key variant as well.

Some configs register helpers under http://server.

 	// Try with https:// prefix
 	httpsRegistry := "https://" + registry
 	if helper, ok := credHelpers[httpsRegistry]; ok {
 		if auth, err := tryCredentialHelper(registry, httpsRegistry, helper); err == nil {
 			return auth, nil
 		}
 	}
 
+	// Try with http:// prefix.
+	httpRegistry := "http://" + registry
+	if helper, ok := credHelpers[httpRegistry]; ok {
+		if auth, err := tryCredentialHelper(registry, httpRegistry, helper); err == nil {
+			return auth, nil
+		}
+	}

152-178: Support Docker Hub-style /v1/ auth keys.

Docker often stores auths under https:///v1/. Add those variants to improve hit rate.

 	// Try different registry formats
 	registryVariants := []string{
 		registry,
 		"https://" + registry,
 		"http://" + registry,
+		"https://" + registry + "/v1/",
+		"http://" + registry + "/v1/",
 	}

238-244: Send newline to credential helper stdin.

Helpers read until newline; sending one avoids edge cases with buffered reads.

-	cmd := exec.CommandContext(ctx, helperCmd, "get")
-	cmd.Stdin = strings.NewReader(registry)
+	cmd := exec.CommandContext(ctx, helperCmd, "get")
+	cmd.Stdin = strings.NewReader(registry + "\n")

238-241: Make helper timeout configurable.

Expose timeout via env (e.g., ATMOS_OCI_CRED_HELPER_TIMEOUT seconds) bound with viper; default to 5s.

Happy to draft the wiring if you want it in this PR.


232-246: Abstract exec for testability.

Consider indirection for exec.LookPath/CommandContext so tests can stub the helper without forking processes.

+// Allow tests to override exec for credential helpers.
+var lookPath = exec.LookPath
+var commandContext = exec.CommandContext
...
-	if _, err := exec.LookPath(helperCmd); err != nil {
+	if _, err := lookPath(helperCmd); err != nil {
 		return nil, fmt.Errorf("%w %s: %v", errCredentialHelperNotFound, helperCmd, err)
 	}
...
-	cmd := exec.CommandContext(ctx, helperCmd, "get")
+	cmd := commandContext(ctx, helperCmd, "get")

157-176: Add a quick test for Docker Hub key variants.

Please include tests that cover auth keys with /v1/ suffix to prevent regressions.

I can add table-driven cases to internal/exec/oci_docker_test.go for the added variants. Want me to push a patch?

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 431510b and 732940f.

📒 Files selected for processing (1)
  • internal/exec/oci_docker.go (1 hunks)
🧰 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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All Go comments must be complete sentences ending with periods (enforced by golangci-lint godot).
Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.
Bind environment variables with viper.BindEnv() and provide an ATMOS_ alternative for every env var.
Distinguish UI output (stderr) from structured logging; never use logging for UI prompts/errors; data/results go to stdout for piping.
Use structured logging without message interpolation and respect logging level hierarchy; logging must not affect execution.
Most text UI must go to stderr (e.g., utils.PrintfMessageToTUI or fmt.Fprintf(os.Stderr,...)); only data/results go to stdout.
For non-standard execution paths, capture telemetry with telemetry.CaptureCmd(cmd, err) or telemetry.CaptureCmdString("name", err); never capture user data.

Files:

  • internal/exec/oci_docker.go
🧠 Learnings (10)
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Follow the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-09-05T14:40:05.107Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T14:40:05.107Z
Learning: Applies to **/*.go : Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Wrap errors with context using `fmt.Errorf("context: %w", err)`

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Provide clear error messages to users

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use meaningful error messages

Applied to files:

  • internal/exec/oci_docker.go
🧬 Code graph analysis (1)
internal/exec/oci_docker.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Settings (846-850)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (3)
internal/exec/oci_docker.go (3)

180-213: Nice fallback ordering and logging.

Clear progression: per-registry helper → global store → direct auth, with structured debug logs. Looks good.


212-213: No action needed: errNoAuthenticationFound is defined in internal/exec/oci_utils.go.
Verified that errNoAuthenticationFound is declared at line 38 in oci_utils.go, so its use in oci_docker.go (lines 212–213) is valid.


53-72: No action needed: no CLI flag for docker_config
Verification shows there’s no viper.BindPFlag or CLI flag defined for docker_config in the codebase, so using a fresh Viper here doesn’t drop any flag bindings. Pass in the shared Viper only if you later introduce a CLI flag for docker_config.

coderabbitai[bot]
coderabbitai bot previously approved these changes Sep 5, 2025
Copy link
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

🧹 Nitpick comments (6)
internal/exec/oci_docker.go (6)

119-149: Nit: end comment sentences with periods (godot).

Add periods to satisfy golangci-lint godot.

-	// Try exact registry match first
+	// Try exact registry match first.
@@
-	// Try with https:// prefix
+	// Try with https:// prefix.

29-36: Optional: support identitytoken in Docker auths.

Docker config may provide identitytoken instead of auth. Handle it to support token-based logins.

 type DockerConfig struct {
-  Auths map[string]struct {
-    Auth string `json:"auth"`
-  } `json:"auths"`
+  Auths map[string]struct {
+    Auth          string `json:"auth"`
+    IdentityToken string `json:"identitytoken"`
+  } `json:"auths"`
   CredsStore  string            `json:"credsStore"`
   CredHelpers map[string]string `json:"credHelpers"`
 }

166-194: Optional: return IdentityToken-based authenticator when present.

Prefer identitytoken when available; fall back to basic auth decoding.

 func tryDirectAuth(registry string, auths map[string]struct {
   Auth string `json:"auth"`
 },
 ) (authn.Authenticator, error) {
@@
   for _, reg := range registryVariants {
-    if authData, exists := auths[reg]; exists && authData.Auth != "" {
-      username, password, err := decodeDockerAuth(authData.Auth)
-      if err != nil {
-        return nil, fmt.Errorf("%w %s: %v", errFailedToDecodeAuthForRegistry, reg, err)
-      }
-      return &authn.Basic{
-        Username: username,
-        Password: password,
-      }, nil
-    }
+    if authData, exists := auths[reg]; exists {
+      if authData.IdentityToken != "" {
+        return authn.FromConfig(authn.AuthConfig{IdentityToken: authData.IdentityToken}), nil
+      }
+      if authData.Auth != "" {
+        username, password, err := decodeDockerAuth(authData.Auth)
+        if err != nil {
+          return nil, fmt.Errorf("%w %s: %v", errFailedToDecodeAuthForRegistry, reg, err)
+        }
+        return &authn.Basic{Username: username, Password: password}, nil
+      }
+    }
   }

231-244: Nit: precompile regexps at package level.

Avoid recompiling on each call; minor hot-path polish.

+// Precompiled regex patterns for validation.
+var (
+  validRegistryRe = regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
+  validStoreRe    = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
+)
@@
-  validRegistry := regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
-  if !validRegistry.MatchString(registry) {
+  if !validRegistryRe.MatchString(registry) {
     return nil, fmt.Errorf("%w: %s", errInvalidRegistryName, registry)
   }
@@
-  if !regexp.MustCompile(`^[A-Za-z0-9_-]+$`).MatchString(credsStore) {
+  if !validStoreRe.MatchString(credsStore) {
     return nil, fmt.Errorf("%w: %s", errInvalidCredentialStoreName, credsStore)
   }

248-252: Nit: end comment with a period (godot).

-  // Try to execute the credential helper
+  // Try to execute the credential helper.

286-299: Nit: end comment sentences with periods (godot).

-  // Decode base64
+  // Decode base64.
@@
-  // Split username:password
+  // Split username:password.
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 732940f and b974604.

📒 Files selected for processing (1)
  • internal/exec/oci_docker.go (1 hunks)
🧰 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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All Go comments must be complete sentences ending with periods (enforced by golangci-lint godot).
Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.
Bind environment variables with viper.BindEnv() and provide an ATMOS_ alternative for every env var.
Distinguish UI output (stderr) from structured logging; never use logging for UI prompts/errors; data/results go to stdout for piping.
Use structured logging without message interpolation and respect logging level hierarchy; logging must not affect execution.
Most text UI must go to stderr (e.g., utils.PrintfMessageToTUI or fmt.Fprintf(os.Stderr,...)); only data/results go to stdout.
For non-standard execution paths, capture telemetry with telemetry.CaptureCmd(cmd, err) or telemetry.CaptureCmdString("name", err); never capture user data.

Files:

  • internal/exec/oci_docker.go
🧠 Learnings (10)
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Follow the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-09-05T14:40:05.107Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T14:40:05.107Z
Learning: Applies to **/*.go : Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Wrap errors with context using `fmt.Errorf("context: %w", err)`

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Provide clear error messages to users

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use meaningful error messages

Applied to files:

  • internal/exec/oci_docker.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (4)
internal/exec/oci_docker.go (4)

59-78: LGTM: Env-first Docker config resolution.

Good use of bindEnv and precedence (env > config > default).


80-101: LGTM: Robust config load with clear sentinels.

Clear error taxonomy and wrapping.


102-117: LGTM: Per-registry helper uses the exact server URL.

Passing helperKey avoids mismatches.


111-112: logFieldRegistry is defined once at internal/exec/oci_utils.go:46 and used consistently across the package.

cmharden and others added 2 commits September 5, 2025 14:34
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Chris Harden <[email protected]>
Copy link
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

♻️ Duplicate comments (3)
internal/exec/oci_docker.go (3)

102-117: Per‑registry helper: correct server URL usage.

Passing helperKey (exact config key) fixes the common mismatch bug. Nice.


151-177: Global credsStore variants: solid hardening.

Trying https/http and /v1/ variants addresses Docker Hub quirks. Looks good.


221-225: Don’t fail fast when Docker config is missing; allow auth fallback.

If config.json is absent, return a typed “no auth here” so upstream can try other sources.

   // Load Docker config
   dockerConfig, err := loadDockerConfig(configPath)
   if err != nil {
-    return nil, err
+    if errors.Is(err, errDockerConfigFileNotFound) {
+      log.Debug("Docker config file not found; skipping Docker auth", "path", configPath)
+      return nil, fmt.Errorf("%w %s", errNoAuthenticationFound, registry)
+    }
+    return nil, err
   }
🧹 Nitpick comments (4)
internal/exec/oci_docker.go (4)

244-257: Avoid recompiling regex each call.

Hoist regexes to package scope to reduce allocations on hot paths.

@@
 var (
   // Static errors for Docker authentication
@@
 )
 
+// Precompiled validators to avoid per-call allocations.
+var (
+  validRegistryRE = regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
+  credStoreRE     = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
+)
@@
-  // Validate registry using an allowlist approach
-  // Registry may only include letters, digits, dots, colons, slashes, and hyphens
-  validRegistry := regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
-  if !validRegistry.MatchString(registry) {
+  // Validate registry using an allowlist approach.
+  if !validRegistryRE.MatchString(registry) {
     return nil, fmt.Errorf("%w: %s", errInvalidRegistryName, registry)
   }
 
-  // Validate credsStore using an allowlist (letters, digits, underscore, hyphen).
-  if !regexp.MustCompile(`^[A-Za-z0-9_-]+$`).MatchString(credsStore) {
+  // Validate credsStore using an allowlist (letters, digits, underscore, hyphen).
+  if !credStoreRE.MatchString(credsStore) {
     return nil, fmt.Errorf("%w: %s", errInvalidCredentialStoreName, credsStore)
   }

29-36: Support username/password and identitytoken entries in auths.

Some Docker configs include username/password or identitytoken instead of “auth”. Add handling to broaden compatibility.

@@
 type DockerConfig struct {
-  Auths map[string]struct {
-    Auth string `json:"auth"`
-  } `json:"auths"`
+  Auths map[string]struct {
+    Auth          string `json:"auth"`
+    Username      string `json:"username"`
+    Password      string `json:"password"`
+    IdentityToken string `json:"identitytoken"`
+  } `json:"auths"`
@@
 func tryDirectAuth(registry string, auths map[string]struct {
-  Auth string `json:"auth"`
+  Auth          string `json:"auth"`
+  Username      string `json:"username"`
+  Password      string `json:"password"`
+  IdentityToken string `json:"identitytoken"`
 },
 ) (authn.Authenticator, error) {
@@
   for _, reg := range registryVariants {
     if authData, exists := auths[reg]; exists && authData.Auth != "" {
       username, password, err := decodeDockerAuth(authData.Auth)
       if err != nil {
         return nil, fmt.Errorf("%w %s: %v", errFailedToDecodeAuthForRegistry, reg, err)
       }
-      return &authn.Basic{
-        Username: username,
-        Password: password,
-      }, nil
+      return &authn.Basic{Username: username, Password: password}, nil
+    }
+    if exists && auths[reg].Username != "" && auths[reg].Password != "" {
+      return &authn.Basic{Username: auths[reg].Username, Password: auths[reg].Password}, nil
+    }
+    if exists && auths[reg].IdentityToken != "" {
+      return authn.FromConfig(authn.AuthConfig{IdentityToken: auths[reg].IdentityToken}), nil
     }
   }

Also applies to: 179-207


125-146: Fix godot: end comments with periods.

A few comments are missing trailing periods; golangci-lint will flag them.

- // Try exact registry match first
+ // Try exact registry match first.
@@
- // Try with https:// prefix
+ // Try with https:// prefix.
@@
- // Try with http:// prefix.
+ // Try with http:// prefix.
@@
- // Try different registry formats
+ // Try different registry formats.
@@
- // Try to execute the credential helper
+ // Try to execute the credential helper.
@@
- // Parse the JSON output from the credential helper
+ // Parse the JSON output from the credential helper.
@@
- // Split username:password
+ // Split username:password.

Also applies to: 185-191, 261-269, 277-283, 305-311


298-303: Trim whitespace before base64 decode.

Defensive trim avoids failures from accidental spaces/newlines.

- decoded, err := base64.StdEncoding.DecodeString(authString)
+ decoded, err := base64.StdEncoding.DecodeString(strings.TrimSpace(authString))
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 b974604 and 5aaa182.

📒 Files selected for processing (1)
  • internal/exec/oci_docker.go (1 hunks)
🧰 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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All Go comments must be complete sentences ending with periods (enforced by golangci-lint godot).
Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.
Bind environment variables with viper.BindEnv() and provide an ATMOS_ alternative for every env var.
Distinguish UI output (stderr) from structured logging; never use logging for UI prompts/errors; data/results go to stdout for piping.
Use structured logging without message interpolation and respect logging level hierarchy; logging must not affect execution.
Most text UI must go to stderr (e.g., utils.PrintfMessageToTUI or fmt.Fprintf(os.Stderr,...)); only data/results go to stdout.
For non-standard execution paths, capture telemetry with telemetry.CaptureCmd(cmd, err) or telemetry.CaptureCmdString("name", err); never capture user data.

Files:

  • internal/exec/oci_docker.go
🧠 Learnings (10)
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Follow the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-09-05T14:40:05.107Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T14:40:05.107Z
Learning: Applies to **/*.go : Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Wrap errors with context using `fmt.Errorf("context: %w", err)`

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Provide clear error messages to users

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use meaningful error messages

Applied to files:

  • internal/exec/oci_docker.go
🧬 Code graph analysis (1)
internal/exec/oci_docker.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Settings (846-850)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (2)
internal/exec/oci_docker.go (2)

59-78: Env precedence honored.

Good use of viper + bindEnv and fallback chain (env → config → default). No action needed.


109-116: The scripts will search for the missing constant and verify the helper’s error returns. Once we have the output, I’ll update the comment.

Copy link
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

♻️ Duplicate comments (1)
internal/exec/oci_docker.go (1)

111-111: Verify logFieldRegistry is defined package-wide.

This was flagged earlier; ensure it exists to avoid an undefined identifier.

🧹 Nitpick comments (6)
internal/exec/oci_docker.go (6)

39-39: Fix godot: end comment with a period.

This avoids golangci-lint failures.

-	// Static errors for Docker authentication
+	// Static errors for Docker authentication.

80-101: Include config path in read/parse error context.

Improves debuggability without leaking secrets.

-	configData, err := os.ReadFile(configPath)
+	configData, err := os.ReadFile(configPath)
 	if err != nil {
-		return DockerConfig{}, fmt.Errorf("%w: %v", errFailedToReadDockerConfigFile, err)
+		return DockerConfig{}, fmt.Errorf("%w %s: %v", errFailedToReadDockerConfigFile, configPath, err)
 	}
@@
-	if err := json.Unmarshal(configData, &dockerConfig); err != nil {
-		return DockerConfig{}, fmt.Errorf("%w: %v", errFailedToParseDockerConfigJSON, err)
+	if err := json.Unmarshal(configData, &dockerConfig); err != nil {
+		return DockerConfig{}, fmt.Errorf("%w %s: %v", errFailedToParseDockerConfigJSON, configPath, err)
 	}

102-117: Remove unused parameter from tryCredentialHelper.

registry isn’t used; unparam may flag this. Simplify signature and call sites.

-func tryCredentialHelper(registry, helperKey, helper string) (authn.Authenticator, error) {
+func tryCredentialHelper(helperKey, helper string) (authn.Authenticator, error) {
@@
-	if helper, ok := credHelpers[registry]; ok {
-		if auth, err := tryCredentialHelper(registry, registry, helper); err == nil {
+	if helper, ok := credHelpers[registry]; ok {
+		if auth, err := tryCredentialHelper(registry, helper); err == nil {
 			return auth, nil
 		}
 	}
@@
-	if helper, ok := credHelpers[httpsRegistry]; ok {
-		if auth, err := tryCredentialHelper(registry, httpsRegistry, helper); err == nil {
+	if helper, ok := credHelpers[httpsRegistry]; ok {
+		if auth, err := tryCredentialHelper(httpsRegistry, helper); err == nil {
 			return auth, nil
 		}
 	}
@@
-	if helper, ok := credHelpers[httpRegistry]; ok {
-		if auth, err := tryCredentialHelper(registry, httpRegistry, helper); err == nil {
+	if helper, ok := credHelpers[httpRegistry]; ok {
+		if auth, err := tryCredentialHelper(httpRegistry, helper); err == nil {
 			return auth, nil
 		}
 	}

Also applies to: 125-147


209-212: Fix doc comment punctuation.

Comply with godot; make it a complete sentence.

-// getDockerAuth attempts to get Docker authentication for a registry
-// Supports DOCKER_CONFIG environment variable, global credential stores (credsStore),
+// getDockerAuth attempts to get Docker authentication for a registry.
+// It supports DOCKER_CONFIG environment variable, global credential stores (credsStore),
 // and per-registry credential helpers (credHelpers).

248-260: Nit: precompile regex and tighten comment punctuation.

Avoid recompiling on each call and satisfy godot.

-	// Validate registry using an allowlist approach
-	// Registry may only include letters, digits, dots, colons, slashes, and hyphens
-	validRegistry := regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
+	// Validate registry using an allowlist approach.
+	// Registry may only include letters, digits, dots, colons, slashes, and hyphens.
+	validRegistry := validRegistryRE

Additional change outside this hunk:

+// Precompiled validation regexes.
+var (
+	validRegistryRE     = regexp.MustCompile(`^[A-Za-z0-9./:-]+$`)
+	validCredsStoreName = regexp.MustCompile(`^[A-Za-z0-9_-]+$`)
+)
-	if !regexp.MustCompile(`^[A-Za-z0-9_-]+$`).MatchString(credsStore) {
+	if !validCredsStoreName.MatchString(credsStore) {
 		return nil, fmt.Errorf("%w: %s", errInvalidCredentialStoreName, credsStore)
 	}

276-279: Capture helper stderr for better diagnostics (kept at debug level).

CombinedOutput preserves stderr; keep error wrapping unchanged, but log stderr when present.

-	output, err := cmd.Output()
+	output, err := cmd.CombinedOutput()
 	if err != nil {
+		if len(output) > 0 {
+			log.Debug("Credential helper stderr", "helper", helperCmd, "stderr", strings.TrimSpace(string(output)))
+		}
 		return nil, fmt.Errorf("%w %s: %v", errFailedToGetCredentialsFromStore, credsStore, err)
 	}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • 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 5aaa182 and e42b659.

📒 Files selected for processing (1)
  • internal/exec/oci_docker.go (1 hunks)
🧰 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
Use interfaces for external dependencies to facilitate mocking
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)
Consider using a custom error type for domain-specific errors
Follow standard Go coding style
Use gofmt and goimports to format code
Prefer short, descriptive variable names
Use snake_case for environment variables
Document all exported functions, types, and methods
Document complex logic with inline comments
Follow Go's documentation conventions
Use Viper for configuration management
Support configuration via files, environment variables, and flags
Follow the precedence order: flags > environment variables > config file > defaults

Format Go code with gofumpt -w . and goimports -w .

**/*.go: All Go comments must be complete sentences ending with periods (enforced by golangci-lint godot).
Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.
Bind environment variables with viper.BindEnv() and provide an ATMOS_ alternative for every env var.
Distinguish UI output (stderr) from structured logging; never use logging for UI prompts/errors; data/results go to stdout for piping.
Use structured logging without message interpolation and respect logging level hierarchy; logging must not affect execution.
Most text UI must go to stderr (e.g., utils.PrintfMessageToTUI or fmt.Fprintf(os.Stderr,...)); only data/results go to stdout.
For non-standard execution paths, capture telemetry with telemetry.CaptureCmd(cmd, err) or telemetry.CaptureCmdString("name", err); never capture user data.

Files:

  • internal/exec/oci_docker.go
🧠 Learnings (10)
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
PR: cloudposse/atmos#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:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Follow the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
PR: cloudposse/atmos#1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
PR: cloudposse/atmos#808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
PR: cloudposse/atmos#1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-09-05T14:40:05.107Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-05T14:40:05.107Z
Learning: Applies to **/*.go : Always wrap errors with a static error first using fmt.Errorf("%w: details", errUtils.ErrStaticError, ...); never return dynamic errors directly.

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Wrap errors with context using `fmt.Errorf("context: %w", err)`

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Provide clear error messages to users

Applied to files:

  • internal/exec/oci_docker.go
📚 Learning: 2025-07-01T02:22:25.901Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-07-01T02:22:25.901Z
Learning: Applies to **/*.go : Use meaningful error messages

Applied to files:

  • internal/exec/oci_docker.go
🧬 Code graph analysis (1)
internal/exec/oci_docker.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (25-60)
  • Settings (846-850)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (4)
internal/exec/oci_docker.go (4)

23-27: LGTM: testability hooks for exec are solid.

Overridable lookPath/commandContext enable clean mocking in tests.


29-36: LGTM: DockerConfig schema mirrors config.json.

Fields/JSON tags look correct for auths, credsStore, and credHelpers.


301-316: LGTM: auth decoding is correct.

Covers base64 decode and username:password split defensively.


59-78: resolveDockerConfigPath verification complete

  • bindEnv defined in internal/exec/oci_utils.go at lines 55–57
  • Settings.OCI.DockerConfig present in pkg/schema/schema.go at line 279
  • getRegistryAuth in internal/exec/oci_utils.go treats errNoAuthenticationFound as a soft-fail (logs Debug then continues)
  • No Cobra-registered CLI flag for Docker config path detected

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
no-release Do not create a new release (wait for additional code changes)
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Enhancement] Vendor pull from terraform private registries
3 participants