Skip to content

Add --yes flag to skip prompts on cluster CLI commands #980

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 36 additions & 21 deletions cli/cmd/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ import (
)

var (
_flagClusterEnv string
_flagClusterConfig string
_flagClusterInfoDebug bool
_flagClusterEnv string
_flagClusterConfig string
_flagClusterInfoDebug bool
_flagClusterDisallowPrompt bool
)

func clusterInit() {
Expand All @@ -55,21 +56,25 @@ func clusterInit() {
_upCmd.Flags().SortFlags = false
addClusterConfigFlag(_upCmd)
_upCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to configure")
_upCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts")
_clusterCmd.AddCommand(_upCmd)

_infoCmd.Flags().SortFlags = false
addClusterConfigFlag(_infoCmd)
_infoCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to configure")
_infoCmd.Flags().BoolVarP(&_flagClusterInfoDebug, "debug", "d", false, "save the current cluster state to a file")
_infoCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts")
_clusterCmd.AddCommand(_infoCmd)

_updateCmd.Flags().SortFlags = false
addClusterConfigFlag(_updateCmd)
_updateCmd.Flags().StringVarP(&_flagClusterEnv, "env", "e", defaultEnv, "environment to configure")
_updateCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts")
_clusterCmd.AddCommand(_updateCmd)

_downCmd.Flags().SortFlags = false
addClusterConfigFlag(_downCmd)
_downCmd.Flags().BoolVarP(&_flagClusterDisallowPrompt, "yes", "y", false, "skip prompts")
_clusterCmd.AddCommand(_downCmd)
}

Expand Down Expand Up @@ -98,13 +103,16 @@ var _upCmd = &cobra.Command{
exit.Error(err)
}

promptForEmail()
awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv)
if !_flagClusterDisallowPrompt {
promptForEmail()
}

awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}

clusterConfig, err := getInstallClusterConfig(awsCreds, _flagClusterEnv)
clusterConfig, err := getInstallClusterConfig(awsCreds, _flagClusterEnv, _flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}
Expand Down Expand Up @@ -170,12 +178,12 @@ var _updateCmd = &cobra.Command{
exit.Error(err)
}

awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv)
awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}

accessConfig, err := getClusterAccessConfig()
accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}
Expand All @@ -198,9 +206,9 @@ var _updateCmd = &cobra.Command{
exit.Error(err)
}

cachedClusterConfig := refreshCachedClusterConfig(awsCreds)
cachedClusterConfig := refreshCachedClusterConfig(awsCreds, _flagClusterDisallowPrompt)

clusterConfig, err := getClusterUpdateConfig(cachedClusterConfig, awsCreds)
clusterConfig, err := getClusterUpdateConfig(cachedClusterConfig, awsCreds, _flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}
Expand Down Expand Up @@ -232,20 +240,20 @@ var _infoCmd = &cobra.Command{
exit.Error(err)
}

awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv)
awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}

accessConfig, err := getClusterAccessConfig()
accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}

if _flagClusterInfoDebug {
cmdDebug(awsCreds, accessConfig)
} else {
cmdInfo(awsCreds, accessConfig)
cmdInfo(awsCreds, accessConfig, _flagClusterDisallowPrompt)
}
},
}
Expand All @@ -261,12 +269,12 @@ var _downCmd = &cobra.Command{
exit.Error(err)
}

awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv)
awsCreds, err := getAWSCredentials(_flagClusterConfig, _flagClusterEnv, _flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}

accessConfig, err := getClusterAccessConfig()
accessConfig, err := getClusterAccessConfig(_flagClusterDisallowPrompt)
if err != nil {
exit.Error(err)
}
Expand All @@ -293,7 +301,9 @@ var _downCmd = &cobra.Command{
exit.Error(ErrorClusterAlreadyDeleted(*accessConfig.ClusterName, *accessConfig.Region))
}

prompt.YesOrExit(fmt.Sprintf("your cluster (%s in %s) will be spun down and all apis will be deleted, are you sure you want to continue?", *accessConfig.ClusterName, *accessConfig.Region), "", "")
if !_flagClusterDisallowPrompt {
prompt.YesOrExit(fmt.Sprintf("your cluster (%s in %s) will be spun down and all apis will be deleted, are you sure you want to continue?", *accessConfig.ClusterName, *accessConfig.Region), "", "")
}

out, exitCode, err := runManagerAccessCommand("/root/uninstall.sh", *accessConfig, awsCreds, _flagClusterEnv)
if err != nil {
Expand Down Expand Up @@ -353,7 +363,7 @@ func promptForEmail() {
}
}

func cmdInfo(awsCreds AWSCredentials, accessConfig *clusterconfig.AccessConfig) {
func cmdInfo(awsCreds AWSCredentials, accessConfig *clusterconfig.AccessConfig, disallowPrompt bool) {
awsClient, err := newAWSClient(*accessConfig.Region, awsCreds)
if err != nil {
exit.Error(err)
Expand All @@ -378,7 +388,7 @@ func cmdInfo(awsCreds AWSCredentials, accessConfig *clusterconfig.AccessConfig)
exit.Error(err)
}

clusterConfig := refreshCachedClusterConfig(awsCreds)
clusterConfig := refreshCachedClusterConfig(awsCreds, disallowPrompt)

out, exitCode, err := runManagerAccessCommand("/root/info.sh", *accessConfig, awsCreds, _flagClusterEnv)
if err != nil {
Expand Down Expand Up @@ -440,7 +450,12 @@ func cmdInfo(awsCreds AWSCredentials, accessConfig *clusterconfig.AccessConfig)
} else if *prevEnv.OperatorEndpoint != operatorConfig.OperatorEndpoint || *prevEnv.AWSAccessKeyID != operatorConfig.AWSAccessKeyID || *prevEnv.AWSSecretAccessKey != operatorConfig.AWSSecretAccessKey {
fmt.Println()
fmt.Println(newEnvironment.String(false))
shouldWriteEnv = prompt.YesOrNo(fmt.Sprintf("found an existing environment named \"%s\"; would you like to overwrite it with the configuration above?", _flagClusterEnv), "", "")
if disallowPrompt {
fmt.Print(fmt.Sprintf("found an existing environment named \"%s\"; overwriting it with the configuration above\n\n", _flagClusterEnv))
shouldWriteEnv = true
} else {
shouldWriteEnv = prompt.YesOrNo(fmt.Sprintf("found an existing environment named \"%s\"; would you like to overwrite it with the configuration above?", _flagClusterEnv), "", "")
}
}

if shouldWriteEnv {
Expand Down Expand Up @@ -473,8 +488,8 @@ func cmdDebug(awsCreds AWSCredentials, accessConfig *clusterconfig.AccessConfig)
return
}

func refreshCachedClusterConfig(awsCreds AWSCredentials) clusterconfig.Config {
accessConfig, err := getClusterAccessConfig()
func refreshCachedClusterConfig(awsCreds AWSCredentials, disallowPrompt bool) clusterconfig.Config {
accessConfig, err := getClusterAccessConfig(disallowPrompt)
if err != nil {
exit.Error(err)
}
Expand Down
14 changes: 7 additions & 7 deletions cli/cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,16 @@ var (
_warningProjectBytes = 1024 * 1024 * 10
_warningFileCount = 1000

_flagDeployEnv string
_flagDeployForce bool
_flagDeployYes bool
_flagDeployEnv string
_flagDeployForce bool
_flagDeployDisallowPrompt bool
)

func deployInit() {
_deployCmd.Flags().SortFlags = false
_deployCmd.Flags().StringVarP(&_flagDeployEnv, "env", "e", getDefaultEnv(_generalCommandType), "environment to use")
_deployCmd.Flags().BoolVarP(&_flagDeployForce, "force", "f", false, "override the in-progress api update")
_deployCmd.Flags().BoolVarP(&_flagDeployYes, "yes", "y", false, "skip prompts")
_deployCmd.Flags().BoolVarP(&_flagDeployDisallowPrompt, "yes", "y", false, "skip prompts")
}

var _deployCmd = &cobra.Command{
Expand Down Expand Up @@ -140,7 +140,7 @@ func findProjectFiles(provider types.ProviderType, configPath string) ([]string,
ignoreFns = append(ignoreFns, cortexIgnore)
}

if !_flagDeployYes && provider != types.LocalProviderType {
if !_flagDeployDisallowPrompt && provider != types.LocalProviderType {
ignoreFns = append(ignoreFns, files.PromptForFilesAboveSize(_warningFileBytes, "do you want to upload %s (%s)?"))
}

Expand Down Expand Up @@ -176,7 +176,7 @@ func getDeploymentBytes(provider types.ProviderType, configPath string) (map[str
}

didPromptFileCount := false
if !_flagDeployYes && len(projectPaths) >= _warningFileCount {
if !_flagDeployDisallowPrompt && len(projectPaths) >= _warningFileCount {
msg := fmt.Sprintf("cortex will zip %d files in %s and upload them to the cluster; we recommend that you upload large files/directories (e.g. models) to s3 and download them in your api's __init__ function, and avoid sending unnecessary files by removing them from this directory or referencing them in a .cortexignore file. Would you like to continue?", len(projectPaths), rootDirMsg)
prompt.YesOrExit(msg, canSkipPromptMsg, "")
didPromptFileCount = true
Expand All @@ -194,7 +194,7 @@ func getDeploymentBytes(provider types.ProviderType, configPath string) (map[str
return nil, errors.Wrap(err, "failed to zip project folder")
}

if !_flagDeployYes && !didPromptFileCount && len(projectZipBytes) >= _warningProjectBytes {
if !_flagDeployDisallowPrompt && !didPromptFileCount && len(projectZipBytes) >= _warningProjectBytes {
msg := fmt.Sprintf("cortex will zip %d files in %s (%s) and upload them to the cluster, though we recommend you upload large files (e.g. models) to s3 and download them in your api's __init__ function. Would you like to continue?", len(projectPaths), rootDirMsg, s.IntToBase2Byte(len(projectZipBytes)))
prompt.YesOrExit(msg, canSkipPromptMsg, "")
}
Expand Down
81 changes: 53 additions & 28 deletions cli/cmd/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
s "github.com/cortexlabs/cortex/pkg/lib/strings"
"github.com/cortexlabs/cortex/pkg/lib/urls"
"github.com/cortexlabs/cortex/pkg/types"
"github.com/cortexlabs/cortex/pkg/types/clusterconfig"
"github.com/cortexlabs/cortex/pkg/types/clusterstate"
)

Expand All @@ -43,34 +44,37 @@ func getCloudFormationURL(clusterName, region string) string {
}

const (
ErrInvalidProvider = "cli.invalid_provider"
ErrNotSupportedInLocalEnvironment = "cli.not_supported_in_local_environment"
ErrEnvironmentNotFound = "cli.environment_not_found"
ErrOperatorEndpointInLocalEnvironment = "cli.operator_endpoint_in_local_environment"
ErrOperatorConfigFromLocalEnvironment = "cli.operater_config_from_local_environment"
ErrFieldNotFoundInEnvironment = "cli.err_field_not_found_in_environment"
ErrInvalidOperatorEndpoint = "cli.invalid_operator_endpoint"
ErrCortexYAMLNotFound = "cli.cortex_yaml_not_found"
ErrConnectToDockerDaemon = "cli.connect_to_docker_daemon"
ErrDockerPermissions = "cli.docker_permissions"
ErrDockerCtrlC = "cli.docker_ctrl_c"
ErrResponseUnknown = "cli.response_unknown"
ErrAPINotReady = "cli.api_not_ready"
ErrOneAWSEnvVarSet = "cli.one_aws_env_var_set"
ErrOneAWSConfigFieldSet = "cli.one_aws_config_field_set"
ErrClusterUp = "cli.cluster_up"
ErrClusterUpdate = "cli.cluster_update"
ErrClusterInfo = "cli.cluster_info"
ErrClusterDebug = "cli.cluster_debug"
ErrClusterRefresh = "cli.cluster_refresh"
ErrClusterDown = "cli.cluster_down"
ErrDuplicateCLIEnvNames = "cli.duplicate_cli_env_names"
ErrClusterUpInProgress = "cli.cluster_up_in_progress"
ErrClusterAlreadyCreated = "cli.cluster_already_created"
ErrClusterDownInProgress = "cli.cluster_down_in_progress"
ErrClusterAlreadyDeleted = "cli.cluster_already_deleted"
ErrFailedClusterStatus = "cli.failed_cluster_status"
ErrClusterDoesNotExist = "cli.cluster_does_not_exist"
ErrInvalidProvider = "cli.invalid_provider"
ErrNotSupportedInLocalEnvironment = "cli.not_supported_in_local_environment"
ErrEnvironmentNotFound = "cli.environment_not_found"
ErrOperatorEndpointInLocalEnvironment = "cli.operator_endpoint_in_local_environment"
ErrOperatorConfigFromLocalEnvironment = "cli.operater_config_from_local_environment"
ErrFieldNotFoundInEnvironment = "cli.err_field_not_found_in_environment"
ErrInvalidOperatorEndpoint = "cli.invalid_operator_endpoint"
ErrCortexYAMLNotFound = "cli.cortex_yaml_not_found"
ErrConnectToDockerDaemon = "cli.connect_to_docker_daemon"
ErrDockerPermissions = "cli.docker_permissions"
ErrDockerCtrlC = "cli.docker_ctrl_c"
ErrResponseUnknown = "cli.response_unknown"
ErrAPINotReady = "cli.api_not_ready"
ErrOneAWSEnvVarSet = "cli.one_aws_env_var_set"
ErrOneAWSConfigFieldSet = "cli.one_aws_config_field_set"
ErrClusterUp = "cli.cluster_up"
ErrClusterUpdate = "cli.cluster_update"
ErrClusterInfo = "cli.cluster_info"
ErrClusterDebug = "cli.cluster_debug"
ErrClusterRefresh = "cli.cluster_refresh"
ErrClusterDown = "cli.cluster_down"
ErrDuplicateCLIEnvNames = "cli.duplicate_cli_env_names"
ErrClusterUpInProgress = "cli.cluster_up_in_progress"
ErrClusterAlreadyCreated = "cli.cluster_already_created"
ErrClusterDownInProgress = "cli.cluster_down_in_progress"
ErrClusterAlreadyDeleted = "cli.cluster_already_deleted"
ErrFailedClusterStatus = "cli.failed_cluster_status"
ErrClusterDoesNotExist = "cli.cluster_does_not_exist"
ErrAWSCredentialsRequired = "cli.aws_credentials_required"
ErrClusterConfigOrPromptsRequired = "cli.cluster_config_or_prompts_required"
ErrClusterAccessConfigOrPromptsRequired = "cli.cluster_access_config_or_prompts_required"
)

func ErrorInvalidProvider(providerStr string) error {
Expand Down Expand Up @@ -287,3 +291,24 @@ func ErrorFailedClusterStatus(status clusterstate.Status, clusterName string, re
Message: fmt.Sprintf("cluster \"%s\" in %s encountered an unexpected status %s, please try to delete the cluster with `cortex cluster down` or delete the cloudformation stacks manually in your AWS console %s", clusterName, region, string(status), getCloudFormationURL(clusterName, region)),
})
}

func ErrorAWSCredentialsRequired() error {
return errors.WithStack(&errors.Error{
Kind: ErrAWSCredentialsRequired,
Message: "AWS credentials are required; please set them in your cluster configuration file (if you're using one), your environment variables (i.e. AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY), or your AWS CLI (i.e. via `aws configure`)",
})
}

func ErrorClusterConfigOrPromptsRequired() error {
return errors.WithStack(&errors.Error{
Kind: ErrClusterConfigOrPromptsRequired,
Message: "this command requires either a cluster configuration file (e.g. `--config=cluster.yaml`) or prompts to be enabled (i.e. omit the `--yes` flag)",
})
}

func ErrorClusterAccessConfigOrPromptsRequired() error {
return errors.WithStack(&errors.Error{
Kind: ErrClusterAccessConfigOrPromptsRequired,
Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s` and `%s` (e.g. `--config=cluster.yaml`) or enable prompts (i.e. omit the `--yes` flag)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey),
})
}
18 changes: 13 additions & 5 deletions cli/cmd/lib_aws_creds.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,19 @@ func newAWSClient(region string, awsCreds AWSCredentials) (*aws.Client, error) {
return awsClient, nil
}

func promptIfNotAdmin(awsClient *aws.Client) {
func promptIfNotAdmin(awsClient *aws.Client, disallowPrompt bool) {
accessKeyMsg := ""
if accessKey := awsClient.AccessKeyID(); accessKey != nil {
accessKeyMsg = fmt.Sprintf(" (with access key %s)", *accessKey)
}

if !awsClient.IsAdmin() {
prompt.YesOrExit(fmt.Sprintf("warning: your IAM user%s does not have administrator access. This will likely prevent Cortex from installing correctly, so it is recommended to attach the AdministratorAccess policy to your IAM user (or to a group that your IAM user belongs to) via the AWS IAM console. If you'd like, you may provide separate credentials for your cluster to use after it's running (see https://cortex.dev/cluster-management/security for instructions).\n\nare you sure you want to continue without administrator access?", accessKeyMsg), "", "")
warningStr := fmt.Sprintf("warning: your IAM user%s does not have administrator access. This will likely prevent Cortex from installing correctly, so it is recommended to attach the AdministratorAccess policy to your IAM user (or to a group that your IAM user belongs to) via the AWS IAM console. If you'd like, you may provide separate credentials for your cluster to use after it's running (see https://cortex.dev/cluster-management/security for instructions).\n\n", accessKeyMsg)
if disallowPrompt {
fmt.Print(warningStr)
} else {
prompt.YesOrExit(warningStr+"are you sure you want to continue without administrator access?", "", "")
}
}
}

Expand Down Expand Up @@ -138,7 +143,7 @@ func readAWSCredsFromConfigFile(awsCreds *AWSCredentials, path string) error {
}

// awsCreds is what was read from the cluster config YAML
func setInstallAWSCredentials(awsCreds *AWSCredentials, envName string) error {
func setInstallAWSCredentials(awsCreds *AWSCredentials, envName string, disallowPrompt bool) error {
// First check env vars
if os.Getenv("AWS_SESSION_TOKEN") != "" {
fmt.Println("warning: credentials requiring aws session tokens are not supported")
Expand Down Expand Up @@ -184,6 +189,9 @@ func setInstallAWSCredentials(awsCreds *AWSCredentials, envName string) error {
}

// Prompt
if disallowPrompt {
return ErrorAWSCredentialsRequired()
}
err = cr.ReadPrompt(awsCreds, _awsCredentialsPromptValidation)
if err != nil {
return err
Expand Down Expand Up @@ -224,14 +232,14 @@ func setOperatorAWSCredentials(awsCreds *AWSCredentials) error {
return nil
}

func getAWSCredentials(userClusterConfigPath string, envName string) (AWSCredentials, error) {
func getAWSCredentials(userClusterConfigPath string, envName string, disallowPrompt bool) (AWSCredentials, error) {
awsCreds := AWSCredentials{}

if userClusterConfigPath != "" {
readAWSCredsFromConfigFile(&awsCreds, userClusterConfigPath)
}

err := setInstallAWSCredentials(&awsCreds, envName)
err := setInstallAWSCredentials(&awsCreds, envName, disallowPrompt)
if err != nil {
return AWSCredentials{}, err
}
Expand Down
Loading