diff --git a/README.md b/README.md index 10f9686e..dbec8a33 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,54 @@ Supported commands so far: | `valet` | Commands for Laravel Valet | | `vault` | Commands for Ansible Vault | +## Configuration +There are three ways to set configuration settings for trellis-cli and they are +loaded in this order of precedence: + +1. global config +2. project config +3. env variables + +The global CLI config (defaults to `$HOME/.config/trellis/cli.yml`) +and will be loaded first (if it exists). + +Next, if a project is detected, the project CLI config will be loaded if it +exists at `.trellis/cli.yml`. + +Finally, env variables prefixed with `TRELLIS_` will be used as +overrides if they match a supported configuration setting. The prefix will be +stripped and the rest is lowercased to determine the setting key. + +Note: only string, numeric, and boolean values are supported when using environment +variables. + +Current supported settings: + +| Setting | Description | Type | Default | +| --- | --- | -- | -- | +| `ask_vault_pass` | Set Ansible to always ask for the vault pass | boolean | false | +| `check_for_updates` | Whether to check for new versions of trellis-cli | boolean | true | +| `load_plugins` | Load external CLI plugins | boolean | true | +| `open` | List of name -> URL shortcuts | map[string]string | none | +| `virtualenv_integration` | Enable automated virtualenv integration | boolean | true | + +Example config: + +```yaml +ask_vault_pass: false +check_for_updates: true +load_plugins: true +open: + site: "https://mysite.com" + admin: "https://mysite.com/wp/wp-admin" +virtualenv_integration: true +``` + +Example env var usage: +```bash +TRELLIS_ASK_VAULT_PASS=true trellis provision production +``` + ## Development trellis-cli requires Go >= 1.18 (`brew install go` on macOS) diff --git a/app_paths/app_paths.go b/app_paths/app_paths.go new file mode 100644 index 00000000..8702612c --- /dev/null +++ b/app_paths/app_paths.go @@ -0,0 +1,52 @@ +package app_paths + +import ( + "os" + "path/filepath" + "runtime" +) + +const ( + appData = "AppData" + trellisConfigDir = "TRELLIS_CONFIG_DIR" + localAppData = "LocalAppData" + xdgCacheHome = "XDG_CACHE_HOME" + xdgConfigHome = "XDG_CONFIG_HOME" + xdgDataHome = "XDG_DATA_HOME" +) + +// Config path precedence: TRELLIS_CONFIG_DIR, XDG_CONFIG_HOME, AppData (windows only), HOME. +func ConfigDir() string { + var path string + + if a := os.Getenv(trellisConfigDir); a != "" { + path = a + } else if b := os.Getenv(xdgConfigHome); b != "" { + path = filepath.Join(b, "trellis") + } else if c := os.Getenv(appData); runtime.GOOS == "windows" && c != "" { + path = filepath.Join(c, "Trellis CLI") + } else { + d, _ := os.UserHomeDir() + path = filepath.Join(d, ".config", "trellis") + } + + return path +} + +func ConfigPath(path string) string { + return filepath.Join(ConfigDir(), path) +} + +// Cache path precedence: XDG_CACHE_HOME, LocalAppData (windows only), HOME. +func CacheDir() string { + var path string + if a := os.Getenv(xdgCacheHome); a != "" { + path = filepath.Join(a, "trellis") + } else if b := os.Getenv(localAppData); runtime.GOOS == "windows" && b != "" { + path = filepath.Join(b, "Trellis CLI") + } else { + c, _ := os.UserHomeDir() + path = filepath.Join(c, ".local", "state", "trellis") + } + return path +} diff --git a/cli_config/cli_config.go b/cli_config/cli_config.go new file mode 100644 index 00000000..5e86707d --- /dev/null +++ b/cli_config/cli_config.go @@ -0,0 +1,101 @@ +package cli_config + +import ( + "errors" + "fmt" + "os" + "reflect" + "strconv" + "strings" + + "gopkg.in/yaml.v2" +) + +type Config struct { + AskVaultPass bool `yaml:"ask_vault_pass"` + CheckForUpdates bool `yaml:"check_for_updates"` + LoadPlugins bool `yaml:"load_plugins"` + Open map[string]string `yaml:"open"` + VirtualenvIntegration bool `yaml:"virtualenv_integration"` +} + +var ( + ErrUnsupportedType = errors.New("Invalid env var config setting: value is an unsupported type.") + ErrCouldNotParse = errors.New("Invalid env var config setting: failed to parse value") +) + +func NewConfig(defaultConfig Config) Config { + return defaultConfig +} + +func (c *Config) LoadFile(path string) error { + configYaml, err := os.ReadFile(path) + + if err != nil && !os.IsNotExist(err) { + return err + } + + if err := yaml.Unmarshal(configYaml, &c); err != nil { + return err + } + + return nil +} + +func (c *Config) LoadEnv(prefix string) error { + structType := reflect.ValueOf(c).Elem() + fields := reflect.VisibleFields(structType.Type()) + + for _, env := range os.Environ() { + parts := strings.Split(env, "=") + originalKey := parts[0] + value := parts[1] + + key := strings.TrimPrefix(originalKey, prefix) + + if originalKey == key { + // key is unchanged and didn't start with prefix + continue + } + + for _, field := range fields { + if strings.ToLower(key) == field.Tag.Get("yaml") { + structValue := structType.FieldByName(field.Name) + + if !structValue.CanSet() { + continue + } + + switch field.Type.Kind() { + case reflect.Bool: + val, err := strconv.ParseBool(value) + + if err != nil { + return fmt.Errorf("%w '%s'\n'%s' can't be parsed as a boolean", ErrCouldNotParse, env, value) + } + + structValue.SetBool(val) + case reflect.Int: + val, err := strconv.ParseInt(value, 10, 32) + + if err != nil { + return fmt.Errorf("%w '%s'\n'%s' can't be parsed as an integer", ErrCouldNotParse, env, value) + } + + structValue.SetInt(val) + case reflect.Float32: + val, err := strconv.ParseFloat(value, 32) + if err != nil { + return fmt.Errorf("%w '%s'\n'%s' can't be parsed as a float", ErrCouldNotParse, env, value) + } + + structValue.SetFloat(val) + default: + return fmt.Errorf("%w\n%s setting of type %s is unsupported.", ErrUnsupportedType, env, field.Type.String()) + } + } + } + } + + return nil +} diff --git a/cli_config/cli_config_test.go b/cli_config/cli_config_test.go new file mode 100644 index 00000000..16581e32 --- /dev/null +++ b/cli_config/cli_config_test.go @@ -0,0 +1,107 @@ +package cli_config + +import ( + _ "fmt" + "os" + "path/filepath" + "strings" + "testing" +) + +func TestLoadFile(t *testing.T) { + conf := Config{ + AskVaultPass: false, + LoadPlugins: true, + } + + dir := t.TempDir() + path := filepath.Join(dir, "cli.yml") + content := ` +ask_vault_pass: true +open: + roots: https://roots.io +` + + if err := os.WriteFile(path, []byte(content), os.ModePerm); err != nil { + t.Fatal(err) + } + + conf.LoadFile(path) + + if conf.LoadPlugins != true { + t.Errorf("expected LoadPlugins to be true (default value)") + } + + if conf.AskVaultPass != true { + t.Errorf("expected AskVaultPass to be true") + } + + open := conf.Open["roots"] + expected := "https://roots.io" + + if open != expected { + t.Errorf("expected open to be %s, got %s", expected, open) + } +} + +func TestLoadEnv(t *testing.T) { + t.Setenv("TRELLIS_ASK_VAULT_PASS", "true") + t.Setenv("TRELLIS_NOPE", "foo") + t.Setenv("ASK_VAULT_PASS", "false") + + conf := Config{ + AskVaultPass: false, + } + + conf.LoadEnv("TRELLIS_") + + if conf.AskVaultPass != true { + t.Errorf("expected AskVaultPass to be true") + } +} + +func TestLoadBoolParseError(t *testing.T) { + t.Setenv("TRELLIS_ASK_VAULT_PASS", "foo") + + conf := Config{} + + err := conf.LoadEnv("TRELLIS_") + + if err == nil { + t.Errorf("expected LoadEnv to return an error") + } + + msg := err.Error() + + expected := ` +Invalid env var config setting: failed to parse value 'TRELLIS_ASK_VAULT_PASS=foo' +'foo' can't be parsed as a boolean +` + + if msg != strings.TrimSpace(expected) { + t.Errorf("expected error %s got %s", expected, msg) + } +} + +func TestLoadEnvUnsupportedType(t *testing.T) { + t.Setenv("TRELLIS_OPEN", "foo") + + conf := Config{} + + err := conf.LoadEnv("TRELLIS_") + + if err == nil { + t.Errorf("expected LoadEnv to return an error") + } + + msg := err.Error() + + expected := ` +Invalid env var config setting: value is an unsupported type. +TRELLIS_OPEN=foo setting of type map[string]string is unsupported. +` + + if msg != strings.TrimSpace(expected) { + t.Errorf("expected error %s got %s", expected, msg) + } +} diff --git a/cmd/init.go b/cmd/init.go index f319c823..eebc735e 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -210,6 +210,5 @@ func virtualenvError(ui cli.Ui) { ui.Error(" 1. Ensure Python 3 is installed and the `python3` command works. trellis-cli will use python3's built-in venv feature.") ui.Error(" Ubuntu/Debian users (including Windows WSL): venv is not built-in, to install it run `sudo apt-get install python3-pip python3-venv`") ui.Error("") - ui.Error(" 2. Disable trellis-cli's virtual env feature, and manage dependencies manually, by setting this env variable:") - ui.Error(fmt.Sprintf(" export %s=false", trellis.TrellisVenvEnvName)) + ui.Error(" 2. Disable trellis-cli's virtualenv feature, and manage dependencies manually, by changing the 'virtualenv_integration' configuration setting to 'false'.") } diff --git a/config/config.go b/config/config.go deleted file mode 100644 index 553c623d..00000000 --- a/config/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package config - -import ( - gap "github.com/muesli/go-app-paths" -) - -var Scope *gap.Scope = gap.NewScope(gap.User, "trellis") diff --git a/go.mod b/go.mod index 923afb1b..8614ae7c 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/mholt/archiver v3.1.1+incompatible github.com/mitchellh/cli v1.1.4 github.com/mitchellh/go-homedir v1.1.0 - github.com/muesli/go-app-paths v0.2.2 github.com/posener/complete v1.2.3 github.com/theckman/yacspin v0.13.12 github.com/weppos/publicsuffix-go v0.20.0 diff --git a/go.sum b/go.sum index cdefe829..261b7d7c 100644 --- a/go.sum +++ b/go.sum @@ -198,8 +198,6 @@ github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrk github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/muesli/go-app-paths v0.2.2 h1:NqG4EEZwNIhBq/pREgfBmgDmt3h1Smr1MjZiXbpZUnI= -github.com/muesli/go-app-paths v0.2.2/go.mod h1:SxS3Umca63pcFcLtbjVb+J0oD7cl4ixQWoBKhGEtEho= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM= diff --git a/main.go b/main.go index f93e31bd..5b848e1c 100644 --- a/main.go +++ b/main.go @@ -4,10 +4,9 @@ import ( "fmt" "os" "path/filepath" - "strconv" + "github.com/roots/trellis-cli/app_paths" "github.com/roots/trellis-cli/cmd" - "github.com/roots/trellis-cli/config" "github.com/roots/trellis-cli/github" "github.com/roots/trellis-cli/plugin" "github.com/roots/trellis-cli/trellis" @@ -22,21 +21,6 @@ var version = "canary" var updaterRepo = "" func main() { - cacheDir, _ := config.Scope.CacheDir() - - updateNotifier := &update.Notifier{ - CacheDir: cacheDir, - Client: github.Client, - Repo: updaterRepo, - Version: version, - } - - updateMessageChan := make(chan *github.Release) - go func() { - release, _ := updateNotifier.CheckForUpdate() - updateMessageChan <- release - }() - c := cli.NewCLI("trellis", version) c.Args = os.Args[1:] @@ -51,6 +35,24 @@ func main() { } trellis := trellis.NewTrellis() + if err := trellis.LoadCliConfig(); err != nil { + ui.Error(err.Error()) + os.Exit(1) + } + + updateNotifier := &update.Notifier{ + CacheDir: app_paths.CacheDir(), + Client: github.Client, + Repo: updaterRepo, + SkipCheck: !trellis.CliConfig.CheckForUpdates, + Version: version, + } + + updateMessageChan := make(chan *github.Release) + go func() { + release, _ := updateNotifier.CheckForUpdate() + updateMessageChan <- release + }() c.Commands = map[string]cli.CommandFactory{ "alias": func() (cli.Command, error) { @@ -180,7 +182,7 @@ func main() { c.HiddenCommands = []string{"venv", "venv hook"} - if shouldSkipPlugins, _ := strconv.ParseBool(os.Getenv("TRELLIS_NO_PLUGINS")); !shouldSkipPlugins { + if trellis.CliConfig.LoadPlugins { pluginPaths := filepath.SplitList(os.Getenv("PATH")) plugin.Register(c, pluginPaths, []string{"trellis"}) } diff --git a/main_test.go b/main_test.go index 8be73715..c80b48a0 100644 --- a/main_test.go +++ b/main_test.go @@ -37,7 +37,7 @@ func TestIntegrationForceNoPlugin(t *testing.T) { mockUi := cli.NewMockUi() trellisCommand := command.WithOptions(command.WithUiOutput(mockUi)).Cmd(bin, []string{"--help"}) - trellisCommand.Env = []string{"PATH=" + tempDir + ":$PATH", "TRELLIS_NO_PLUGINS=true"} + trellisCommand.Env = []string{"PATH=" + tempDir + ":$PATH", "TRELLIS_LOAD_PLUGINS=false"} trellisCommand.Run() output := mockUi.ErrorWriter.String() diff --git a/trellis/cli_config.go b/trellis/cli_config.go deleted file mode 100644 index 1feff619..00000000 --- a/trellis/cli_config.go +++ /dev/null @@ -1,19 +0,0 @@ -package trellis - -import ( - "os" -) - -type CliConfig struct { - AskVaultPass bool `yaml:"ask_vault_pass"` - Open map[string]string `yaml:"open"` -} - -func (c *CliConfig) Init() error { - if c.AskVaultPass { - // https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-ask-vault-pass - os.Setenv("ANSIBLE_ASK_VAULT_PASS", "true") - } - - return nil -} diff --git a/trellis/cli_config_test.go b/trellis/cli_config_test.go deleted file mode 100644 index e5cd3025..00000000 --- a/trellis/cli_config_test.go +++ /dev/null @@ -1,30 +0,0 @@ -package trellis - -import ( - "os" - "testing" -) - -func TestCliConfigInit(t *testing.T) { - config := CliConfig{} - config.Init() - - _, ok := os.LookupEnv("ANSIBLE_ASK_VAULT_PASS") - - if ok { - t.Error("expected ANSIBLE_ASK_VAULT_PASS to not be set") - } -} - -func TestCliConfigInitEnablesAskVaultPass(t *testing.T) { - config := CliConfig{AskVaultPass: true} - config.Init() - - env := os.Getenv("ANSIBLE_ASK_VAULT_PASS") - - os.Setenv("ANSIBLE_ASK_VAULT_PASS", "") - - if env != "true" { - t.Error("expected ANSIBLE_ASK_VAULT_PASS to be true") - } -} diff --git a/trellis/trellis.go b/trellis/trellis.go index 5520d03f..59380a0e 100644 --- a/trellis/trellis.go +++ b/trellis/trellis.go @@ -10,13 +10,18 @@ import ( "strings" "github.com/mitchellh/cli" + "github.com/roots/trellis-cli/app_paths" + "github.com/roots/trellis-cli/cli_config" "gopkg.in/ini.v1" "gopkg.in/yaml.v2" ) -const ConfigDir = ".trellis" -const ConfigFile = "cli.yml" -const GlobPattern = "group_vars/*/wordpress_sites.yml" +const ( + ConfigDir = ".trellis" + GlobPattern = "group_vars/*/wordpress_sites.yml" +) + +const cliConfigFile = "cli.yml" type Options struct { Detector Detector @@ -25,8 +30,16 @@ type Options struct { type TrellisOption func(*Trellis) +var DefaultCliConfig = cli_config.Config{ + AskVaultPass: false, + CheckForUpdates: true, + LoadPlugins: true, + Open: make(map[string]string), + VirtualenvIntegration: true, +} + type Trellis struct { - CliConfig *CliConfig + CliConfig cli_config.Config ConfigDir string Detector Detector Environments map[string]*Config @@ -44,8 +57,9 @@ func NewTrellis(opts ...TrellisOption) *Trellis { ) t := &Trellis{ - Detector: &ProjectDetector{}, + CliConfig: cli_config.NewConfig(DefaultCliConfig), ConfigDir: defaultConfigDir, + Detector: &ProjectDetector{}, VenvInitialized: defaultVenvInitialized, venvWarned: defaultVenvWarned, } @@ -95,7 +109,7 @@ func (t *Trellis) CreateConfigDir() error { func (t *Trellis) CheckVirtualenv(ui cli.Ui) { if !t.venvWarned && !t.VenvInitialized { - ui.Warn(fmt.Sprintf(` + ui.Warn(` WARNING: This project has not been initialized with trellis-cli and may not work as expected. You may see this warning if you are using trellis-cli on an existing project (previously created without the CLI). @@ -104,9 +118,9 @@ To ensure you have the required dependencies, initialize the project with the fo $ trellis init If you want to manage dependencies yourself manually, you can ignore this warning. -To disable this automated check, set the %s environment variable to 'false': export %s=false +To disable this automated check, set the 'virtualenv_integration' configuration setting to 'false'. - `, TrellisVenvEnvName, TrellisVenvEnvName)) + `) } t.venvWarned = true @@ -163,8 +177,7 @@ func (t *Trellis) LoadProject() error { return errors.New("No Trellis project detected in the current directory or any of its parent directories.") } - t.CliConfig, err = t.LoadCliConfig() - if err != nil { + if err = t.LoadProjectCliConfig(); err != nil { return err } @@ -173,7 +186,7 @@ func (t *Trellis) LoadProject() error { os.Chdir(t.Path) - if os.Getenv(TrellisVenvEnvName) != "false" { + if t.CliConfig.VirtualenvIntegration { if t.Virtualenv.Initialized() { t.VenvInitialized = true t.Virtualenv.Activate() @@ -260,26 +273,35 @@ func (t *Trellis) getDefaultSiteNameFromEnvironment(environment string) (siteNam return sites[0], nil } -func (t *Trellis) LoadCliConfig() (config *CliConfig, err error) { - config = &CliConfig{} - path := filepath.Join(t.ConfigPath(), ConfigFile) - configYaml, err := os.ReadFile(path) +func (t *Trellis) LoadCliConfig() error { + path := app_paths.ConfigPath(cliConfigFile) - if err != nil && !os.IsNotExist(err) { - return config, fmt.Errorf("Error reading CLI config file %s: %v", path, err) + if err := t.CliConfig.LoadFile(path); err != nil { + return fmt.Errorf("Error loading CLI config %s\n\n%v", path, err) } - if err == nil { - if err = yaml.UnmarshalStrict(configYaml, &config); err != nil { - return config, fmt.Errorf("Error parsing CLI config file: %v", err) - } + if err := t.CliConfig.LoadEnv("TRELLIS_"); err != nil { + return fmt.Errorf("Error loading CLI config\n\n%v", err) } - if err = config.Init(); err != nil { - return config, fmt.Errorf("Error initializing CLI config: %v", err) + return nil +} + +func (t *Trellis) LoadProjectCliConfig() error { + path := filepath.Join(t.ConfigPath(), cliConfigFile) + + if err := t.CliConfig.LoadFile(path); err != nil { + return fmt.Errorf("Error loading CLI config %s\n%v", path, err) } - return config, nil + t.CliConfig.LoadEnv("TRELLIS_") + + if t.CliConfig.AskVaultPass { + // https://docs.ansible.com/ansible/latest/reference_appendices/config.html#default-ask-vault-pass + os.Setenv("ANSIBLE_ASK_VAULT_PASS", "true") + } + + return nil } func (t *Trellis) SiteFromEnvironmentAndName(environment string, name string) *Site { diff --git a/trellis/trellis_test.go b/trellis/trellis_test.go index 9e34175b..e3b2e81d 100644 --- a/trellis/trellis_test.go +++ b/trellis/trellis_test.go @@ -6,6 +6,8 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/roots/trellis-cli/app_paths" ) func TestCreateConfigDir(t *testing.T) { @@ -296,16 +298,6 @@ func TestLoadProjectForProjects(t *testing.T) { t.Errorf("expected %s to be %s", tp.Path, wd) } - expectedConfig := &CliConfig{ - Open: map[string]string{ - "sentry": "https://myapp.sentry.io", - }, - } - - if !reflect.DeepEqual(tp.CliConfig, expectedConfig) { - t.Errorf("expected config not equal") - } - expectedEnvNames := []string{"development", "production", "valet-link"} if !reflect.DeepEqual(tp.EnvironmentNames(), expectedEnvNames) { @@ -314,30 +306,83 @@ func TestLoadProjectForProjects(t *testing.T) { } func TestLoadCliConfigWhenFileDoesNotExist(t *testing.T) { + tempDir := t.TempDir() + t.Setenv("TRELLIS_CONFIG_DIR", tempDir) + + tp := NewTrellis() + err := tp.LoadCliConfig() + + if err != nil { + t.Error("expected no error") + } + + if !reflect.DeepEqual(tp.CliConfig, DefaultCliConfig) { + t.Errorf("expected default CLI config %v, got %v", DefaultCliConfig, tp.CliConfig) + } +} + +func TestLoadGlobalCliConfig(t *testing.T) { tp := NewTrellis() - config, _ := tp.LoadCliConfig() + tempDir := t.TempDir() + t.Setenv("TRELLIS_CONFIG_DIR", tempDir) + + configFilePath := app_paths.ConfigPath(cliConfigFile) + configContents := ` +ask_vault_pass: true +` + + if err := os.WriteFile(configFilePath, []byte(configContents), 0666); err != nil { + t.Fatal(err) + } - if config == nil { - t.Error("expected config object") + err := tp.LoadCliConfig() + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + if tp.CliConfig.AskVaultPass != true { + t.Errorf("expected global CLI config to get AskVaultPass to true") } } -func TestLoadCliConfigWhenFileExists(t *testing.T) { +func TestLoadProjectCliConfig(t *testing.T) { defer LoadFixtureProject(t)() tp := NewTrellis() - configFilePath := filepath.Join(tp.ConfigPath(), ConfigFile) - configContents := `` + tempDir := t.TempDir() + t.Setenv("TRELLIS_CONFIG_DIR", tempDir) + + configFilePath := app_paths.ConfigPath(cliConfigFile) + configContents := ` +ask_vault_pass: true +` if err := os.WriteFile(configFilePath, []byte(configContents), 0666); err != nil { t.Fatal(err) } - config, _ := tp.LoadCliConfig() + err := tp.LoadCliConfig() + + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + projectConfigContents := ` +ask_vault_pass: false +` + + if err := os.WriteFile(filepath.Join(tp.ConfigPath(), cliConfigFile), []byte(projectConfigContents), 0666); err != nil { + t.Fatal(err) + } + + if err = tp.LoadProjectCliConfig(); err != nil { + t.Fatalf("unexpected error: %v", err) + } - if !reflect.DeepEqual(config, &CliConfig{}) { - t.Error("expected open object") + if tp.CliConfig.AskVaultPass != false { + t.Errorf("expected project CLI config to override AskVaultPass to false") } } diff --git a/trellis/virtualenv.go b/trellis/virtualenv.go index bd468b2c..73aecf23 100644 --- a/trellis/virtualenv.go +++ b/trellis/virtualenv.go @@ -13,7 +13,6 @@ import ( "github.com/roots/trellis-cli/command" ) -const TrellisVenvEnvName string = "TRELLIS_VENV" const VenvEnvName string = "VIRTUAL_ENV" const PathEnvName string = "PATH" const OldPathEnvName string = "PRE_TRELLIS_PATH" diff --git a/update/main.go b/update/main.go index c369dc02..4a2eff99 100644 --- a/update/main.go +++ b/update/main.go @@ -1,7 +1,6 @@ package update import ( - "github.com/roots/trellis-cli/github" "net/http" "os" "path/filepath" @@ -9,12 +8,14 @@ import ( "github.com/hashicorp/go-version" "github.com/mattn/go-isatty" + "github.com/roots/trellis-cli/github" "gopkg.in/yaml.v2" ) type Notifier struct { CacheDir string ForceCheck bool + SkipCheck bool Repo string Version string Client *http.Client @@ -48,16 +49,16 @@ func (n *Notifier) shouldCheckForUpdate() bool { return true } - if n.Repo == "" || n.CacheDir == "" { + if n.SkipCheck { return false } - // skip completion commands - if os.Getenv("COMP_LINE") != "" { + if n.Repo == "" || n.CacheDir == "" { return false } - if os.Getenv("TRELLIS_NO_UPDATE_NOTIFIER") != "" { + // skip completion commands + if os.Getenv("COMP_LINE") != "" { return false } diff --git a/update/main_test.go b/update/main_test.go index 6bf162d3..03aa6250 100644 --- a/update/main_test.go +++ b/update/main_test.go @@ -25,50 +25,70 @@ func TestDoesNotCheckForUpdate(t *testing.T) { name string repo string cacheDir string - env []Env + skipCheck bool + env Env latestRelease *github.Release }{ { "no_repo", "", cacheDir, - nil, + false, + Env{}, nil, }, { "no_cache_dir", "roots/trellis-cli", "", - nil, + false, + Env{}, nil, }, { "completion_command", "roots/trellis-cli", cacheDir, - []Env{{"COMP_LINE", "foo"}}, + false, + Env{"COMP_LINE", "foo"}, nil, }, { - "TRELLIS_NO_UPDATE_NOTIFIER set", + "CI set", "roots/trellis-cli", cacheDir, - []Env{{"TRELLIS_NO_UPDATE_NOTIFIER", "1"}}, + false, + Env{"CI", "1"}, nil, }, { - "CI set", + "SkipCheck set", "roots/trellis-cli", cacheDir, - []Env{{"CI", "1"}}, + true, + Env{}, nil, }, } for _, tc := range cases { - updateNotifier := &Notifier{CacheDir: tc.cacheDir, Repo: tc.repo, Version: "1.0"} + updateNotifier := &Notifier{ + CacheDir: tc.cacheDir, + SkipCheck: tc.skipCheck, + Repo: tc.repo, + Version: "1.0", + } + + if tc.env.key != "" { + t.Setenv(tc.env.key, tc.env.value) + } + release, err := updateNotifier.CheckForUpdate() + if tc.env.key != "" { + os.Unsetenv(tc.env.key) + } + if err != nil { t.Errorf("expected no error, but got %q", err) } @@ -228,11 +248,11 @@ latest_release: } updateNotifier := &Notifier{ - ForceCheck: true, CacheDir: cacheDir, + Client: client, + ForceCheck: true, Repo: "roots/trellis-cli", Version: tc.version, - Client: client, } release, _ := updateNotifier.CheckForUpdate()