diff --git a/CHANGELOG.md b/CHANGELOG.md index 55cbc5af..68fbbdbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ * Warnings about invalid accounts/roles in config.yaml are now Debug messages #980 * Default ProfileFormat is now the `Friendly` format #992 * `config`, `config-profiles` and `completions` are now sub-commands of `setup` #975 + * Only the and `cache` command will auto-update the contents of `~/.aws/config` #974 + * `tags` command no longer supports the `--force-update` option ### New Features diff --git a/cmd/aws-sso/cache_cmd.go b/cmd/aws-sso/cache_cmd.go index 23d6dff3..b656e7d3 100644 --- a/cmd/aws-sso/cache_cmd.go +++ b/cmd/aws-sso/cache_cmd.go @@ -20,9 +20,15 @@ package main import ( "fmt" + + "github.com/synfinatic/aws-sso-cli/internal/awsconfig" + "github.com/synfinatic/aws-sso-cli/internal/url" ) -type CacheCmd struct{} +type CacheCmd struct { + NoConfigCheck bool `kong:"help='Disable automatic ~/.aws/config updates'"` + Threads int `kong:"help='Override number of threads for talking to AWS',default=${DEFAULT_THREADS}"` +} func (cc *CacheCmd) Run(ctx *RunContext) error { s, err := ctx.Settings.GetSelectedSSO(ctx.Cli.SSO) @@ -35,7 +41,7 @@ func (cc *CacheCmd) Run(ctx *RunContext) error { log.Fatalf(err.Error()) } - err = ctx.Settings.Cache.Refresh(AwsSSO, s, ssoName, ctx.Cli.Threads) + added, deleted, err := ctx.Settings.Cache.Refresh(AwsSSO, s, ssoName, ctx.Cli.Cache.Threads) if err != nil { return fmt.Errorf("unable to refresh role cache: %s", err.Error()) } @@ -46,5 +52,19 @@ func (cc *CacheCmd) Run(ctx *RunContext) error { return fmt.Errorf("unable to save role cache: %s", err.Error()) } + if added > 0 || deleted > 0 { + log.Infof("Updated cache: %d added, %d deleted", added, deleted) + // should we update our config?? + if !ctx.Cli.Cache.NoConfigCheck && ctx.Settings.AutoConfigCheck { + if ctx.Settings.ConfigProfilesUrlAction != url.ConfigProfilesUndef { + action, _ := url.NewAction(string(ctx.Settings.ConfigProfilesUrlAction)) + err := awsconfig.UpdateAwsConfig(ctx.Settings, action, "", true, false) + if err != nil { + log.Errorf("Unable to auto-update aws config file: %s", err.Error()) + } + } + } + } + return nil } diff --git a/cmd/aws-sso/login_cmd.go b/cmd/aws-sso/login_cmd.go index 6aa980d9..ce2ff161 100644 --- a/cmd/aws-sso/login_cmd.go +++ b/cmd/aws-sso/login_cmd.go @@ -19,12 +19,12 @@ package main */ import ( - "github.com/synfinatic/aws-sso-cli/internal/awsconfig" "github.com/synfinatic/aws-sso-cli/internal/sso" - "github.com/synfinatic/aws-sso-cli/internal/url" ) -type LoginCmd struct{} +type LoginCmd struct { + Threads int `kong:"help='Override number of threads for talking to AWS',default=${DEFAULT_THREADS}"` +} func (cc *LoginCmd) Run(ctx *RunContext) error { doAuth(ctx) @@ -71,22 +71,17 @@ func doAuth(ctx *RunContext) { if err != nil { log.Fatalf(err.Error()) } - if err = ctx.Settings.Cache.Refresh(AwsSSO, s, ssoName, ctx.Cli.Threads); err != nil { + added, deleted, err := ctx.Settings.Cache.Refresh(AwsSSO, s, ssoName, ctx.Cli.Login.Threads) + if err != nil { log.WithError(err).Fatalf("Unable to refresh cache") } - if err = ctx.Settings.Cache.Save(true); err != nil { - log.WithError(err).Errorf("Unable to save cache") + + if added > 0 || deleted > 0 { + log.Infof("Updated cache: %d added, %d deleted", added, deleted) } - // should we update our config?? - if !ctx.Cli.NoConfigCheck && ctx.Settings.AutoConfigCheck { - if ctx.Settings.ConfigProfilesUrlAction != url.ConfigProfilesUndef { - action, _ := url.NewAction(string(ctx.Settings.ConfigProfilesUrlAction)) - err := awsconfig.UpdateAwsConfig(ctx.Settings, action, "", true, false) - if err != nil { - log.Errorf("Unable to auto-update aws config file: %s", err.Error()) - } - } + if err = ctx.Settings.Cache.Save(true); err != nil { + log.WithError(err).Errorf("Unable to save cache") } } } diff --git a/cmd/aws-sso/main.go b/cmd/aws-sso/main.go index 34a399f2..669d3305 100644 --- a/cmd/aws-sso/main.go +++ b/cmd/aws-sso/main.go @@ -60,8 +60,9 @@ type RunContext struct { } const ( - DEFAULT_STORE = "file" - COPYRIGHT_YEAR = "2021-2024" + DEFAULT_STORE = "file" + COPYRIGHT_YEAR = "2021-2024" + DEFAULT_THREADS = 5 ) var DEFAULT_CONFIG map[string]interface{} = map[string]interface{}{ @@ -95,22 +96,20 @@ var DEFAULT_CONFIG map[string]interface{} = map[string]interface{}{ "UrlAction": "open", "LogLevel": "warn", "ProfileFormat": NICE_PROFILE_FORMAT, - "Threads": 5, + "Threads": DEFAULT_THREADS, "MaxBackoff": 5, // seconds "MaxRetry": 10, } type CLI struct { // Common Arguments - Browser string `kong:"short='b',help='Path to browser to open URLs with',env='AWS_SSO_BROWSER'"` - ConfigFile string `kong:"name='config',default='${CONFIG_FILE}',help='Config file',env='AWS_SSO_CONFIG',predict='allFiles'"` - LogLevel string `kong:"short='L',name='level',help='Logging level [error|warn|info|debug|trace] (default: warn)'"` - Lines bool `kong:"help='Print line number in logs'"` - UrlAction string `kong:"short='u',help='How to handle URLs [clip|exec|open|print|printurl|granted-containers|open-url-in-container] (default: open)'"` - SSO string `kong:"short='S',help='Override default AWS SSO Instance',env='AWS_SSO',predictor='sso'"` - STSRefresh bool `kong:"help='Force refresh of STS Token Credentials'"` - NoConfigCheck bool `kong:"help='Disable automatic ~/.aws/config updates'"` - Threads int `kong:"help='Override number of threads for talking to AWS (default: 5)'"` + Browser string `kong:"short='b',help='Path to browser to open URLs with',env='AWS_SSO_BROWSER'"` + ConfigFile string `kong:"name='config',default='${CONFIG_FILE}',help='Config file',env='AWS_SSO_CONFIG',predict='allFiles'"` + LogLevel string `kong:"short='L',name='level',help='Logging level [error|warn|info|debug|trace] (default: warn)'"` + Lines bool `kong:"help='Print line number in logs'"` + UrlAction string `kong:"short='u',help='How to handle URLs [clip|exec|open|print|printurl|granted-containers|open-url-in-container] (default: open)'"` + SSO string `kong:"short='S',help='Override default AWS SSO Instance',env='AWS_SSO',predictor='sso'"` + STSRefresh bool `kong:"help='Force refresh of STS Token Credentials'"` // Commands Cache CacheCmd `kong:"cmd,help='Force reload of cached AWS SSO role info and config.yaml'"` @@ -251,6 +250,7 @@ func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) { "CONFIG_DIR": config.ConfigDir(false), "CONFIG_FILE": config.ConfigFile(false), "DEFAULT_STORE": DEFAULT_STORE, + "DEFAULT_THREADS": fmt.Sprintf("%d", DEFAULT_THREADS), "JSON_STORE_FILE": config.JsonStoreFile(false), "VERSION": Version, } @@ -292,12 +292,19 @@ func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) { log.Fatalf("Invalid --url-action %s", cli.UrlAction) } + threads := 0 + if cli.Cache.Threads != DEFAULT_THREADS { + threads = cli.Cache.Threads + } else if cli.Login.Threads != DEFAULT_THREADS { + threads = cli.Login.Threads + } + override := sso.OverrideSettings{ Browser: cli.Browser, DefaultSSO: cli.SSO, LogLevel: cli.LogLevel, LogLines: cli.Lines, - Threads: cli.Threads, + Threads: threads, // must be > 0 to override config UrlAction: action, } diff --git a/cmd/aws-sso/tags_cmd.go b/cmd/aws-sso/tags_cmd.go index e92909b0..bb8ab5e3 100644 --- a/cmd/aws-sso/tags_cmd.go +++ b/cmd/aws-sso/tags_cmd.go @@ -26,43 +26,24 @@ import ( ) type TagsCmd struct { - AccountId int64 `kong:"name='account',short='A',help='Filter results based on AWS AccountID'"` - Role string `kong:"short='R',help='Filter results based on AWS Role Name'"` - ForceUpdate bool `kong:"help='Force account/role cache update'"` + AccountId int64 `kong:"name='account',short='A',help='Filter results based on AWS AccountID'"` + Role string `kong:"short='R',help='Filter results based on AWS Role Name'"` } func (cc *TagsCmd) Run(ctx *RunContext) error { set := ctx.Settings cache := ctx.Settings.Cache.GetSSO() - if ctx.Cli.Tags.ForceUpdate { - s := set.SSO[ctx.Cli.SSO] - - ssoName, err := ctx.Settings.GetSelectedSSOName(ctx.Cli.SSO) - if err != nil { - log.Fatalf(err.Error()) - } + s, err := ctx.Settings.GetSelectedSSO(ctx.Cli.SSO) + if err != nil { + return err + } - err = set.Cache.Refresh(AwsSSO, s, ssoName, ctx.Cli.Threads) - if err != nil { - log.WithError(err).Fatalf("Unable to refresh role cache") - } - err = set.Cache.Save(true) - if err != nil { - log.WithError(err).Errorf("Unable to save cache") - } - } else { - s, err := ctx.Settings.GetSelectedSSO(ctx.Cli.SSO) - if err != nil { + if err := set.Cache.Expired(s); err != nil { + log.Warn(err.Error()) + c := &CacheCmd{} + if err = c.Run(ctx); err != nil { return err } - - if err := set.Cache.Expired(s); err != nil { - log.Warn(err.Error()) - c := &CacheCmd{} - if err = c.Run(ctx); err != nil { - return err - } - } } roles := []*sso.AWSRoleFlat{} diff --git a/docs/commands.md b/docs/commands.md index 8a337605..ed5e9e57 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -10,8 +10,6 @@ * `--url-action`, `-u` -- How to handle URLs for your SSO provider * `--sso `, `-S` -- Specify non-default AWS SSO instance to use (`$AWS_SSO`) * `--sts-refresh` -- Force refresh of STS Token Credentials - * `--no-config-check` -- Disable automatic updating of `~/.aws/config` - * `--threads ` -- Number of threads to use with AWS (default: 5) ## Commands @@ -24,6 +22,11 @@ hours, but you can force this data to be refreshed immediately. Cache data is also automatically updated anytime the `config.yaml` file is modified. +Flags: + + * `--no-config-check` -- Disable automatic updating of `~/.aws/config` + * `--threads ` -- Number of threads to use with AWS (default: 5) + --- ### console @@ -245,6 +248,11 @@ case-sensitive manner. Login via AWS IAM Identity Center (AWS SSO) and retrieve a security token used to fetch IAM Role credentials. +Flags: + + * `--no-config-check` -- Disable automatic updating of `~/.aws/config` + * `--threads ` -- Number of threads to use with AWS (default: 5) + --- ### logout diff --git a/internal/sso/cache.go b/internal/sso/cache.go index b3ac3e6c..a45500d3 100644 --- a/internal/sso/cache.go +++ b/internal/sso/cache.go @@ -22,6 +22,7 @@ import ( "encoding/json" "fmt" "os" + "slices" "strconv" "strings" "time" @@ -277,11 +278,11 @@ func (c *Cache) deleteOldHistory() { } // Refresh updates our cached Roles based on AWS SSO & our Config -// but does not save this data! -func (c *Cache) Refresh(sso *AWSSSO, config *SSOConfig, ssoName string, threads int) error { +// but does not save this data! Returns the number of roles added/deleted +func (c *Cache) Refresh(sso *AWSSSO, config *SSOConfig, ssoName string, threads int) (int, int, error) { // Only refresh once per execution if c.refreshed { - return nil + return 0, 0, nil } c.refreshed = true log.Debugf("refreshing %s SSO cache", ssoName) @@ -311,16 +312,41 @@ func (c *Cache) Refresh(sso *AWSSSO, config *SSOConfig, ssoName string, threads } // zero out our current roles cache entries so they don't get merged + oldRoles := cache.Roles.GetAllRoles() + oldRoleArns := []string{} + for _, role := range oldRoles { + oldRoleArns = append(oldRoleArns, role.Arn) + } + c.SSO[ssoName].Roles = &Roles{} c.SSO[ssoName].ConfigHash = config.GetConfigHash(c.settings.ProfileFormat) // load our AWSSSO & Config r, err := c.NewRoles(sso, config, threads) if err != nil { - return err + return 0, 0, err } c.SSO[ssoName].Roles = r + // figure out what roles were added/deleted + newRoles := c.SSO[ssoName].Roles.GetAllRoles() + newRoleArns := []string{} + for _, role := range newRoles { + newRoleArns = append(newRoleArns, role.Arn) + } + + added, deleted := 0, 0 + for _, arn := range newRoleArns { + if !slices.Contains(oldRoleArns, arn) { + added++ + } + } + for _, arn := range oldRoleArns { + if !slices.Contains(newRoleArns, arn) { + deleted++ + } + } + // restore our history tags & expires for _, account := range c.SSO[ssoName].Roles.Accounts { for _, role := range account.Roles { @@ -333,7 +359,7 @@ func (c *Cache) Refresh(sso *AWSSSO, config *SSOConfig, ssoName string, threads } } c.ConfigCreatedAt = config.CreatedAt() - return nil + return added, deleted, nil } // pruneSSO removes any SSO instances that are no longer configured