Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
105 commits
Select commit Hold shift + click to select a range
0a1daf1
feat: Add path-based component resolution for terraform, helmfile, pa…
Nov 9, 2025
a0edfdb
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 9, 2025
882fe6e
fix: Resolve MDX compilation errors in documentation
Nov 9, 2025
56bd044
fix: Return actual stack key when component aliases are used
Nov 9, 2025
d235ae3
fix: Only treat arguments as paths if they explicitly look like paths
Nov 9, 2025
26c0bff
refactor: Create pkg/component package with IsExplicitComponentPath h…
Nov 9, 2025
91b945b
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 9, 2025
9c72a2f
refactor: Break circular dependencies with dependency injection
Nov 9, 2025
a2f2f46
fix: Restore pkg/stack wrappers for Spacelift integration
Nov 9, 2025
e3a776d
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 9, 2025
9dd96ec
fix: Add Windows-style relative path detection to IsExplicitComponent…
Nov 9, 2025
3febdbc
docs: Fix broken links in path-based component resolution blog post
Nov 9, 2025
2aad133
fix: resolve ATMOS_BASE_PATH relative to CLI config directory
Nov 9, 2025
b31d90e
fix: remove redundant ATMOS_BASE_PATH from tests
Nov 9, 2025
f85234b
feat: enhance component path resolution with ambiguous path detection
Nov 9, 2025
7ef0e85
feat: add path resolution fallback for component names
Nov 9, 2025
54eb40c
test: ignore provisioned_by_user in describe component snapshots
Nov 9, 2025
00b2300
fix: use direct path extraction for describe component without stack …
Nov 9, 2025
fd098f7
fix: Make tests environment-agnostic and fix validate component path …
Nov 9, 2025
29d56b1
fix: resolve base path correctly for ENV vars and add BasePathAbsolut…
Nov 10, 2025
dfc90c5
Merge branch 'main' into component-path-resolution
osterman Nov 10, 2025
7511dca
Add sanitization for environment-specific fields in test snapshots
Nov 11, 2025
42b13bd
Merge remote-tracking branch 'origin/main' into component-path-resolu…
Nov 11, 2025
8b12ec4
Use sanitize field instead of diff for environment-specific values
Nov 11, 2025
40dcd99
feat: improve error handling for component path resolution with hints…
Nov 11, 2025
63c5f4b
test: regenerate snapshots for improved error formatting and config o…
Nov 11, 2025
c9e0419
Fix test cases for new error handler formatting
Nov 12, 2025
10f1e60
test: fix test logging and regenerate snapshots for improved error fo…
Nov 12, 2025
86ff07b
fix: Resolve test failures and improve validation test architecture
Nov 12, 2025
cc77d6b
fix: Support forward slash in path completion on Windows
Nov 13, 2025
73a67e7
refactor: break down validateComponentInStack into testable functions
Nov 14, 2025
1528cd5
fix: capitalize comment for loadStackConfig to satisfy godot linter
Nov 14, 2025
49c64a3
refactor: eliminate deep exit in getConfigAndStacksInfo for better te…
Nov 14, 2025
9c31959
refactor: extract validate component logic into testable functions
Nov 14, 2025
29973e9
fix: use proper error chaining instead of errors.Join for single errors
Nov 15, 2025
9a5ff54
fix: address linting errors - forbidigo, godot, ineffassign, lintroller
Nov 15, 2025
ba3323a
fix: replace dynamic errors with static error sentinels (err113)
Nov 15, 2025
8ea6437
fix: address remaining linting errors - gocritic, dupl, revive
Nov 15, 2025
8091fc6
fix: reduce cyclomatic complexity in tryExtractComponentType (revive)
Nov 15, 2025
7cef7f8
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 15, 2025
ae26ae8
fix: preserve detailed path error messages when component resolution …
Nov 15, 2025
7762b7d
fix: correct ATMOS_BASE_PATH in component path resolution tests
Nov 15, 2025
9ab10c3
fix: reduce path deduplication minimum length from 3 to 2
Nov 16, 2025
6574203
feat: add CleanDuplicatedPath function and apply to path resolution
Nov 16, 2025
2b3c45a
feat: prevent component path resolution in stacks/workflows directories
Nov 16, 2025
2f9d1cf
refactor(tests): reduce verbose output by using deferred logging
Nov 17, 2025
0ddb486
Merge branch 'main' into component-path-resolution
Nov 17, 2025
b0916c3
fix(tests): use correct error sentinel in path resolution test
Nov 17, 2025
93e85dc
refactor: fix linting issues on component-path-resolution branch
Nov 17, 2025
42c4919
fix(tests): use correct error sentinel in path resolution test
Nov 17, 2025
9a153a9
fix: format error messages to avoid environment-specific wrapping
Nov 17, 2025
e95f1e1
fix: remove leading spaces from error message newlines
Nov 17, 2025
00b0041
fix: use markdown hard breaks for error message paths
Nov 17, 2025
fe89ff1
fix(tests): update external path regex to handle nested directories
osterman Nov 18, 2025
17773a4
fix: resolve merge conflicts and address CodeRabbit review comments
osterman Nov 18, 2025
d0d31a7
fix(tests): handle Glamour word-wrapped paths in snapshot sanitization
osterman Nov 18, 2025
d53fa30
fix(tests): normalize hint message path formatting in snapshots
osterman Nov 18, 2025
28ca630
fix(tests): normalize path separators in cleanDuplicatedPath tests
osterman Nov 18, 2025
06959ed
fix(glob): prevent Windows path duplication in GetGlobMatches
osterman Nov 18, 2025
5d69522
fix(path): add Windows volume duplication cleanup in path utilities
osterman Nov 18, 2025
ac6c5c4
feat: add interactive component selection for ambiguous paths
osterman Nov 20, 2025
debd256
fix(tests): fix Windows path tests and hint message word wrapping
osterman Nov 20, 2025
dfaf9b5
fix(cmd): refactor describe component to avoid deep exits in tests
osterman Nov 20, 2025
cb317a1
chore: remove ERROR_CHAIN_FIX.md scratch file from repository
osterman Nov 20, 2025
5a8e6fd
fix(cmd): attach exit code 1 to config validation errors
osterman Nov 20, 2025
83fc21b
refactor(cmd): use error builder pattern for config validation
osterman Nov 20, 2025
1a0da6b
fix(cmd): correct semantic usage of error builder hints vs explanations
osterman Nov 20, 2025
5eb08fc
fix(tests): restore exit code handling in terraform tests
osterman Nov 20, 2025
d5a8aca
fix(tests): normalize line endings before sanitization for Windows CI
osterman Nov 20, 2025
146201e
Merge branch 'main' into component-path-resolution
osterman Nov 20, 2025
7445857
fix(tests): handle Windows paths in hint path joining regex
osterman Nov 21, 2025
2c19b25
Revert "fix(tests): handle Windows paths in hint path joining regex"
osterman Nov 21, 2025
d5ad2a8
fix(tests): move hint path joining to run after path sanitization
osterman Nov 21, 2025
b484023
test(component): add unit tests for resolver package
osterman Nov 21, 2025
6d3f7f7
test(cmd): add unit tests for cmd_utils.go functions
osterman Nov 21, 2025
c333299
test: add comprehensive unit tests for coverage improvement
osterman Nov 21, 2025
cc47645
test: fix non-deterministic and platform-specific test failures
osterman Nov 23, 2025
4ebf648
Merge branch 'main' into component-path-resolution
osterman Nov 23, 2025
9d151a7
Restore component registry and mock provider files
osterman Nov 23, 2025
6fe6e33
Merge branch 'main' into component-path-resolution
osterman Nov 24, 2025
9bf8aa9
test: increase coverage for component path resolution changes
osterman Nov 25, 2025
3bef86f
fix: update golangci-lint to v2.6.1 to resolve custom build failures
osterman Nov 26, 2025
35fa390
test: fix Windows test failure for absolute path resolution
osterman Nov 26, 2025
1df54de
Revert "fix: update golangci-lint to v2.6.1 to resolve custom build f…
osterman Nov 27, 2025
d403cd0
Merge branch 'main' into component-path-resolution
osterman Nov 27, 2025
215ef2d
fix: update comment punctuation and blog post date
osterman Nov 28, 2025
b695d08
docs: improve blog intro to mention all path formats
osterman Nov 28, 2025
e610f81
docs: reframe blog intro with user-centric benefit
osterman Nov 28, 2025
669ae37
docs: add intro sentence to Supported Path Formats section
osterman Nov 28, 2025
bf28fff
Merge branch 'main' into component-path-resolution
osterman Nov 29, 2025
388e013
revert: restore golangci-lint v2.5.0 in pre-commit workflow
osterman Nov 29, 2025
8c03f0f
Merge branch 'main' into component-path-resolution
aknysh Dec 3, 2025
653182c
add tests and docs
aknysh Dec 3, 2025
e15d4a7
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 3, 2025
d5596ec
add tests and docs
aknysh Dec 3, 2025
05c37da
address comments, add tests
aknysh Dec 3, 2025
9425b35
address comments, add tests
aknysh Dec 3, 2025
2751ac0
Merge branch 'main' into component-path-resolution
aknysh Dec 3, 2025
ffb01ff
address comments, add tests
aknysh Dec 3, 2025
5dd203d
address comments, add tests
aknysh Dec 3, 2025
232d2b0
Merge branch 'main' into component-path-resolution
aknysh Dec 4, 2025
1988959
Merge branch 'main' into component-path-resolution
osterman Dec 6, 2025
d2b3613
Merge branch 'main' into component-path-resolution
aknysh Dec 6, 2025
a592dd3
fixes and new tests
aknysh Dec 7, 2025
a0bb973
address comments
aknysh Dec 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 102 additions & 1 deletion cmd/cmd_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,38 @@ func getConfigAndStacksInfo(commandName string, cmd *cobra.Command, args []strin

info, err := e.ProcessCommandLineArgs(commandName, cmd, finalArgs, argsAfterDoubleDash)
errUtils.CheckErrorPrintAndExit(err, "", "")

// Resolve path-based component arguments to component names
if info.NeedsPathResolution && info.ComponentFromArg != "" {
atmosConfig, err := cfg.InitCliConfig(info, true)
if err != nil {
errUtils.CheckErrorPrintAndExit(fmt.Errorf("%w: %w", errUtils.ErrPathResolutionFailed, err), "", "")
}

resolvedComponent, err := e.ResolveComponentFromPath(
&atmosConfig,
info.ComponentFromArg,
info.Stack,
commandName, // Component type is the command name (terraform, helmfile, packer)
)
if err != nil {
errUtils.CheckErrorPrintAndExit(
fmt.Errorf("%w: %w", errUtils.ErrPathResolutionFailed, err),
"",
fmt.Sprintf("Hint: Make sure the path is within your component directories and the component exists in stack '%s'", info.Stack),
)
}

log.Debug("Resolved component from path",
"original_path", info.ComponentFromArg,
"resolved_component", resolvedComponent,
"stack", info.Stack,
)

info.ComponentFromArg = resolvedComponent
info.NeedsPathResolution = false // Mark as resolved
}

return info
}

Expand Down Expand Up @@ -779,7 +811,49 @@ func showUsageExample(cmd *cobra.Command, details string) {
func stackFlagCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
// If a component was provided as the first argument, filter stacks by that component.
if len(args) > 0 && args[0] != "" {
output, err := listStacksForComponent(args[0])
component := args[0]

// Check if argument is a path that needs resolution
// Paths are: ".", or contain path separators
if component == "." || strings.Contains(component, string(filepath.Separator)) {
// Attempt to resolve path to component name for stack filtering
// Use silent error handling - if resolution fails, just list all stacks (graceful degradation)
configAndStacksInfo := schema.ConfigAndStacksInfo{}
atmosConfig, err := cfg.InitCliConfig(configAndStacksInfo, true)
if err == nil {
// Determine component type from command
// Walk up command chain to find root command (terraform, helmfile, packer)
componentType := determineComponentTypeFromCommand(cmd)

// Try to resolve the path (without stack context for completion)
resolvedComponent, err := e.ResolveComponentFromPath(
&atmosConfig,
component,
"", // No stack context yet - we're completing the stack flag
componentType,
)
if err == nil {
component = resolvedComponent
log.Trace("Resolved path for stack completion",
"original", args[0],
"resolved", component,
)
} else {
// If resolution fails, fall through to list all stacks (graceful degradation)
log.Trace("Could not resolve path for stack completion, listing all stacks",
"path", component,
"error", err,
)
output, err := listStacks(cmd)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
return output, cobra.ShellCompDirectiveNoFileComp
}
}
}

output, err := listStacksForComponent(component)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
}
Expand All @@ -794,6 +868,25 @@ func stackFlagCompletion(cmd *cobra.Command, args []string, toComplete string) (
return output, cobra.ShellCompDirectiveNoFileComp
}

// determineComponentTypeFromCommand walks up the command chain to find the component type.
func determineComponentTypeFromCommand(cmd *cobra.Command) string {
// Walk up to find the root component command (terraform, helmfile, packer).
current := cmd
for current != nil {
switch current.Name() {
case "terraform":
return "terraform"
case "helmfile":
return "helmfile"
case "packer":
return "packer"
}
current = current.Parent()
}
// Default to terraform if we can't determine
return "terraform"
}

// listStacksForComponent returns stacks that contain the specified component.
// It initializes the CLI configuration, describes all stacks, and filters them
// to include only those defining the given component.
Expand Down Expand Up @@ -877,6 +970,14 @@ func identityArgCompletion(cmd *cobra.Command, args []string, toComplete string)

func ComponentsArgCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) == 0 {
// Check if user is typing a path
// Enable directory completion for paths
if toComplete == "." || strings.Contains(toComplete, string(filepath.Separator)) {
log.Trace("Enabling directory completion for path input", "toComplete", toComplete)
return nil, cobra.ShellCompDirectiveFilterDirs
}

// Otherwise, suggest component names from stack configurations
output, err := listComponents(cmd)
if err != nil {
return nil, cobra.ShellCompDirectiveNoFileComp
Expand Down
25 changes: 25 additions & 0 deletions cmd/describe_component.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package cmd
import (
"errors"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"

Expand Down Expand Up @@ -71,6 +73,9 @@ var describeComponentCmd = &cobra.Command{

component := args[0]

// Check if argument is a path that needs resolution
needsPathResolution := component == "." || strings.Contains(component, string(filepath.Separator))

// Load atmos configuration to get auth config.
atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{
ComponentFromArg: component,
Expand All @@ -80,6 +85,26 @@ var describeComponentCmd = &cobra.Command{
return errors.Join(errUtils.ErrFailedToInitConfig, err)
}

// Resolve path-based component arguments to component names
if needsPathResolution {
if stack == "" {
return errors.New("--stack flag is required when using path-based component resolution")
}

// We don't know the component type yet - describe component detects it
// So we'll try to resolve the path without component type validation
// The component type will be validated during ExecuteDescribeComponent
resolvedComponent, err := e.ResolveComponentFromPathWithoutTypeCheck(
&atmosConfig,
component,
stack,
)
if err != nil {
return err
}
component = resolvedComponent
}

// Get identity from flag and create AuthManager if provided.
identityName := GetIdentityFromFlags(cmd, os.Args)
authManager, err := CreateAuthManagerFromIdentity(identityName, &atmosConfig.Auth)
Expand Down
46 changes: 46 additions & 0 deletions cmd/validate_component.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package cmd

import (
"errors"
"path/filepath"
"strings"

"github.com/spf13/cobra"

errUtils "github.com/cloudposse/atmos/errors"
e "github.com/cloudposse/atmos/internal/exec"
cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
)

// validateComponentCmd validates atmos components
Expand All @@ -19,6 +25,46 @@ var validateComponentCmd = &cobra.Command{
// Check Atmos configuration
checkAtmosConfig()

// Handle path-based component resolution
if len(args) > 0 {
component := args[0]
needsPathResolution := component == "." || strings.Contains(component, string(filepath.Separator))

if needsPathResolution {
flags := cmd.Flags()
stack, err := flags.GetString("stack")
if err != nil {
return err
}

if stack == "" {
return errors.New("--stack flag is required when using path-based component resolution")
}

// Load atmos configuration
atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{
ComponentFromArg: component,
Stack: stack,
}, false)
if err != nil {
return err
}

// Resolve path to component name (without type check - validate detects type)
resolvedComponent, err := e.ResolveComponentFromPathWithoutTypeCheck(
&atmosConfig,
component,
stack,
)
if err != nil {
return err
}

// Replace the argument with the resolved component name
args[0] = resolvedComponent
}
}

_, _, err := e.ExecuteValidateComponentCmd(cmd, args)
if err != nil {
return err
Expand Down
Loading
Loading