Skip to content

Commit

Permalink
Added force options for other prompt methods and made --force a glo…
Browse files Browse the repository at this point in the history
…bal flag.
  • Loading branch information
mitchell-as committed Nov 21, 2024
1 parent 4113b3e commit ee56104
Show file tree
Hide file tree
Showing 30 changed files with 161 additions and 180 deletions.
14 changes: 10 additions & 4 deletions cmd/state-installer/installer.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,17 @@ func (i *Installer) Install() (rerr error) {
if err != nil {
return errs.Wrap(err, "Could not determine if running as Windows administrator")
}
if isAdmin && !i.Params.force && !i.Params.isUpdate && !i.Params.nonInteractive {
prompter := prompt.New(true, i.an)
confirm, err := prompter.Confirm("", locale.T("installer_prompt_is_admin"), ptr.To(false), nil)
if isAdmin && !i.Params.isUpdate {
prompter := prompt.New(i.an)
if i.Params.nonInteractive {
prompter.SetInteractive(false)
}
if i.Params.force {
prompter.SetForce(true)
}
confirm, err := prompter.Confirm("", locale.T("installer_prompt_is_admin"), ptr.To(false), ptr.To(true))
if err != nil {
return errs.Wrap(err, "Unable to confirm")
return errs.Wrap(err, "Not confirmed")
}
if !confirm {
return locale.NewInputError("installer_aborted", "Installation aborted by the user")
Expand Down
4 changes: 2 additions & 2 deletions cmd/state-remote-installer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ func main() {
an = sync.New(anaConst.SrcStateRemoteInstaller, cfg, nil, out)

// Set up prompter
prompter := prompt.New(true, an)
prompter := prompt.New(an)

params := newParams()
cmd := captain.NewCommand(
Expand Down Expand Up @@ -176,7 +176,7 @@ func execute(out output.Outputer, prompt prompt.Prompter, cfg *config.Instance,
msg += locale.Tr("tos_disclaimer_prompt", constants.TermsOfServiceURLLatest)
cont, err := prompt.Confirm(locale.Tr("install_remote_title"), msg, ptr.To(true), nil)
if err != nil {
return errs.Wrap(err, "Could not prompt for confirmation")
return errs.Wrap(err, "Not confirmed")
}

if !cont {
Expand Down
27 changes: 8 additions & 19 deletions cmd/state/internal/cmdtree/clean.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,6 @@ func newCleanUninstallCommand(prime *primer.Values, globals *globalOptions) *cap
Description: locale.Tl("flag_state_clean_uninstall_all", "Also delete all associated config and cache files"),
Value: &params.All,
},
{
Name: "force",
Shorthand: "f",
Description: locale.T("flag_state_clean_uninstall_force_description"),
Value: &params.Force,
},
{
// This option is only used by the Windows uninstall shortcut to ask the user if they wish
// to delete everything or keep cache and config. The user is also asked to press Enter
Expand All @@ -62,8 +56,9 @@ func newCleanUninstallCommand(prime *primer.Values, globals *globalOptions) *cap
if globals.NonInteractive {
prime.Prompt().SetInteractive(false)
}
if params.Force {
prime.Prompt().EnableForce()
if globals.Force {
prime.Prompt().SetForce(true)
params.Force = true
}
return runner.Run(&params)
},
Expand Down Expand Up @@ -96,26 +91,20 @@ func newCleanCacheCommand(prime *primer.Values, globals *globalOptions) *captain
)
}

func newCleanConfigCommand(prime *primer.Values) *captain.Command {
func newCleanConfigCommand(prime *primer.Values, globals *globalOptions) *captain.Command {
runner := clean.NewConfig(prime)
params := clean.ConfigParams{}
return captain.NewCommand(
"config",
locale.Tl("clean_config_title", "Cleaning Configuration"),
locale.T("clean_config_description"),
prime,
[]*captain.Flag{
{
Name: "force",
Shorthand: "f",
Description: locale.T("flag_state_clean_config_force_description"),
Value: &params.Force,
},
},
[]*captain.Flag{},
[]*captain.Argument{},
func(ccmd *captain.Command, _ []string) error {
if params.Force {
prime.Prompt().EnableForce()
if globals.Force {
prime.Prompt().SetForce(true)
params.Force = true
}
return runner.Run(&params)
},
Expand Down
11 changes: 9 additions & 2 deletions cmd/state/internal/cmdtree/cmdtree.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ func New(prime *primer.Values, args ...string) *CmdTree {
cleanCmd.AddChildren(
newCleanUninstallCommand(prime, globals),
newCleanCacheCommand(prime, globals),
newCleanConfigCommand(prime),
newCleanConfigCommand(prime, globals),
)

deployCmd := newDeployCommand(prime)
Expand All @@ -95,7 +95,7 @@ func New(prime *primer.Values, args ...string) *CmdTree {
eventsCmd := newEventsCommand(prime)
eventsCmd.AddChildren(newEventsLogCommand(prime))

installCmd := newInstallCommand(prime)
installCmd := newInstallCommand(prime, globals)
uninstallCmd := newUninstallCommand(prime)
importCmd := newImportCommand(prime, globals)
searchCmd := newSearchCommand(prime)
Expand Down Expand Up @@ -234,6 +234,7 @@ type globalOptions struct {
Output string
Monochrome bool
NonInteractive bool
Force bool
}

// Group instances are used to group command help output.
Expand Down Expand Up @@ -303,6 +304,12 @@ func newStateCommand(globals *globalOptions, prime *primer.Values) *captain.Comm
Persist: true,
Value: &globals.NonInteractive,
},
{
Name: "force",
Description: locale.T("flag_state_force_description"),
Persist: true,
Value: &globals.Force,
},
{
Name: "version",
Description: locale.T("flag_state_version_description"),
Expand Down
12 changes: 3 additions & 9 deletions cmd/state/internal/cmdtree/packages.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,10 @@ func newPackagesCommand(prime *primer.Values) *captain.Command {
return cmd
}

func newInstallCommand(prime *primer.Values) *captain.Command {
func newInstallCommand(prime *primer.Values, globals *globalOptions) *captain.Command {
runner := install.New(prime, model.NamespacePackage)

params := install.Params{}
force := false

var packagesRaw string
cmd := captain.NewCommand(
Expand All @@ -68,11 +67,6 @@ func newInstallCommand(prime *primer.Values) *captain.Command {
Description: locale.T("package_flag_ts_description"),
Value: &params.Timestamp,
},
{
Name: "force",
Description: locale.Tl("package_flag_force_description", "Ignore security policy preventing packages with CVEs from being installed (not recommended)"),
Value: &force,
},
},
[]*captain.Argument{
{
Expand All @@ -88,8 +82,8 @@ func newInstallCommand(prime *primer.Values) *captain.Command {
return locale.WrapInputError(err, "err_install_packages_args", "Invalid install arguments")
}
}
if force {
prime.Prompt().EnableForce()
if globals.Force {
prime.Prompt().SetForce(true)
}
return runner.Run(params)
},
Expand Down
5 changes: 4 additions & 1 deletion cmd/state/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,10 @@ func run(args []string, isInteractive bool, cfg *config.Instance, out output.Out
}()

// Set up prompter
prompter := prompt.New(isInteractive, an)
prompter := prompt.New(an)
if !isInteractive {
prompter.SetInteractive(false)
}

// Set up conditional, which accesses a lot of primer data
sshell := subshell.New(cfg)
Expand Down
10 changes: 6 additions & 4 deletions internal/locale/locales/en-us.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ flag_state_output_description:
other: "Set the output method. Possible values: plain, simple, json, editor"
flag_state_non_interactive_description:
other: Run the State Tool without any prompts
flag_state_force_description:
other: Run the State Tool without any prompts, overriding any safe defaults
flag_state_version_description:
other: Show the version of our state executable
flag_state_activate_path_description:
Expand Down Expand Up @@ -755,12 +757,8 @@ cache_description:
other: Removes cached Runtime Environments
clean_config_description:
other: Removes global State Tool configuration. Project configuration will not be affected.
flag_state_clean_uninstall_force_description:
other: Run uninstall operation without prompts and ignoring any errors stopping running services
arg_state_clean_cache_project_description:
other: The project to be removed from the local cache.
flag_state_clean_config_force_description:
other: Run clean config operation without prompts and ignoring any errors stopping running services
err_uninstall_activated:
other: Cannot uninstall the State Tool while in an activated state. Please deactivate by entering [ACTIONABLE]exit[/RESET] or pressing [ACTIONABLE]Ctrl+D[/RESET] and then try again.
err_remove_cache:
Expand Down Expand Up @@ -1171,6 +1169,10 @@ prompt_continue_non_interactive:
other: "Continuing because State Tool is running in non-interactive mode."
prompt_abort_non_interactive:
other: "Aborting because State Tool is running in non-interactive mode. To bypass you can use the '[ACTIONABLE]--force[/RESET]' flag."
prompt_using_force:
other: "Using '[ACTIONABLE]{{.V0}}[/RESET]' because the '[ACTIONABLE]--force[/RESET]' flag is set."
prompt_using_non_interactive:
other: "Using '[ACTIONABLE]{{.V0}}[/RESET]' because State Tool is running in non-interactive mode."
unstable_command_warning:
other: |
This command is still in beta. If you want to opt-in to unstable features, run the following command:
Expand Down
79 changes: 54 additions & 25 deletions internal/prompt/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"github.com/ActiveState/cli/internal/analytics/constants"
"github.com/ActiveState/cli/internal/errs"
"github.com/ActiveState/cli/internal/locale"
"github.com/ActiveState/cli/internal/logging"
"github.com/ActiveState/cli/internal/output"
)

Expand All @@ -21,15 +20,14 @@ type EventDispatcher interface {

// Prompter is the interface used to run our prompt from
type Prompter interface {
Input(title, message string, defaultResponse *string, flags ...ValidatorFlag) (string, error)
InputAndValidate(title, message string, defaultResponse *string, validator ValidatorFunc, flags ...ValidatorFlag) (string, error)
Select(title, message string, choices []string, defaultResponse *string) (string, error)
Input(title, message string, defaultResponse *string, forcedResponse *string, flags ...ValidatorFlag) (string, error)
InputAndValidate(title, message string, defaultResponse *string, forcedResponse *string, validator ValidatorFunc, flags ...ValidatorFlag) (string, error)
Select(title, message string, choices []string, defaultResponse *string, forcedResponse *string) (string, error)
Confirm(title, message string, defaultChoice *bool, forcedChoice *bool) (bool, error)
InputSecret(title, message string, flags ...ValidatorFlag) (string, error)
IsInteractive() bool
SetInteractive(bool)
EnableForce()
IsForceEnabled() bool
SetForce(bool)
}

// ValidatorFunc is a function pass to the Prompter to perform validation
Expand All @@ -47,8 +45,8 @@ type Prompt struct {
}

// New creates a new prompter
func New(isInteractive bool, an EventDispatcher) Prompter {
return &Prompt{output.Get(), an, isInteractive, false}
func New(an EventDispatcher) Prompter {
return &Prompt{output.Get(), an, true, false}
}

// IsInteractive checks if the prompts can be interactive or should just return default values
Expand All @@ -60,13 +58,13 @@ func (p *Prompt) SetInteractive(interactive bool) {
p.isInteractive = interactive
}

// EnableForce forces confirm prompts to return the force value (which is often different from the
// SetForce enables prompts to return the force value (which is often different from the
// non-interactive value).
func (p *Prompt) EnableForce() {
p.isForced = true
func (p *Prompt) SetForce(force bool) {
p.isForced = force
}

func (p *Prompt) IsForceEnabled() bool {
func (p *Prompt) IsForced() bool {
return p.isForced
}

Expand All @@ -82,8 +80,8 @@ const (
)

// Input prompts the user for input. The user can specify available validation flags to trigger validation of responses
func (p *Prompt) Input(title, message string, defaultResponse *string, flags ...ValidatorFlag) (string, error) {
return p.InputAndValidate(title, message, defaultResponse, func(val interface{}) error {
func (p *Prompt) Input(title, message string, defaultResponse *string, forcedResponse *string, flags ...ValidatorFlag) (string, error) {
return p.InputAndValidate(title, message, defaultResponse, forcedResponse, func(val interface{}) error {
return nil
}, flags...)
}
Expand All @@ -100,10 +98,22 @@ func interactiveInputError(message string) error {
}

// InputAndValidate prompts an input field and allows you to specfiy a custom validation function as well as the built in flags
func (p *Prompt) InputAndValidate(title, message string, defaultResponse *string, validator ValidatorFunc, flags ...ValidatorFlag) (string, error) {
func (p *Prompt) InputAndValidate(title, message string, defaultResponse *string, forcedResponse *string, validator ValidatorFunc, flags ...ValidatorFlag) (string, error) {
if p.isForced {
response := forcedResponse
if response == nil {
response = defaultResponse
}
if response != nil {
p.out.Notice(locale.Tr("prompt_using_force", *response))
return *response, nil
}
return "", errs.New("No force option given for forced prompt")
}

if !p.isInteractive {
if defaultResponse != nil {
logging.Debug("Selecting default choice %s for Input prompt %s in non-interactive mode", *defaultResponse, title)
p.out.Notice(locale.Tr("prompt_using_non_interactive", *defaultResponse))
return *defaultResponse, nil
}
return "", interactiveInputError(message)
Expand All @@ -124,7 +134,7 @@ func (p *Prompt) InputAndValidate(title, message string, defaultResponse *string

// We handle defaults more clearly than the survey package can
if defaultResponse != nil && *defaultResponse != "" {
v, err := p.Select("", formatMessage(message, !p.out.Config().Colored), []string{*defaultResponse, locale.Tl("prompt_custom", "Other ..")}, defaultResponse)
v, err := p.Select("", formatMessage(message, !p.out.Config().Colored), []string{*defaultResponse, locale.Tl("prompt_custom", "Other ..")}, defaultResponse, forcedResponse)
if err != nil {
return "", err
}
Expand All @@ -145,10 +155,22 @@ func (p *Prompt) InputAndValidate(title, message string, defaultResponse *string
}

// Select prompts the user to select one entry from multiple choices
func (p *Prompt) Select(title, message string, choices []string, defaultChoice *string) (string, error) {
func (p *Prompt) Select(title, message string, choices []string, defaultChoice *string, forcedChoice *string) (string, error) {
if p.isForced {
choice := forcedChoice
if choice == nil {
choice = defaultChoice
}
if choice != nil {
p.out.Notice(locale.Tr("prompt_using_force", *choice))
return *choice, nil
}
return "", errs.New("No force option given for forced prompt")
}

if !p.isInteractive {
if defaultChoice != nil {
logging.Debug("Selecting default choice %s for Select prompt %s in non-interactive mode", *defaultChoice, title)
p.out.Notice(locale.Tr("prompt_using_non_interactive", *defaultChoice))
return *defaultChoice, nil
}
return "", interactiveInputError(message)
Expand Down Expand Up @@ -179,17 +201,24 @@ func (p *Prompt) Select(title, message string, choices []string, defaultChoice *
// Confirm prompts user for yes or no response.
func (p *Prompt) Confirm(title, message string, defaultChoice *bool, forcedChoice *bool) (bool, error) {
if p.isForced {
if forcedChoice == nil {
return false, errs.New("No force option given for force-enabled prompt")
choice := forcedChoice
if choice == nil {
choice = defaultChoice
}
logging.Debug("Prompt %s confirmed with choice %v in force mode", title, forcedChoice)
return *forcedChoice, nil
if choice != nil {
p.out.Notice(locale.T("prompt_continue_force"))
return *choice, nil
}
return false, errs.New("No force option given for forced prompt")
}

if !p.isInteractive {
if defaultChoice != nil {
logging.Debug("Prompt %s confirmed with default choice %v in non-interactive mode", title, defaultChoice)
return *defaultChoice, nil
if *defaultChoice {
p.out.Notice(locale.T("prompt_continue_non_interactive"))
return true, nil
}
return false, locale.NewInputError("prompt_abort_non_interactive")
}
return false, interactiveInputError(message)
}
Expand Down
6 changes: 3 additions & 3 deletions internal/runbits/auth/keypair.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,12 @@ func promptForPreviousPassphrase(prompt prompt.Prompter) (string, error) {
return passphrase, nil
}

func promptUserToRegenerateKeypair(passphrase string, cfg keypairs.Configurable, out output.Outputer, prmpt prompt.Prompter, auth *authentication.Auth) error {
func promptUserToRegenerateKeypair(passphrase string, cfg keypairs.Configurable, out output.Outputer, prompt prompt.Prompter, auth *authentication.Auth) error {
// previous passphrase is invalid, inform user and ask if they want to generate a new keypair
out.Notice(locale.T("auth_generate_new_keypair_message"))
yes, err := prmpt.Confirm("", locale.T("auth_confirm_generate_new_keypair_prompt"), ptr.To(false), nil)
yes, err := prompt.Confirm("", locale.T("auth_confirm_generate_new_keypair_prompt"), ptr.To(false), nil)
if err != nil {
return errs.Wrap(err, "Unable to confirm")
return errs.Wrap(err, "Not confirmed")
}
if !yes {
return locale.NewInputError("auth_err_unrecoverable_keypair")
Expand Down
Loading

0 comments on commit ee56104

Please sign in to comment.