diff --git a/CHANGELOG.md b/CHANGELOG.md index 128a4791..745c19a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,12 +10,14 @@ * Replace `--port` with `--server` flag for the `aws-sso ecs [list|load|unload|profile]` commands #937 * Update cache during login when relevant settings in the config.yaml changes #555 * Add support for `$AWS_SHARED_CREDENTIALS_FILE` #914 + * Add support for `~/.config/aws-sso` #330 ### Changes * Bump cache file version to 4. * `ConfigProfilesUrlAction` now defaults to value of `UrlAction` instead of `url` #946 * Rename/rework many of the `ecs` commands #938 + * New installs of `aws-sso` will default to `~/.config/aws-sso` instead of `~/.aws-sso` for configuration ## [v1.16.1] - 2024-06-13 diff --git a/cmd/aws-sso/main.go b/cmd/aws-sso/main.go index 251fa2be..6edde02f 100644 --- a/cmd/aws-sso/main.go +++ b/cmd/aws-sso/main.go @@ -29,6 +29,7 @@ import ( // "github.com/davecgh/go-spew/spew" "github.com/sirupsen/logrus" "github.com/synfinatic/aws-sso-cli/internal/awscreds" + "github.com/synfinatic/aws-sso-cli/internal/config" "github.com/synfinatic/aws-sso-cli/internal/ecs" "github.com/synfinatic/aws-sso-cli/internal/ecs/client" "github.com/synfinatic/aws-sso-cli/internal/ecs/server" @@ -60,12 +61,8 @@ type RunContext struct { } const ( - CONFIG_DIR = "~/.aws-sso" - CONFIG_FILE = CONFIG_DIR + "/config.yaml" - JSON_STORE_FILE = CONFIG_DIR + "/store.json" - INSECURE_CACHE_FILE = CONFIG_DIR + "/cache.json" - DEFAULT_STORE = "file" - COPYRIGHT_YEAR = "2021-2024" + DEFAULT_STORE = "file" + COPYRIGHT_YEAR = "2021-2024" ) var DEFAULT_CONFIG map[string]interface{} = map[string]interface{}{ @@ -192,7 +189,7 @@ func main() { log.WithError(err).Fatalf("Unable to open config file: %s", cli.ConfigFile) } - cacheFile := utils.GetHomePath(INSECURE_CACHE_FILE) + cacheFile := config.InsecureCacheFile(true) if runCtx.Settings, err = sso.LoadSettings(cli.ConfigFile, cacheFile, DEFAULT_CONFIG, override); err != nil { log.Fatalf("%s", err.Error()) @@ -201,7 +198,7 @@ func main() { // Load the secure store data switch runCtx.Settings.SecureStore { case "json": - sfile := utils.GetHomePath(JSON_STORE_FILE) + sfile := config.JsonStoreFile(true) if runCtx.Settings.JsonStore != "" { sfile = utils.GetHomePath(runCtx.Settings.JsonStore) } @@ -211,7 +208,7 @@ func main() { } log.Warnf("Using insecure json file for SecureStore: %s", sfile) default: - cfg, err := storage.NewKeyringConfig(runCtx.Settings.SecureStore, CONFIG_DIR) + cfg, err := storage.NewKeyringConfig(runCtx.Settings.SecureStore, config.ConfigDir(true)) if err != nil { log.WithError(err).Fatalf("Unable to create SecureStore") } @@ -231,10 +228,10 @@ func main() { func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) { // need to pass in the variables for defaults vars := kong.Vars{ - "CONFIG_DIR": CONFIG_DIR, - "CONFIG_FILE": CONFIG_FILE, + "CONFIG_DIR": config.ConfigDir(false), + "CONFIG_FILE": config.ConfigFile(false), "DEFAULT_STORE": DEFAULT_STORE, - "JSON_STORE_FILE": JSON_STORE_FILE, + "JSON_STORE_FILE": config.JsonStoreFile(false), "VERSION": Version, } @@ -245,7 +242,7 @@ func parseArgs(cli *CLI) (*kong.Context, sso.OverrideSettings) { vars, ) - p := predictor.NewPredictor(utils.GetHomePath(INSECURE_CACHE_FILE), utils.GetHomePath(CONFIG_FILE)) + p := predictor.NewPredictor(config.InsecureCacheFile(true), config.ConfigFile(true)) kongplete.Complete(parser, kongplete.WithPredictors( diff --git a/docs/config.md b/docs/config.md index 60ea83e9..10cf97b1 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,8 +1,17 @@ # Configuration -By default, `aws-sso` stores it's configuration file in `~/.aws-sso/config.yaml`, -but this can be overridden by setting `$AWS_SSO_CONFIG` in your shell or via the -`--config` flag. +By default, `aws-sso` will by default store all it's configuration and state files in +`~/.config/aws-sso` for versions `>= 1.17.0` per the [XDG spec]( +https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). +Previous versions of `aws-sso` used `~/.aws-sso`. Users at their own descresion may move the +files to the new location. To keep files co-located in the same place, if the directory +`~/.aws-sso` exists, then it will be used. + +**Note:** The `aws-sso` documentation will generally use the older file path (`~/.aws-sso/...`) +when describing file locations. + +The main configuration file is named `~/.aws-sso/config.yaml`, but this can be overridden by +setting `$AWS_SSO_CONFIG` in your shell or via the `--config` flag. The first time you run `aws-sso` and it detects there is no configuration file, it will prompt you for a number of questions to give you a basic configuration. diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..d887d8b5 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,94 @@ +package config + +/* + * AWS SSO CLI + * Copyright (c) 2021-2024 Aaron Turner + * + * This program is free software: you can redistribute it + * and/or modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or with the authors permission any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import ( + "fmt" + "os" + + "github.com/synfinatic/aws-sso-cli/internal/utils" +) + +const ( + OLD_CONFIG_DIR = "~/.aws-sso" + CONFIG_DIR = "~/.config/aws-sso" + CONFIG_FILE = "%s/config.yaml" + JSON_STORE_FILE = "%s/store.json" + INSECURE_CACHE_FILE = "%s/cache.json" +) + +func ConfigDir(expand bool) string { + var path string + fi, err := os.Stat(utils.GetHomePath(OLD_CONFIG_DIR)) + if err == nil && fi.IsDir() { + path = OLD_CONFIG_DIR + } else { + path = CONFIG_DIR + } + if expand { + path = utils.GetHomePath(path) + } + return path +} + +// ConfigFile returns the path to the config file +func ConfigFile(expand bool) string { + var path string + fi, err := os.Stat(utils.GetHomePath(OLD_CONFIG_DIR)) + fmt.Printf("fi: %v, err: %v\n", fi, err) + if err == nil && fi.IsDir() { + path = fmt.Sprintf(CONFIG_FILE, OLD_CONFIG_DIR) + } else { + path = fmt.Sprintf(CONFIG_FILE, CONFIG_DIR) + } + if expand { + path = utils.GetHomePath(path) + } + return path +} + +// JsonStoreFile returns the path to the JSON store file +func JsonStoreFile(expand bool) string { + var path string + fi, err := os.Stat(utils.GetHomePath(OLD_CONFIG_DIR)) + if err == nil && fi.IsDir() { + path = fmt.Sprintf(JSON_STORE_FILE, OLD_CONFIG_DIR) + } else { + path = fmt.Sprintf(JSON_STORE_FILE, CONFIG_DIR) + } + if expand { + path = utils.GetHomePath(path) + } + return path +} + +// InsecureCacheFile returns the path to the insecure cache file +func InsecureCacheFile(expand bool) string { + var path string + fi, err := os.Stat(utils.GetHomePath(OLD_CONFIG_DIR)) + if err == nil && fi.IsDir() { + path = fmt.Sprintf(INSECURE_CACHE_FILE, OLD_CONFIG_DIR) + } else { + path = fmt.Sprintf(INSECURE_CACHE_FILE, CONFIG_DIR) + } + if expand { + path = utils.GetHomePath(path) + } + return path +} diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 00000000..5af36934 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,72 @@ +package config + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestConfigDir(t *testing.T) { + home := os.Getenv("HOME") + tempDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + os.Setenv("HOME", tempDir) + defer os.Setenv("HOME", home) + + assert.Equal(t, tempDir+"/.config/aws-sso", ConfigDir(true)) + assert.Equal(t, "~/.config/aws-sso", ConfigDir(false)) + _ = os.MkdirAll(fmt.Sprintf("%s/.aws-sso", tempDir), 0755) + assert.Equal(t, tempDir+"/.aws-sso", ConfigDir(true)) + assert.Equal(t, "~/.aws-sso", ConfigDir(false)) +} +func TestConfigFile(t *testing.T) { + tempDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + os.Setenv("HOME", tempDir) + home := os.Getenv("HOME") + defer os.Setenv("HOME", home) + + assert.Equal(t, tempDir+"/.config/aws-sso/config.yaml", ConfigFile(true)) + assert.Equal(t, "~/.config/aws-sso/config.yaml", ConfigFile(false)) + _ = os.MkdirAll(fmt.Sprintf("%s/.aws-sso", tempDir), 0755) + assert.Equal(t, tempDir+"/.aws-sso/config.yaml", ConfigFile(true)) + assert.Equal(t, "~/.aws-sso/config.yaml", ConfigFile(false)) +} + +func TestJsonStoreFile(t *testing.T) { + tempDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + os.Setenv("HOME", tempDir) + home := os.Getenv("HOME") + defer os.Setenv("HOME", home) + + assert.Equal(t, tempDir+"/.config/aws-sso/store.json", JsonStoreFile(true)) + assert.Equal(t, "~/.config/aws-sso/store.json", JsonStoreFile(false)) + _ = os.MkdirAll(fmt.Sprintf("%s/.aws-sso", tempDir), 0755) + assert.Equal(t, tempDir+"/.aws-sso/store.json", JsonStoreFile(true)) + assert.Equal(t, "~/.aws-sso/store.json", JsonStoreFile(false)) +} + +func TestInsecureCacheFile(t *testing.T) { + tempDir, err := os.MkdirTemp("", "") + assert.NoError(t, err) + defer os.RemoveAll(tempDir) + + os.Setenv("HOME", tempDir) + home := os.Getenv("HOME") + defer os.Setenv("HOME", home) + + assert.Equal(t, tempDir+"/.config/aws-sso/cache.json", InsecureCacheFile(true)) + assert.Equal(t, "~/.config/aws-sso/cache.json", InsecureCacheFile(false)) + _ = os.MkdirAll(fmt.Sprintf("%s/.aws-sso", tempDir), 0755) + assert.Equal(t, tempDir+"/.aws-sso/cache.json", InsecureCacheFile(true)) + assert.Equal(t, "~/.aws-sso/cache.json", InsecureCacheFile(false)) +}