Skip to content

Commit

Permalink
Merge pull request #436 from DopplerHQ/tom-flags
Browse files Browse the repository at this point in the history
Add `configure flags` command for configuring CLI behavior
  • Loading branch information
nmanoogian committed Feb 15, 2024
2 parents e7a95f7 + e093993 commit 2522b47
Show file tree
Hide file tree
Showing 16 changed files with 504 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/salus.yaml
Expand Up @@ -25,7 +25,7 @@ jobs:
- uses: actions/checkout@v2
- name: Salus Scan
id: salus_scan
uses: federacy/scan-action@0.1.4
uses: federacy/scan-action@0.1.5
env:
SALUS_CONFIGURATION: "file://salus-config.yaml"
with:
Expand Down
18 changes: 13 additions & 5 deletions pkg/cmd/analytics.go
Expand Up @@ -19,22 +19,26 @@ import (
"fmt"

"github.com/DopplerHQ/cli/pkg/configuration"
"github.com/DopplerHQ/cli/pkg/models"
"github.com/DopplerHQ/cli/pkg/printer"
"github.com/DopplerHQ/cli/pkg/utils"
"github.com/spf13/cobra"
)

var analyticsCmd = &cobra.Command{
Use: "analytics",
Short: "Manage anonymous analytics",
Args: cobra.NoArgs,
Use: "analytics",
Short: "Manage anonymous analytics",
Hidden: true,
Args: cobra.NoArgs,
}

var analyticsStatusCmd = &cobra.Command{
Use: "status",
Short: "Check whether anonymous analytics are enabled",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
deprecatedCommand("configure flags get analytics")

if utils.OutputJSON {
printer.JSON(map[string]bool{"enabled": configuration.IsAnalyticsEnabled()})
} else {
Expand All @@ -52,7 +56,9 @@ var analyticsEnableCmd = &cobra.Command{
Short: "Enable anonymous analytics",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
configuration.EnableAnalytics()
deprecatedCommand("configure flags enable analytics")

configuration.SetFlag(models.FlagAnalytics, true)

if utils.OutputJSON {
printer.JSON(map[string]bool{"enabled": true})
Expand All @@ -67,7 +73,9 @@ var analyticsDisableCmd = &cobra.Command{
Short: "Disable anonymous analytics",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
configuration.DisableAnalytics()
deprecatedCommand("configure flags disable analytics")

configuration.SetFlag(models.FlagAnalytics, false)

if utils.OutputJSON {
printer.JSON(map[string]bool{"enabled": false})
Expand Down
151 changes: 151 additions & 0 deletions pkg/cmd/configure_flags.go
@@ -0,0 +1,151 @@
/*
Copyright © 2023 Doppler <support@doppler.com>
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
"errors"
"fmt"

"github.com/DopplerHQ/cli/pkg/configuration"
"github.com/DopplerHQ/cli/pkg/models"
"github.com/DopplerHQ/cli/pkg/printer"
"github.com/DopplerHQ/cli/pkg/utils"
"github.com/spf13/cobra"
)

var configureFlagsCmd = &cobra.Command{
Use: "flags",
Short: "View current flags",
Args: cobra.NoArgs,
Run: func(cmd *cobra.Command, args []string) {
values := map[string]bool{}
flags := models.GetFlags()
for _, flag := range flags {
value := configuration.GetFlag(flag)
values[flag] = value
}

printer.Flags(values, utils.OutputJSON)
},
}

var configureFlagsGetCmd = &cobra.Command{
Use: "get [flag]",
Short: "Get the value of a flag",
ValidArgsFunction: FlagsValidArgs,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
plain := utils.GetBoolFlag(cmd, "plain")

flag := args[0]
if !configuration.IsValidFlag(flag) {
utils.HandleError(errors.New("invalid flag " + flag))
}

enabled := configuration.GetFlag(flag)

printer.Flag(flag, enabled, utils.OutputJSON, plain, false)
},
}

var configureFlagsEnableCmd = &cobra.Command{
Use: "enable [flag]",
Short: "Enable a flag",
ValidArgsFunction: FlagsValidArgs,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
flag := args[0]
if !configuration.IsValidFlag(flag) {
utils.HandleError(errors.New("invalid flag " + flag))
}

const value = true
configuration.SetFlag(flag, value)

if !utils.Silent {
printer.Flag(flag, value, utils.OutputJSON, false, false)
}
},
}

var configureFlagsDisableCmd = &cobra.Command{
Use: "disable [flag]",
Short: "Disable a flag",
ValidArgsFunction: FlagsValidArgs,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
flag := args[0]
if !configuration.IsValidFlag(flag) {
utils.HandleError(errors.New("invalid flag " + flag))
}

const value = false
configuration.SetFlag(flag, value)

if !utils.Silent {
printer.Flag(flag, value, utils.OutputJSON, false, false)
}
},
}

var configureFlagsResetCmd = &cobra.Command{
Use: "reset [flag]",
Short: "Reset a flag to its default",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {

flag := args[0]
if !configuration.IsValidFlag(flag) {
utils.HandleError(errors.New("invalid flag " + flag))
}

yes := utils.GetBoolFlag(cmd, "yes")
defaultValue := configuration.GetFlagDefault(flag)

if !yes {
utils.PrintWarning(fmt.Sprintf("This will reset the %s flag to %t", flag, defaultValue))
if !utils.ConfirmationPrompt("Continue?", false) {
utils.Log("Aborting")
return
}
}

configuration.SetFlag(flag, defaultValue)
printer.Flag(flag, defaultValue, utils.OutputJSON, false, false)
},
}

func FlagsValidArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
persistentValidArgsFunction(cmd)

return models.GetFlags(), cobra.ShellCompDirectiveNoFileComp
}

func init() {
configureCmd.AddCommand(configureFlagsCmd)

configureFlagsGetCmd.Flags().Bool("plain", false, "print value without formatting")
configureFlagsCmd.AddCommand(configureFlagsGetCmd)

configureFlagsCmd.AddCommand(configureFlagsEnableCmd)

configureFlagsCmd.AddCommand(configureFlagsDisableCmd)

configureFlagsResetCmd.Flags().BoolP("yes", "y", false, "proceed without confirmation")
configureFlagsCmd.AddCommand(configureFlagsResetCmd)

rootCmd.AddCommand(configureFlagsCmd)
}
30 changes: 19 additions & 11 deletions pkg/cmd/root.go
Expand Up @@ -24,6 +24,7 @@ import (
"github.com/DopplerHQ/cli/pkg/controllers"
"github.com/DopplerHQ/cli/pkg/global"
"github.com/DopplerHQ/cli/pkg/http"
"github.com/DopplerHQ/cli/pkg/models"
"github.com/DopplerHQ/cli/pkg/printer"
"github.com/DopplerHQ/cli/pkg/utils"
"github.com/DopplerHQ/cli/pkg/version"
Expand Down Expand Up @@ -61,6 +62,21 @@ var rootCmd = &cobra.Command{
// tty is required to accept user input, otherwise the update can't be accepted/declined
isTTY := isatty.IsTerminal(os.Stdout.Fd())

// version check
if !configuration.GetFlag(models.FlagUpdateCheck) {
version.PerformVersionCheck = false
}
if version.PerformVersionCheck && configuration.CanReadEnv {
enable := os.Getenv("DOPPLER_ENABLE_VERSION_CHECK")
if enable == "false" {
logValueFromEnvironmentNotice("DOPPLER_ENABLE_VERSION_CHECK")
version.PerformVersionCheck = false
}
}
if version.PerformVersionCheck {
version.PerformVersionCheck = !utils.GetBoolFlagIfChanged(cmd, "no-check-version", !version.PerformVersionCheck)
}

// only run version check if we can print the results
// --plain doesn't normally affect logging output, but due to legacy reasons it does here
// also don't want to display updates if user doesn't want to be prompted (--no-prompt/--no-interactive)
Expand All @@ -84,6 +100,7 @@ func persistentValidArgsFunction(cmd *cobra.Command) {
loadFlags(cmd)
}

// this function runs before the config file has been loaded, so flags will not be honored
func loadFlags(cmd *cobra.Command) {
var err error
var normalizedScope string
Expand All @@ -99,7 +116,8 @@ func loadFlags(cmd *cobra.Command) {
if configuration.CanReadEnv {
userConfigDir := os.Getenv("DOPPLER_CONFIG_DIR")
if userConfigDir != "" {
utils.Log(valueFromEnvironmentNotice("DOPPLER_CONFIG_DIR"))
// this warning will always be printed since the config file's flags haven't been loaded yet
logValueFromEnvironmentNotice("DOPPLER_CONFIG_DIR")
configuration.SetConfigDir(userConfigDir)
}
}
Expand All @@ -121,16 +139,6 @@ func loadFlags(cmd *cobra.Command) {

// no-file is used by the 'secrets download' command to output secrets to stdout
utils.Silent = utils.GetBoolFlagIfChanged(cmd, "no-file", utils.Silent)

// version check
if configuration.CanReadEnv {
enable := os.Getenv("DOPPLER_ENABLE_VERSION_CHECK")
if enable == "false" {
utils.Log(valueFromEnvironmentNotice("DOPPLER_ENABLE_VERSION_CHECK"))
version.PerformVersionCheck = false
}
}
version.PerformVersionCheck = !utils.GetBoolFlagIfChanged(cmd, "no-check-version", !version.PerformVersionCheck)
}

func deprecatedCommand(newCommand string) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cmd/run.go
Expand Up @@ -504,7 +504,7 @@ func getPassphrase(cmd *cobra.Command, flag string, config models.ScopedOptions)
if configuration.CanReadEnv {
passphrase := os.Getenv("DOPPLER_PASSPHRASE")
if passphrase != "" {
utils.Log(valueFromEnvironmentNotice("DOPPLER_PASSPHRASE"))
logValueFromEnvironmentNotice("DOPPLER_PASSPHRASE")
return passphrase
}
}
Expand Down
52 changes: 47 additions & 5 deletions pkg/cmd/setup.go
Expand Up @@ -53,7 +53,7 @@ func setup(cmd *cobra.Command, args []string) {
case models.FlagSource.String():
saveToken = true
case models.EnvironmentSource.String():
utils.Log(valueFromEnvironmentNotice("DOPPLER_TOKEN"))
logValueFromEnvironmentNotice("DOPPLER_TOKEN")
saveToken = true
}
}
Expand Down Expand Up @@ -94,7 +94,7 @@ func setup(cmd *cobra.Command, args []string) {
case models.FlagSource.String():
selectedProject = localConfig.EnclaveProject.Value
case models.EnvironmentSource.String():
utils.Log(valueFromEnvironmentNotice("DOPPLER_PROJECT"))
logValueFromEnvironmentNotice("DOPPLER_PROJECT")
selectedProject = localConfig.EnclaveProject.Value
default:
if useRepoConfig && repo.Project != "" {
Expand Down Expand Up @@ -129,7 +129,7 @@ func setup(cmd *cobra.Command, args []string) {
case models.FlagSource.String():
selectedConfig = localConfig.EnclaveConfig.Value
case models.EnvironmentSource.String():
utils.Log(valueFromEnvironmentNotice("DOPPLER_CONFIG"))
logValueFromEnvironmentNotice("DOPPLER_CONFIG")
selectedConfig = localConfig.EnclaveConfig.Value
default:
if useRepoConfig && repo.Config != "" {
Expand Down Expand Up @@ -177,6 +177,46 @@ func setup(cmd *cobra.Command, args []string) {
printer.ScopedConfigValues(conf, valuesToPrint, models.ScopedOptionsMap(&conf), utils.OutputJSON, false, false)
}
}

if repoConfig.Flags.Analytics != nil {
flag := models.FlagAnalytics
value := *repoConfig.Flags.Analytics

if utils.CanLogInfo() {
verb := "Enabling"
if !value {
verb = "Disabling"
}
utils.Log(fmt.Sprintf("%s %s", verb, flag))
}
configuration.SetFlag(flag, value)
}
if repoConfig.Flags.EnvWarning != nil {
flag := models.FlagEnvWarning
value := *repoConfig.Flags.EnvWarning

if utils.CanLogInfo() {
verb := "Enabling"
if !value {
verb = "Disabling"
}
utils.Log(fmt.Sprintf("%s %s", verb, flag))
}
configuration.SetFlag(flag, value)
}
if repoConfig.Flags.UpdateCheck != nil {
flag := models.FlagUpdateCheck
value := *repoConfig.Flags.UpdateCheck

if utils.CanLogInfo() {
verb := "Enabling"
if !value {
verb = "Disabling"
}
utils.Log(fmt.Sprintf("%s %s", verb, flag))
}
configuration.SetFlag(flag, value)
}
}

func selectProject(projects []models.ProjectInfo, prevConfiguredProject string, canPromptUser bool) string {
Expand Down Expand Up @@ -246,8 +286,10 @@ func selectConfig(configs []models.ConfigInfo, selectedConfiguredProject bool, p
return selectedConfig
}

func valueFromEnvironmentNotice(name string) string {
return fmt.Sprintf("Using %s from the environment. To disable this, use --no-read-env.", name)
func logValueFromEnvironmentNotice(name string) {
if configuration.GetFlag(models.FlagEnvWarning) {
utils.Log(fmt.Sprintf("Using %s from the environment. To disable this, use --no-read-env.", name))
}
}

// we're looking for duplicate paths and more than one repo being defined without a path.
Expand Down
12 changes: 12 additions & 0 deletions pkg/configuration/config.go
Expand Up @@ -403,6 +403,11 @@ func ClearConfig() {

// Write config to filesystem
func writeConfig(config models.ConfigFile) {
// keep both analytics properties up-to-date
if config.Flags.Analytics != nil {
config.Analytics.Disable = !*config.Flags.Analytics
}

bytes, err := yaml.Marshal(config)
if err != nil {
utils.HandleError(err)
Expand Down Expand Up @@ -481,6 +486,13 @@ func readConfig() (models.ConfigFile, int, int) {
}

config.Scoped = normalizedOptions

// support legacy analytics property when new property isn't set
if config.Flags.Analytics == nil && config.Analytics.Disable {
b := false
config.Flags.Analytics = &b
}

return config, uid, gid
}

Expand Down

0 comments on commit 2522b47

Please sign in to comment.