Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
af9cf3d
Fix markdown code fence in Nerd Fonts installation instructions (#1917)
osterman Dec 28, 2025
f2384ad
Address PR review comments for auth registry refactor
osterman Dec 29, 2025
83e8adf
Fix NoOptDefVal preprocessing for identity flag in auth commands
osterman Dec 29, 2025
df871c1
Add documentation for --identity flag placement best practices
osterman Dec 29, 2025
03d98f1
Address PR review comments: constants and security annotations
osterman Dec 29, 2025
50dca0a
Add --identity flag documentation to global flags reference
osterman Dec 29, 2025
3f3ea78
Refactor preprocessing architecture with preprocessArgs orchestrator
osterman Dec 30, 2025
d7ff75a
Address PR review comments
osterman Jan 2, 2026
9e12803
Add unit tests for cmd/auth package to improve coverage
osterman Jan 2, 2026
3aeeb0a
Fix cross-platform compatibility for exec tests
osterman Jan 6, 2026
c66f3e6
Merge origin/main into osterman/auth-registry-refactor
osterman Jan 10, 2026
b352c69
Merge origin/main into osterman/auth-registry-refactor
osterman Jan 13, 2026
5546d2b
test: Add tests for --profile flag in auth commands (fixes #1973)
osterman Jan 15, 2026
f45fdc9
docs: Reorganize terraform usage examples into distinct sections
osterman Jan 16, 2026
02a76d7
test: Regenerate auth command golden snapshots
osterman Jan 16, 2026
95e491a
Merge remote-tracking branch 'origin/main' into osterman/auth-registr…
osterman Jan 16, 2026
ed6c41e
fix: Windows test failures and website build
osterman Jan 16, 2026
2d79d7e
fix: Add IsExperimental method to AuthCommandProvider
osterman Jan 17, 2026
caccbca
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 17, 2026
ec40c5a
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Jan 17, 2026
d56631c
[autofix.ci] apply automated fixes (attempt 3/3)
autofix-ci[bot] Jan 17, 2026
b2cf1c2
Merge branch 'main' into osterman/auth-registry-refactor
osterman Jan 17, 2026
2e097bb
[autofix.ci] apply automated fixes
autofix-ci[bot] Jan 17, 2026
cfb8fc8
fix: Address CodeRabbit review comments for auth-registry-refactor
osterman Jan 18, 2026
ce3ae23
Merge branch 'main' into osterman/auth-registry-refactor
osterman Jan 20, 2026
3bc2495
chore: Regenerate golden snapshots for experimental auth command
osterman Jan 20, 2026
e6a589c
Merge branch 'main' into osterman/auth-registry-refactor
osterman Jan 24, 2026
3a95d93
Merge branch 'main' into osterman/auth-registry-refactor
osterman Mar 10, 2026
69f1ab0
fix: Address CodeRabbit review findings in auth commands
osterman Mar 10, 2026
d8c0c60
fix: Migrate auth commands from legacy u.Print* to ui/data layer and …
osterman Mar 11, 2026
137ffb6
Merge branch 'main' into osterman/auth-registry-refactor
aknysh May 7, 2026
52c23bb
feat(auth): port main features to refactored cmd/auth package
aknysh May 7, 2026
5798a2f
updates
aknysh May 7, 2026
8d83f2d
[autocommit] formatting fixes
atmos-pro[bot] May 7, 2026
ef352d5
test(auth): add regression tests for issues #1973 and #2392
aknysh May 7, 2026
7db3f62
Merge remote-tracking branch 'origin/osterman/auth-registry-refactor'…
aknysh May 7, 2026
f3c3d4a
updates
aknysh May 7, 2026
7cce4a5
Merge branch 'main' into osterman/auth-registry-refactor
aknysh May 9, 2026
5530c27
test(auth): boost cmd/auth coverage from 33% to 47%
aknysh May 9, 2026
9792dc0
style: apply gofumpt v0.10 multiline call wrapping
aknysh May 9, 2026
ceb57db
test(auth): boost cmd/auth coverage from 47% to 69%
aknysh May 10, 2026
60a0cf3
test(auth): address review comments and push coverage from 47% to 78%
aknysh May 11, 2026
5cdaceb
test(auth): address review comments — fence repair, helpers, sentinel
aknysh May 11, 2026
3a71bb9
test(auth): apply resetAuthCmdFlags across all smoke tests for state …
aknysh May 11, 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
164 changes: 84 additions & 80 deletions NOTICE

Large diffs are not rendered by default.

50 changes: 0 additions & 50 deletions cmd/auth.go

This file was deleted.

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

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

"github.com/cloudposse/atmos/cmd/auth/user"
"github.com/cloudposse/atmos/cmd/internal"
cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/flags"
"github.com/cloudposse/atmos/pkg/flags/compat"
"github.com/cloudposse/atmos/pkg/perf"
)

const (
// IdentityFlagName is the name of the identity flag (from pkg/config/const.go).
IdentityFlagName = cfg.IdentityFlagName
// IdentityFlagSelectValue is the sentinel value for interactive selection (from pkg/config/const.go).
IdentityFlagSelectValue = cfg.IdentityFlagSelectValue
)

// authParser handles persistent flags for auth command.
var authParser *flags.StandardParser

// authCmd groups authentication-related subcommands.
var authCmd = &cobra.Command{
Use: "auth",
Short: "Authenticate with cloud providers and identity services.",
Long: "Obtain, refresh, and configure credentials from external identity providers such as AWS SSO, Vault, or OIDC. Provides the necessary authentication context for tools like Terraform and Helm to interact with cloud infrastructure.",
FParseErrWhitelist: struct{ UnknownFlags bool }{UnknownFlags: false},
ValidArgsFunction: ComponentsArgCompletion,
}

func init() {
defer perf.Track(nil, "auth.init")()

// Create parser with persistent identity flag.
authParser = flags.NewStandardParser(
flags.WithStringFlag(IdentityFlagName, "i", "", "Specify the target identity to assume. Use without value to interactively select."),
flags.WithNoOptDefVal(IdentityFlagName, IdentityFlagSelectValue),
flags.WithEnvVars(IdentityFlagName, "ATMOS_IDENTITY"),
)

// Register as persistent flags (inherited by subcommands).
authParser.RegisterPersistentFlags(authCmd)

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

// Add identity completion.
AddIdentityCompletion(authCmd)

// Add user subcommand (nested command structure).
authCmd.AddCommand(user.AuthUserCmd)

// Register this command with the registry.
internal.Register(&AuthCommandProvider{})
}
Comment thread
aknysh marked this conversation as resolved.

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

// GetCommand returns the auth command.
func (a *AuthCommandProvider) GetCommand() *cobra.Command {
return authCmd
}

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

// GetGroup returns the command group for help organization.
func (a *AuthCommandProvider) GetGroup() string {
return "Pro Features"
}

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

// GetPositionalArgsBuilder returns the positional args builder for this command.
func (a *AuthCommandProvider) GetPositionalArgsBuilder() *flags.PositionalArgsBuilder {
return nil
}

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

// GetAliases returns command aliases.
func (a *AuthCommandProvider) GetAliases() []internal.CommandAlias {
return nil
}

// IsExperimental returns whether this command is experimental.
// Auth commands are currently experimental as part of Pro Features.
func (a *AuthCommandProvider) IsExperimental() bool {
return true
}

// GetIdentityFromFlags retrieves the identity flag value, handling the NoOptDefVal quirk.
// The identity flag uses NoOptDefVal="__SELECT__" which causes Viper to always return "__SELECT__"
// after parsing, even when the flag wasn't provided. This function checks the actual flag state
// to return the correct value.
//
// Returns:
// - Empty string if --identity was not provided (use config/env default).
// - IdentityFlagSelectValue if --identity was provided without a value (user wants selection).
// - The actual value if --identity=value was provided.
func GetIdentityFromFlags(cmd *cobra.Command) string {
defer perf.Track(nil, "auth.GetIdentityFromFlags")()

flag := cmd.Flags().Lookup(IdentityFlagName)
if flag == nil {
// Check persistent flags if not found in local flags.
flag = cmd.InheritedFlags().Lookup(IdentityFlagName)
}

if flag == nil || !flag.Changed {
// Flag was not explicitly set on command line.
// Return empty string to signal "use default from config/env".
return ""
}

// Flag was explicitly set - return its value.
// This could be:
// - The actual value (--identity=prod or --identity prod).
// - IdentityFlagSelectValue (--identity without value).
return flag.Value.String()
}

// GetAuthCmd returns the auth command for subcommand registration.
func GetAuthCmd() *cobra.Command {
return authCmd
}
172 changes: 172 additions & 0 deletions cmd/auth/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package auth

import (
"testing"

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

func TestAuthCommandProvider(t *testing.T) {
provider := &AuthCommandProvider{}

assert.Equal(t, "auth", provider.GetName())
assert.Equal(t, "Pro Features", provider.GetGroup())
assert.NotNil(t, provider.GetCommand())
assert.NotNil(t, provider.GetFlagsBuilder())
}

func TestGetIdentityFromFlags(t *testing.T) {
tests := []struct {
name string
setupCmd func(*cobra.Command)
expected string
}{
{
name: "flag not set",
setupCmd: func(cmd *cobra.Command) {
cmd.Flags().String(IdentityFlagName, "", "identity")
},
expected: "",
},
{
name: "flag set with value",
setupCmd: func(cmd *cobra.Command) {
cmd.Flags().String(IdentityFlagName, "", "identity")
_ = cmd.Flags().Set(IdentityFlagName, "prod-admin")
},
expected: "prod-admin",
},
{
name: "flag set without value (NoOptDefVal)",
setupCmd: func(cmd *cobra.Command) {
cmd.Flags().String(IdentityFlagName, "", "identity")
// Simulate NoOptDefVal behavior by setting the select value.
_ = cmd.Flags().Set(IdentityFlagName, IdentityFlagSelectValue)
},
expected: IdentityFlagSelectValue,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
cmd := &cobra.Command{Use: "test"}
tt.setupCmd(cmd)

result := GetIdentityFromFlags(cmd)
assert.Equal(t, tt.expected, result)
})
}
}

func TestAuthCommand_Structure(t *testing.T) {
assert.Equal(t, "auth", authCmd.Use)
assert.NotEmpty(t, authCmd.Short)
assert.NotEmpty(t, authCmd.Long)

// Auth command should have subcommands.
subcommands := authCmd.Commands()
assert.Greater(t, len(subcommands), 0)

// Get subcommand names.
subcommandNames := make([]string, len(subcommands))
for i, cmd := range subcommands {
subcommandNames[i] = cmd.Name()
}

// Verify expected subcommands exist.
assert.Contains(t, subcommandNames, "login")
assert.Contains(t, subcommandNames, "logout")
assert.Contains(t, subcommandNames, "whoami")
assert.Contains(t, subcommandNames, "list")
assert.Contains(t, subcommandNames, "env")
assert.Contains(t, subcommandNames, "shell")
assert.Contains(t, subcommandNames, "exec")
assert.Contains(t, subcommandNames, "console")
assert.Contains(t, subcommandNames, "validate")
}

func TestAuthCommand_IdentityFlag(t *testing.T) {
// Check identity flag is registered as persistent flag.
identityFlag := authCmd.PersistentFlags().Lookup(IdentityFlagName)
assert.NotNil(t, identityFlag)
assert.Equal(t, "i", identityFlag.Shorthand)
}

func TestIdentityFlagName(t *testing.T) {
assert.Equal(t, "identity", IdentityFlagName)
}

func TestIdentityFlagSelectValue(t *testing.T) {
assert.Equal(t, "__SELECT__", IdentityFlagSelectValue)
}

func TestAuthCommand_HasRunE(t *testing.T) {
// Auth parent command may or may not have RunE.
// Just verify the command is properly configured.
assert.Equal(t, "auth", authCmd.Use)
}

func TestAuthCommand_PersistentPreRunE(t *testing.T) {
// Auth command should have a PersistentPreRunE for flag setup.
// The actual function is set, so verify it's not nil.
// Note: In the actual implementation, this might be set to nil or a function.
// Just verify the command is properly initialized.
assert.NotNil(t, authCmd)
}

func TestGetIdentityFromFlags_NoFlag(t *testing.T) {
// Test with a command that doesn't have the identity flag at all.
cmd := &cobra.Command{Use: "test"}

// Should return empty string without panicking.
result := GetIdentityFromFlags(cmd)
assert.Equal(t, "", result)
}

func TestAddIdentityCompletion(t *testing.T) {
cmd := &cobra.Command{Use: "test"}
cmd.Flags().String(IdentityFlagName, "", "identity")

// Should not panic.
assert.NotPanics(t, func() {
AddIdentityCompletion(cmd)
})
}

// TestAuthCommandProvider_OptionalMethods covers the CommandProvider interface
// methods that have stable, contract-level returns (no auth/IO required).
func TestAuthCommandProvider_OptionalMethods(t *testing.T) {
provider := &AuthCommandProvider{}

t.Run("GetPositionalArgsBuilder returns nil", func(t *testing.T) {
assert.Nil(t, provider.GetPositionalArgsBuilder(),
"auth command does not declare positional args via the builder")
})

t.Run("GetCompatibilityFlags returns nil", func(t *testing.T) {
assert.Nil(t, provider.GetCompatibilityFlags(),
"auth command has no terraform/helmfile-style compat flags")
})

t.Run("GetAliases returns nil", func(t *testing.T) {
assert.Nil(t, provider.GetAliases(),
"auth command has no top-level aliases")
})

t.Run("IsExperimental returns true", func(t *testing.T) {
// Auth is currently in experimental Pro Features. If this flips to
// stable, the change is intentional and the assertion should be flipped.
assert.True(t, provider.IsExperimental(),
"auth command is experimental (Pro Feature)")
})
}

// TestGetAuthCmd_ReturnsAuthCmd guards the public accessor used by other
// packages (e.g. cmd/ai) to attach subcommands.
func TestGetAuthCmd_ReturnsAuthCmd(t *testing.T) {
got := GetAuthCmd()
assert.NotNil(t, got)
assert.Equal(t, "auth", got.Use)
assert.Same(t, authCmd, got, "GetAuthCmd must return the package-level authCmd")
}
Loading
Loading