diff --git a/cmd/cloud/clicontext/clicontext.go b/cmd/cloud/clicontext/clicontext.go index d3450e21b..a57ea4c7c 100644 --- a/cmd/cloud/clicontext/clicontext.go +++ b/cmd/cloud/clicontext/clicontext.go @@ -12,11 +12,12 @@ import ( type ContextKeyServerURL struct{} type CLIContext struct { - AuthData *auth.AuthorizationResponse `json:"auth_data"` - ClientID string `json:"client_id"` - OrgURL string `json:"org_url"` - ServerURL string `json:"server_url"` - Alias string `json:"alias"` + AuthData *auth.AuthorizationResponse `json:"auth_data"` + ClientID string `json:"client_id"` + OrgURL string `json:"org_url"` + ServerURL string `json:"server_url"` + Alias string `json:"alias"` + ConfirmationRequired bool `json:"confirmation_required"` } type Contexts struct { @@ -42,13 +43,14 @@ func (c *Contexts) Get(contextName string) *CLIContext { return &context } -func (c *Contexts) UpdateContext(contextName string, authData *auth.AuthorizationResponse, clientID, orgURL, alias, serverURL string) error { +func (c *Contexts) UpdateContext(contextName string, authData *auth.AuthorizationResponse, clientID, orgURL, alias, serverURL string, confirmationRequired bool) error { c.Contexts[contextName] = CLIContext{ - AuthData: authData, - ClientID: clientID, - OrgURL: orgURL, - Alias: alias, - ServerURL: serverURL, + AuthData: authData, + ClientID: clientID, + OrgURL: orgURL, + Alias: alias, + ServerURL: serverURL, + ConfirmationRequired: confirmationRequired, } return WriteContexts(c) diff --git a/cmd/cloud/contexts.go b/cmd/cloud/contexts.go index 9ebf73967..01b156e34 100644 --- a/cmd/cloud/contexts.go +++ b/cmd/cloud/contexts.go @@ -82,8 +82,9 @@ func newCmdContextGet() *cobra.Command { type updateContextFlags struct { createContextFlags - Context string - ClearAuth bool + Context string + ClearAuth bool + ConfirmationRequired bool } func (f *updateContextFlags) addFlags(command *cobra.Command) { @@ -91,6 +92,7 @@ func (f *updateContextFlags) addFlags(command *cobra.Command) { command.Flags().StringVar(&f.Context, "context", "", "Name of the context to update") command.MarkFlagRequired("context") command.Flags().BoolVar(&f.ClearAuth, "clear-auth", false, "Turns off authentication for this context") + command.Flags().BoolVar(&f.ConfirmationRequired, "confirmation-required", false, "Require confirmation for commands run in this context") } func newCmdContextUpdate() *cobra.Command { @@ -107,6 +109,7 @@ func newCmdContextUpdate() *cobra.Command { Alias := flags.Alias clearAuth := flags.ClearAuth contextName := flags.Context + confirmationRequired := flags.ConfirmationRequired contexts, err := clicontext.ReadContexts() if err != nil { @@ -135,6 +138,10 @@ func newCmdContextUpdate() *cobra.Command { context.Alias = Alias } + if confirmationRequired != context.ConfirmationRequired { + context.ConfirmationRequired = confirmationRequired + } + if !skipAuth && !clearAuth && clientID != "" && orgURL != "" { login, err := auth.Login(command.Context(), orgURL, clientID) if err != nil { @@ -163,11 +170,12 @@ func newCmdContextUpdate() *cobra.Command { type createContextFlags struct { clusterFlags - ClientID string - OrgURL string - SkipAuth bool - Alias string - ServerURL string + ClientID string + OrgURL string + SkipAuth bool + Alias string + ServerURL string + ConfirmationRequired bool } func (f *createContextFlags) addFlags(command *cobra.Command) { @@ -189,6 +197,7 @@ func newCmdContextCreate() *cobra.Command { orgURL := flags.OrgURL skipAuth := flags.SkipAuth Alias := flags.Alias + confirmationRequired := flags.ConfirmationRequired contextName := "" if Alias != "" { @@ -217,11 +226,12 @@ func newCmdContextCreate() *cobra.Command { } contexts.Contexts[contextName] = clicontext.CLIContext{ - ClientID: clientID, - OrgURL: orgURL, - AuthData: authData, - Alias: Alias, - ServerURL: serverAddress, + ClientID: clientID, + OrgURL: orgURL, + AuthData: authData, + Alias: Alias, + ServerURL: serverAddress, + ConfirmationRequired: confirmationRequired, } contexts.CurrentContext = contextName diff --git a/cmd/cloud/login.go b/cmd/cloud/login.go index e1e92afd2..d691e9cba 100644 --- a/cmd/cloud/login.go +++ b/cmd/cloud/login.go @@ -52,7 +52,7 @@ func newCmdLogin() *cobra.Command { context.AuthData = &login - err = contexts.UpdateContext(contextName, context.AuthData, context.ClientID, context.OrgURL, context.Alias, context.ServerURL) + err = contexts.UpdateContext(contextName, context.AuthData, context.ClientID, context.OrgURL, context.Alias, context.ServerURL, context.ConfirmationRequired) return err }, } diff --git a/cmd/cloud/main.go b/cmd/cloud/main.go index 8e779321a..fffd03f8c 100644 --- a/cmd/cloud/main.go +++ b/cmd/cloud/main.go @@ -6,7 +6,9 @@ package main import ( + "bufio" "context" + "fmt" "os" "strings" @@ -25,6 +27,18 @@ func isCommandThatSkipsAuth(cmd *cobra.Command) bool { return cmd.Use == "migrate" || cmd.Use == "server" || cmd.Use == "login" || cmd.Parent().Use == "contexts" } +func askConfirmation(contextURL string) bool { + reader := bufio.NewReader(os.Stdin) + fmt.Printf("Commands against this context (URL: %s) require confirmation. Do you want to proceed? (Y/N): ", contextURL) + response, err := reader.ReadString('\n') + if err != nil { + logger.WithError(err).Fatal("Failed to read user input.") + return false + } + response = strings.TrimSpace(strings.ToLower(response)) + return response == "y" || response == "yes" +} + // TODO: Add support for --context flag to all commands that can use a context different from current one var rootCmd = &cobra.Command{ Use: "cloud", @@ -75,11 +89,21 @@ var rootCmd = &cobra.Command{ } // Update disk copy of context with new auth data if any - err = contexts.UpdateContext(currentContext.Alias, authData, currentContext.ClientID, currentContext.OrgURL, currentContext.Alias, currentContext.ServerURL) + err = contexts.UpdateContext(contexts.CurrentContext, authData, currentContext.ClientID, currentContext.OrgURL, currentContext.Alias, currentContext.ServerURL, currentContext.ConfirmationRequired) if err != nil { logger.WithError(err).Fatal("Failed to update context with new auth data.") } cmd.SetContext(context.WithValue(cmd.Context(), clicontext.ContextKeyServerURL{}, currentContext.ServerURL)) + + skipConfirmation, _ := cmd.Flags().GetBool("y") + if currentContext.ConfirmationRequired && !skipConfirmation { + if !askConfirmation(currentContext.ServerURL) { + logger.Fatal("Confirmation required to proceed.") + return + } + } else if strings.Contains(currentContext.ServerURL, "prod") { + logger.Warn("\"Prod\" detected in server URL. Consider requiring confirmation on this context. Proceed with caution.") + } } }, RunE: func(cmd *cobra.Command, args []string) error { @@ -101,6 +125,7 @@ func init() { _ = rootCmd.MarkFlagRequired("database") + rootCmd.PersistentFlags().BoolVarP(new(bool), "y", "y", false, "Skip confirmation prompts") rootCmd.PersistentFlags().String("context", viper.GetString("context"), "Override the current context") rootCmd.AddCommand(newCmdServer())