Skip to content
Merged
Show file tree
Hide file tree
Changes from 34 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c3fb27c
feat: Modernize Helmfile EKS integration with flexible cluster names
osterman Dec 20, 2025
73d3dc8
feat: Add cluster name precedence and deprecation warnings to EKS upd…
osterman Dec 20, 2025
6395d87
chore: Update examples to use cluster_name_template instead of deprec…
osterman Dec 20, 2025
6e99b32
test: Regenerate golden snapshots for Helmfile schema changes
osterman Dec 21, 2025
a91c9e5
test: Add test coverage for Helmfile EKS integration
osterman Dec 22, 2025
2bc7ade
refactor: Migrate helmfile and aws commands to command registry pattern
osterman Dec 22, 2025
d3f7e0b
fix: Address CodeRabbit review feedback
osterman Dec 22, 2025
7bee1e7
fix: Restore helmfile path resolution and help output after registry …
osterman Dec 22, 2025
36c9941
fix: Address CodeRabbit review feedback
osterman Dec 23, 2025
8f07b06
docs: Update roadmap with Helmfile EKS modernization
osterman Dec 27, 2025
722b51a
fix: Remove unused errUtils import from describe_affected_helpers.go
osterman Dec 28, 2025
5cd1c02
fix: Add nil check for context in ResolveClusterName
osterman Jan 3, 2026
84d2527
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
aknysh Jan 3, 2026
f910665
fix: Regenerate helmfile snapshots after source command removal
osterman Jan 4, 2026
03633ae
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
osterman Jan 10, 2026
096a439
Merge remote-tracking branch 'origin/main' into feature/dev-2345-clus…
osterman Jan 10, 2026
73d1877
fix: Regenerate stack-manifest-name snapshots with new helmfile fields
osterman Jan 10, 2026
5f5a925
Merge remote-tracking branch 'origin/main' into feature/dev-2345-clus…
osterman Jan 15, 2026
49a26bd
fix: Regenerate describe_component_with_stack_flag snapshot
osterman Jan 15, 2026
3252375
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 16, 2026
09f1b5b
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Jan 16, 2026
5a58a1a
test: Add tests and update roadmap for PR #1903
osterman Jan 18, 2026
e1c2caa
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
osterman Jan 18, 2026
d188d35
docs: Add migration guide for use_eks default change
osterman Jan 18, 2026
333ea47
test: Assert passthrough semantics in handlePathResolutionError test
osterman Jan 18, 2026
182a8c6
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
osterman Jan 18, 2026
d28fad1
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
osterman Jan 20, 2026
47a4274
fix: Add helmfile source subcommand and fix broken changelog links
osterman Jan 20, 2026
b93051d
test: Regenerate helmfile help snapshots with source subcommand
osterman Jan 20, 2026
0ac5925
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
osterman Jan 21, 2026
c58c07d
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
osterman Jan 24, 2026
4d2e140
fix: Update snapshots for new helmfile cluster_name fields and fix PR…
osterman Jan 24, 2026
ffb6ca9
Merge branch 'main' into feature/dev-2345-cluster_name_pattern-should…
aknysh Feb 10, 2026
9cd5afb
test: increase test coverage for Helmfile EKS modernization
aknysh Feb 10, 2026
7308eff
fix: address CodeRabbit review comments and increase test coverage
aknysh Feb 10, 2026
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
19 changes: 0 additions & 19 deletions cmd/aws.go

This file was deleted.

75 changes: 75 additions & 0 deletions cmd/aws/aws.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package aws

import (
"github.com/spf13/cobra"

"github.com/cloudposse/atmos/cmd/aws/eks"
"github.com/cloudposse/atmos/cmd/internal"
"github.com/cloudposse/atmos/pkg/flags"
"github.com/cloudposse/atmos/pkg/flags/compat"
)

// doubleDashHint is displayed in help output.
const doubleDashHint = "Use double dashes to separate Atmos-specific options from native arguments and flags for the command."

// awsCmd executes 'aws' CLI commands.
var awsCmd = &cobra.Command{
Use: "aws",
Short: "Run AWS-specific commands for interacting with cloud resources",
Long: `This command allows interaction with AWS resources through various CLI commands.`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
Args: cobra.NoArgs,
}

func init() {
awsCmd.PersistentFlags().Bool("", false, doubleDashHint)

// Add EKS subcommand from the eks subpackage.
awsCmd.AddCommand(eks.EksCmd)

// Register this command with the registry.
internal.Register(&AWSCommandProvider{})
}

// AWSCommandProvider implements the CommandProvider interface.
type AWSCommandProvider struct{}

// GetCommand returns the aws command.
func (a *AWSCommandProvider) GetCommand() *cobra.Command {
return awsCmd
}

// GetName returns the command name.
func (a *AWSCommandProvider) GetName() string {
return "aws"
}

// GetGroup returns the command group for help organization.
func (a *AWSCommandProvider) GetGroup() string {
return "Cloud Integration"
}

// GetAliases returns command aliases.
func (a *AWSCommandProvider) GetAliases() []internal.CommandAlias {
return nil // No aliases for aws command.
}

// GetFlagsBuilder returns the flags builder for this command.
func (a *AWSCommandProvider) GetFlagsBuilder() flags.Builder {
return nil
}

// GetPositionalArgsBuilder returns the positional args builder for this command.
func (a *AWSCommandProvider) GetPositionalArgsBuilder() *flags.PositionalArgsBuilder {
return nil // AWS command has subcommands, not positional args.
}

// GetCompatibilityFlags returns compatibility flags for this command.
func (a *AWSCommandProvider) GetCompatibilityFlags() map[string]compat.CompatibilityFlag {
return nil
}

// IsExperimental returns whether this command is experimental.
func (a *AWSCommandProvider) IsExperimental() bool {
return false
}
81 changes: 81 additions & 0 deletions cmd/aws/aws_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package aws

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestAWSCommandProvider_GetCommand(t *testing.T) {
provider := &AWSCommandProvider{}
cmd := provider.GetCommand()

assert.NotNil(t, cmd)
assert.Equal(t, "aws", cmd.Use)
assert.Contains(t, cmd.Short, "AWS")
}

func TestAWSCommandProvider_GetName(t *testing.T) {
provider := &AWSCommandProvider{}
name := provider.GetName()

assert.Equal(t, "aws", name)
}

func TestAWSCommandProvider_GetGroup(t *testing.T) {
provider := &AWSCommandProvider{}
group := provider.GetGroup()

assert.Equal(t, "Cloud Integration", group)
}

func TestAWSCommandProvider_GetAliases(t *testing.T) {
provider := &AWSCommandProvider{}
aliases := provider.GetAliases()

assert.Nil(t, aliases)
}

func TestAWSCommandProvider_GetFlagsBuilder(t *testing.T) {
provider := &AWSCommandProvider{}
builder := provider.GetFlagsBuilder()

assert.Nil(t, builder)
}

func TestAWSCommandProvider_GetPositionalArgsBuilder(t *testing.T) {
provider := &AWSCommandProvider{}
builder := provider.GetPositionalArgsBuilder()

assert.Nil(t, builder)
}

func TestAWSCommandProvider_GetCompatibilityFlags(t *testing.T) {
provider := &AWSCommandProvider{}
flags := provider.GetCompatibilityFlags()

assert.Nil(t, flags)
}

func TestAWSCommandProvider_IsExperimental(t *testing.T) {
provider := &AWSCommandProvider{}
experimental := provider.IsExperimental()

assert.False(t, experimental)
}

func TestAWSCommand_HasEksSubcommand(t *testing.T) {
provider := &AWSCommandProvider{}
cmd := provider.GetCommand()

// Find the EKS subcommand.
var foundEks bool
for _, subCmd := range cmd.Commands() {
if subCmd.Use == "eks" {
foundEks = true
break
}
}

assert.True(t, foundEks, "aws command should have eks subcommand")
}
14 changes: 4 additions & 10 deletions cmd/aws_eks.go → cmd/aws/eks/eks.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package cmd
package eks

import (
"github.com/spf13/cobra"
)
import "github.com/spf13/cobra"

// awsCmd executes 'aws eks' CLI commands.
var awsEksCmd = &cobra.Command{
// EksCmd executes 'aws eks' CLI commands.
var EksCmd = &cobra.Command{
Use: "eks",
Short: "Run AWS EKS CLI commands for cluster management",
Long: `Manage Amazon EKS clusters using AWS CLI, including configuring kubeconfig and performing cluster-related operations.
Expand All @@ -17,7 +15,3 @@ https://atmos.tools/cli/commands/aws/eks-update-kubeconfig`,
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
Args: cobra.NoArgs,
}

func init() {
awsCmd.AddCommand(awsEksCmd)
}
79 changes: 79 additions & 0 deletions cmd/aws/eks/update_kubeconfig.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package eks

import (
"github.com/spf13/cobra"
"github.com/spf13/viper"

e "github.com/cloudposse/atmos/internal/exec"
"github.com/cloudposse/atmos/pkg/flags"
"github.com/cloudposse/atmos/pkg/perf"
)

// updateKubeconfigParser handles flag parsing with Viper precedence.
var updateKubeconfigParser *flags.StandardParser

// updateKubeconfigCmd executes 'aws eks update-kubeconfig' command.
var updateKubeconfigCmd = &cobra.Command{
Use: "update-kubeconfig",
Short: "Update `kubeconfig` for an EKS cluster using AWS CLI",
Long: `This command executes ` + "`" + `aws eks update-kubeconfig` + "`" + ` to download ` + "`" + `kubeconfig` + "`" + ` from an EKS cluster and saves it to a file. The command executes ` + "`" + `aws eks update-kubeconfig` + "`" + ` in three different ways:

1. If all the required parameters (cluster name and AWS profile/role) are provided on the command-line,
then ` + "`" + `atmos` + "`" + ` executes the command without requiring the ` + "`" + `atmos.yaml` + "`" + ` CLI config and context.

2. If 'component' and 'stack' are provided on the command-line,
then ` + "`" + `atmos` + "`" + ` executes the command using the ` + "`" + `atmos.yaml` + "`" + ` CLI config and stack's context by searching for the following settings:
- 'components.helmfile.cluster_name_pattern' in the 'atmos.yaml' CLI config (and calculates the '--name' parameter using the pattern)
- 'components.helmfile.helm_aws_profile_pattern' in the 'atmos.yaml' CLI config (and calculates the ` + "`" + `--profile` + "`" + ` parameter using the pattern)
- 'components.helmfile.kubeconfig_path' in the 'atmos.yaml' CLI config
- the variables for the component in the provided stack
- 'region' from the variables for the component in the stack

3. Combination of the above. Provide a component and a stack, and override other parameters on the command line.

See https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html for more information.`,

FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
RunE: func(cmd *cobra.Command, args []string) error {
defer perf.Track(nil, "eks.updateKubeconfig.RunE")()

// Bind flags to Viper for precedence handling.
v := viper.GetViper()
if err := updateKubeconfigParser.BindFlagsToViper(cmd, v); err != nil {
return err
}

return e.ExecuteAwsEksUpdateKubeconfigCommand(cmd, args)
},
}

// https://docs.aws.amazon.com/cli/latest/reference/eks/update-kubeconfig.html.
func init() {
// Create parser with update-kubeconfig-specific flags using functional options.
updateKubeconfigParser = flags.NewStandardParser(
flags.WithStringFlag("stack", "s", "", "Specify the stack name"),
flags.WithStringFlag("profile", "", "", "Specify the AWS CLI profile to use for authentication"),
flags.WithStringFlag("name", "", "", "Specify the name of the EKS cluster to update the kubeconfig for"),
flags.WithStringFlag("region", "", "", "Specify the AWS region where the EKS cluster is located"),
flags.WithStringFlag("kubeconfig", "", "", "Specify the path to the kubeconfig file to be updated or created for accessing the EKS cluster."),
flags.WithStringFlag("role-arn", "", "", "Specify the ARN of the IAM role to assume for authenticating with the EKS cluster."),
flags.WithBoolFlag("dry-run", "", false, "Perform a dry run to simulate updating the kubeconfig without making any changes."),
flags.WithBoolFlag("verbose", "", false, "Enable verbose logging to provide detailed output during the kubeconfig update process."),
flags.WithStringFlag("alias", "", "", "Specify an alias to use for the cluster context name in the kubeconfig file."),
// Environment variable bindings.
flags.WithEnvVars("stack", "ATMOS_STACK"),
flags.WithEnvVars("profile", "ATMOS_AWS_PROFILE", "AWS_PROFILE"),
flags.WithEnvVars("region", "ATMOS_AWS_REGION", "AWS_REGION"),
flags.WithEnvVars("kubeconfig", "ATMOS_KUBECONFIG", "KUBECONFIG"),
)

// Register flags with Cobra command.
updateKubeconfigParser.RegisterFlags(updateKubeconfigCmd)

// Bind to Viper for environment variable support.
if err := updateKubeconfigParser.BindToViper(viper.GetViper()); err != nil {
panic(err)
}

EksCmd.AddCommand(updateKubeconfigCmd)
}
64 changes: 64 additions & 0 deletions cmd/aws/eks/update_kubeconfig_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package eks

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestUpdateKubeconfigCmd_Error(t *testing.T) {
err := updateKubeconfigCmd.RunE(updateKubeconfigCmd, []string{})
assert.Error(t, err, "aws eks update-kubeconfig command should return an error when called with no parameters")
}

func TestUpdateKubeconfigCmd_Flags(t *testing.T) {
// Verify all expected flags are registered.
flags := updateKubeconfigCmd.Flags()

tests := []struct {
name string
flagName string
shorthand string
expectFlag bool
}{
{name: "stack flag", flagName: "stack", shorthand: "s", expectFlag: true},
{name: "profile flag", flagName: "profile", shorthand: "", expectFlag: true},
{name: "name flag", flagName: "name", shorthand: "", expectFlag: true},
{name: "region flag", flagName: "region", shorthand: "", expectFlag: true},
{name: "kubeconfig flag", flagName: "kubeconfig", shorthand: "", expectFlag: true},
{name: "role-arn flag", flagName: "role-arn", shorthand: "", expectFlag: true},
{name: "dry-run flag", flagName: "dry-run", shorthand: "", expectFlag: true},
{name: "verbose flag", flagName: "verbose", shorthand: "", expectFlag: true},
{name: "alias flag", flagName: "alias", shorthand: "", expectFlag: true},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
flag := flags.Lookup(tt.flagName)
if tt.expectFlag {
require.NotNil(t, flag, "flag %s should exist", tt.flagName)
if tt.shorthand != "" {
assert.Equal(t, tt.shorthand, flag.Shorthand)
}
}
})
}
}

func TestUpdateKubeconfigCmd_CommandMetadata(t *testing.T) {
assert.Equal(t, "update-kubeconfig", updateKubeconfigCmd.Use)
assert.Contains(t, updateKubeconfigCmd.Short, "Update")
assert.Contains(t, updateKubeconfigCmd.Short, "kubeconfig")
assert.NotEmpty(t, updateKubeconfigCmd.Long)
}

func TestUpdateKubeconfigCmd_FParseErrWhitelist(t *testing.T) {
// This command should NOT whitelist unknown flags (strict parsing).
assert.False(t, updateKubeconfigCmd.FParseErrWhitelist.UnknownFlags)
}

func TestUpdateKubeconfigParser(t *testing.T) {
// Verify the parser is initialized.
require.NotNil(t, updateKubeconfigParser, "updateKubeconfigParser should be initialized")
}
52 changes: 0 additions & 52 deletions cmd/aws_eks_update_kubeconfig.go

This file was deleted.

Loading