-
-
Notifications
You must be signed in to change notification settings - Fork 153
Expand file tree
/
Copy pathidentity_flag.go
More file actions
161 lines (148 loc) · 6.12 KB
/
identity_flag.go
File metadata and controls
161 lines (148 loc) · 6.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package cmd
import (
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cloudposse/atmos/pkg/auth"
cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
)
// GetIdentityFromFlags retrieves the identity value from command-line flags and environment variables.
// This function handles the Cobra NoOptDefVal quirk where --identity <value> with positional args
// can be misinterpreted as --identity (without value).
//
// Returns:
// - identity value if explicitly provided
// - cfg.IdentityFlagSelectValue if --identity was used without a value (interactive selection)
// - cfg.IdentityFlagDisabledValue if --identity=false (authentication disabled)
// - value from ATMOS_IDENTITY env var if flag not provided
// - empty string if no identity specified anywhere
//
// Usage:
//
// identity := GetIdentityFromFlags(cmd, os.Args)
// if identity == cfg.IdentityFlagSelectValue {
// // Show interactive selector
// } else if identity == cfg.IdentityFlagDisabledValue {
// // Skip authentication
// } else if identity != "" {
// // Use explicit identity
// }
func GetIdentityFromFlags(cmd *cobra.Command, osArgs []string) string {
// Check if flag was set via Cobra first (handles both SetArgs in tests and real CLI).
// For commands without positional args, this is reliable.
// For commands with positional args, we'll use os.Args parsing as a fallback.
if cmd.Flags().Changed(IdentityFlagName) {
value, _ := cmd.Flags().GetString(IdentityFlagName)
// Only trust this value if it's not the NoOptDefVal issue.
// If we got "__SELECT__" but there might be a real value in os.Args, check os.Args.
if value != cfg.IdentityFlagSelectValue {
return normalizeIdentityValue(value)
}
// Got __SELECT__ - check if os.Args has an actual value.
identity := extractIdentityFromArgs(osArgs)
if identity != "" && identity != cfg.IdentityFlagSelectValue {
// Found actual value in os.Args - use that instead.
return normalizeIdentityValue(identity)
}
// No value in os.Args either - return __SELECT__.
return value
}
// Flag not changed - fall back to environment variable.
envValue := viper.GetString(IdentityFlagName)
return normalizeIdentityValue(envValue)
}
// normalizeIdentityValue converts boolean false representations to the disabled sentinel value.
// Recognizes: false, False, FALSE, 0, no, No, NO, off, Off, OFF.
// All other values are returned unchanged.
func normalizeIdentityValue(value string) string {
if value == "" {
return ""
}
// Check if value represents boolean false.
switch strings.ToLower(value) {
case "false", "0", "no", "off":
return cfg.IdentityFlagDisabledValue
default:
return value
}
}
// extractIdentityFromArgs manually parses os.Args to find --identity flag and its value.
// This is necessary because Cobra's NoOptDefVal behavior causes it to misinterpret
// "--identity value" as "--identity" (without value) when positional args are present.
//
// Handles three cases:
// - --identity value (space-separated) -> returns "value"
// - --identity=value (equals sign) -> returns "value"
// - --identity (no value) -> returns cfg.IdentityFlagSelectValue
//
// Returns empty string if --identity flag is not present in args.
func extractIdentityFromArgs(args []string) string {
for i, arg := range args {
// Handle --identity=value format.
if strings.HasPrefix(arg, cfg.IdentityFlag+"=") {
value := strings.TrimPrefix(arg, cfg.IdentityFlag+"=")
if value == "" {
// --identity= (empty value) -> interactive selection.
return cfg.IdentityFlagSelectValue
}
return value
}
// Handle -i=value format (short flag).
if strings.HasPrefix(arg, "-i=") {
value := strings.TrimPrefix(arg, "-i=")
if value == "" {
// -i= (empty value) -> interactive selection.
return cfg.IdentityFlagSelectValue
}
return value
}
// Handle --identity value format (space-separated).
if arg == cfg.IdentityFlag || arg == "-i" {
// Check if next arg exists and is not another flag.
if i+1 < len(args) && !strings.HasPrefix(args[i+1], "-") {
// Has value: --identity <value>.
return args[i+1]
}
// No value: --identity (interactive selection).
return cfg.IdentityFlagSelectValue
}
}
// Flag not found in args.
return ""
}
// CreateAuthManagerFromIdentity creates and authenticates an AuthManager from an identity name.
// Returns nil if identityName is empty (no authentication requested).
// Returns error if identityName is provided but auth is not configured in atmos.yaml.
// This helper reduces nested complexity in describe commands.
//
// This function delegates to auth.CreateAndAuthenticateManager to ensure consistent
// authentication behavior across CLI commands and internal execution logic.
//
// Note: This function does not scan stack configs for default identities.
// Use CreateAuthManagerFromIdentityWithAtmosConfig if you need stack-level default identity resolution.
func CreateAuthManagerFromIdentity(
identityName string,
authConfig *schema.AuthConfig,
) (auth.AuthManager, error) {
return auth.CreateAndAuthenticateManager(identityName, authConfig, IdentityFlagSelectValue)
}
// CreateAuthManagerFromIdentityWithAtmosConfig creates and authenticates an AuthManager from an identity name.
// This version accepts the full atmosConfig to enable scanning stack configs for default identities.
//
// When identityName is empty and atmosConfig is provided:
// - Scans stack configuration files for auth identity defaults
// - Merges stack-level defaults with atmos.yaml defaults
// - Stack defaults have lower priority than atmos.yaml defaults
//
// This solves the chicken-and-egg problem where:
// - We need to know the default identity to authenticate
// - But stack configs are only loaded after authentication is configured
// - Stack-level defaults (auth.identities.*.default: true) would otherwise be ignored
func CreateAuthManagerFromIdentityWithAtmosConfig(
identityName string,
authConfig *schema.AuthConfig,
atmosConfig *schema.AtmosConfiguration,
) (auth.AuthManager, error) {
return auth.CreateAndAuthenticateManagerWithAtmosConfig(identityName, authConfig, IdentityFlagSelectValue, atmosConfig)
}