diff --git a/cli/cmd/cluster.go b/cli/cmd/cluster.go index 99d18ea156..84f5fd0567 100644 --- a/cli/cmd/cluster.go +++ b/cli/cmd/cluster.go @@ -44,9 +44,10 @@ import ( ) var ( - _flagClusterEnv string - _flagClusterConfig string - _flagClusterInfoDebug bool + _flagClusterEnv string + _flagClusterConfig string + _flagClusterInfoDebug bool + _flagClusterDisallowPrompt bool ) func clusterInit() { @@ -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) } @@ -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) } @@ -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) } @@ -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) } @@ -232,12 +240,12 @@ 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) } @@ -245,7 +253,7 @@ var _infoCmd = &cobra.Command{ if _flagClusterInfoDebug { cmdDebug(awsCreds, accessConfig) } else { - cmdInfo(awsCreds, accessConfig) + cmdInfo(awsCreds, accessConfig, _flagClusterDisallowPrompt) } }, } @@ -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) } @@ -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 { @@ -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) @@ -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 { @@ -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 { @@ -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) } diff --git a/cli/cmd/deploy.go b/cli/cmd/deploy.go index 1667569127..e4441441b8 100644 --- a/cli/cmd/deploy.go +++ b/cli/cmd/deploy.go @@ -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{ @@ -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)?")) } @@ -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 @@ -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, "") } diff --git a/cli/cmd/errors.go b/cli/cmd/errors.go index 8fd3677cf0..7f1fa649ee 100644 --- a/cli/cmd/errors.go +++ b/cli/cmd/errors.go @@ -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" ) @@ -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 { @@ -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), + }) +} diff --git a/cli/cmd/lib_aws_creds.go b/cli/cmd/lib_aws_creds.go index cb37d1109a..fc29d15a68 100644 --- a/cli/cmd/lib_aws_creds.go +++ b/cli/cmd/lib_aws_creds.go @@ -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?", "", "") + } } } @@ -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") @@ -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 @@ -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 } diff --git a/cli/cmd/lib_cluster_config.go b/cli/cmd/lib_cluster_config.go index 2b2e89f91f..003d5c0fee 100644 --- a/cli/cmd/lib_cluster_config.go +++ b/cli/cmd/lib_cluster_config.go @@ -79,7 +79,7 @@ func readUserClusterConfigFile(clusterConfig *clusterconfig.Config) error { return nil } -func getClusterAccessConfig() (*clusterconfig.AccessConfig, error) { +func getClusterAccessConfig(disallowPrompt bool) (*clusterconfig.AccessConfig, error) { accessConfig, err := clusterconfig.DefaultAccessConfig() if err != nil { return nil, err @@ -108,6 +108,20 @@ func getClusterAccessConfig() (*clusterconfig.AccessConfig, error) { } } + if disallowPrompt { + if len(cachedPaths) > 1 && (accessConfig.ClusterName == nil || accessConfig.Region == nil) { + return nil, ErrorClusterAccessConfigOrPromptsRequired() + } + + if accessConfig.ClusterName == nil { + accessConfig.ClusterName = pointer.String("cortex") + } + if accessConfig.Region == nil { + accessConfig.Region = pointer.String("us-west-2") + } + return accessConfig, nil + } + err = cr.ReadPrompt(accessConfig, clusterconfig.AccessPromptValidation) if err != nil { return nil, err @@ -116,7 +130,7 @@ func getClusterAccessConfig() (*clusterconfig.AccessConfig, error) { return accessConfig, nil } -func getInstallClusterConfig(awsCreds AWSCredentials, envName string) (*clusterconfig.Config, error) { +func getInstallClusterConfig(awsCreds AWSCredentials, envName string, disallowPrompt bool) (*clusterconfig.Config, error) { clusterConfig := &clusterconfig.Config{} err := clusterconfig.SetDefaults(clusterConfig) @@ -131,7 +145,7 @@ func getInstallClusterConfig(awsCreds AWSCredentials, envName string) (*clusterc } } - err = clusterconfig.RegionPrompt(clusterConfig) + err = clusterconfig.RegionPrompt(clusterConfig, disallowPrompt) if err != nil { return nil, err } @@ -140,9 +154,9 @@ func getInstallClusterConfig(awsCreds AWSCredentials, envName string) (*clusterc if err != nil { return nil, err } - promptIfNotAdmin(awsClient) + promptIfNotAdmin(awsClient, disallowPrompt) - err = clusterconfig.InstallPrompt(clusterConfig, awsClient) + err = clusterconfig.InstallPrompt(clusterConfig, awsClient, disallowPrompt) if err != nil { return nil, err } @@ -160,26 +174,32 @@ func getInstallClusterConfig(awsCreds AWSCredentials, envName string) (*clusterc return nil, err } - confirmInstallClusterConfig(clusterConfig, awsCreds, awsClient, envName) + confirmInstallClusterConfig(clusterConfig, awsCreds, awsClient, envName, disallowPrompt) return clusterConfig, nil } -func getClusterUpdateConfig(cachedClusterConfig clusterconfig.Config, awsCreds AWSCredentials) (*clusterconfig.Config, error) { +func getClusterUpdateConfig(cachedClusterConfig clusterconfig.Config, awsCreds AWSCredentials, disallowPrompt bool) (*clusterconfig.Config, error) { userClusterConfig := &clusterconfig.Config{} var awsClient *aws.Client if _flagClusterConfig == "" { + if disallowPrompt { + return nil, ErrorClusterConfigOrPromptsRequired() + } + userClusterConfig = &cachedClusterConfig - err := cr.ReadPrompt(userClusterConfig, clusterconfig.UpdatePromptValidation(false, &cachedClusterConfig)) + err := clusterconfig.UpdatePrompt(userClusterConfig, &cachedClusterConfig, false, disallowPrompt) if err != nil { return nil, err } + awsClient, err = newAWSClient(*userClusterConfig.Region, awsCreds) if err != nil { return nil, err } - promptIfNotAdmin(awsClient) + promptIfNotAdmin(awsClient, disallowPrompt) + } else { err := readUserClusterConfigFile(userClusterConfig) if err != nil { @@ -192,7 +212,7 @@ func getClusterUpdateConfig(cachedClusterConfig clusterconfig.Config, awsCreds A if err != nil { return nil, err } - promptIfNotAdmin(awsClient) + promptIfNotAdmin(awsClient, disallowPrompt) if userClusterConfig.Bucket != "" && userClusterConfig.Bucket != cachedClusterConfig.Bucket { return nil, clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.BucketKey, cachedClusterConfig.Bucket) @@ -262,7 +282,7 @@ func getClusterUpdateConfig(cachedClusterConfig clusterconfig.Config, awsCreds A } userClusterConfig.SpotConfig = cachedClusterConfig.SpotConfig - err = cr.ReadPrompt(userClusterConfig, clusterconfig.UpdatePromptValidation(true, &cachedClusterConfig)) + err = clusterconfig.UpdatePrompt(userClusterConfig, &cachedClusterConfig, true, disallowPrompt) if err != nil { return nil, err } @@ -282,12 +302,12 @@ func getClusterUpdateConfig(cachedClusterConfig clusterconfig.Config, awsCreds A return nil, err } - confirmUpdateClusterConfig(*userClusterConfig, awsCreds, awsClient) + confirmUpdateClusterConfig(*userClusterConfig, awsCreds, awsClient, disallowPrompt) return userClusterConfig, nil } -func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsCreds AWSCredentials, awsClient *aws.Client, envName string) { +func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsCreds AWSCredentials, awsClient *aws.Client, envName string, disallowPrompt bool) { eksPrice := aws.EKSPrices[*clusterConfig.Region] operatorInstancePrice := aws.InstanceMetadatas[*clusterConfig.Region]["t3.medium"].Price operatorEBSPrice := aws.EBSMetadatas[*clusterConfig.Region].Price * 20 / 30 / 24 @@ -371,15 +391,19 @@ func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsCreds A fmt.Println() } - exitMessage := fmt.Sprintf("cluster configuration can be modified via the cluster config file; see https://cortex.dev/v/%s/cluster-management/config for more information", consts.CortexVersionMinor) - prompt.YesOrExit("would you like to continue?", "", exitMessage) + if !disallowPrompt { + exitMessage := fmt.Sprintf("cluster configuration can be modified via the cluster config file; see https://cortex.dev/v/%s/cluster-management/config for more information", consts.CortexVersionMinor) + prompt.YesOrExit("would you like to continue?", "", exitMessage) + } } -func confirmUpdateClusterConfig(clusterConfig clusterconfig.Config, awsCreds AWSCredentials, awsClient *aws.Client) { +func confirmUpdateClusterConfig(clusterConfig clusterconfig.Config, awsCreds AWSCredentials, awsClient *aws.Client, disallowPrompt bool) { fmt.Println(clusterConfigConfirmaionStr(clusterConfig, awsCreds, awsClient)) - exitMessage := fmt.Sprintf("cluster configuration can be modified via the cluster config file; see https://cortex.dev/v/%s/cluster-management/config for more information", consts.CortexVersionMinor) - prompt.YesOrExit(fmt.Sprintf("your cluster (%s in %s) will be updated according to the configuration above, are you sure you want to continue?", clusterConfig.ClusterName, *clusterConfig.Region), "", exitMessage) + if !disallowPrompt { + exitMessage := fmt.Sprintf("cluster configuration can be modified via the cluster config file; see https://cortex.dev/v/%s/cluster-management/config for more information", consts.CortexVersionMinor) + prompt.YesOrExit(fmt.Sprintf("your cluster (%s in %s) will be updated according to the configuration above, are you sure you want to continue?", clusterConfig.ClusterName, *clusterConfig.Region), "", exitMessage) + } } func clusterConfigConfirmaionStr(clusterConfig clusterconfig.Config, awsCreds AWSCredentials, awsClient *aws.Client) string { diff --git a/docs/cluster-management/cli.md b/docs/cluster-management/cli.md index 402c12d9f0..e10b2b1877 100644 --- a/docs/cluster-management/cli.md +++ b/docs/cluster-management/cli.md @@ -98,6 +98,7 @@ Usage: Flags: -c, --config string path to a cluster configuration file -e, --env string environment to configure (default "aws") + -y, --yes skip prompts -h, --help help for up ``` @@ -113,6 +114,7 @@ Flags: -c, --config string path to a cluster configuration file -e, --env string environment to configure (default "aws") -d, --debug save the current cluster state to a file + -y, --yes skip prompts -h, --help help for info ``` @@ -127,6 +129,7 @@ Usage: Flags: -c, --config string path to a cluster configuration file -e, --env string environment to configure (default "aws") + -y, --yes skip prompts -h, --help help for update ``` @@ -140,6 +143,7 @@ Usage: Flags: -c, --config string path to a cluster configuration file + -y, --yes skip prompts -h, --help help for down ``` diff --git a/pkg/types/clusterconfig/clusterconfig.go b/pkg/types/clusterconfig/clusterconfig.go index 671362afbb..3f40e25522 100644 --- a/pkg/types/clusterconfig/clusterconfig.go +++ b/pkg/types/clusterconfig/clusterconfig.go @@ -585,8 +585,16 @@ func applyPromptDefaults(defaults Config) *Config { return defaultConfig } -func RegionPrompt(clusterConfig *Config) error { +func RegionPrompt(clusterConfig *Config, disallowPrompt bool) error { defaults := applyPromptDefaults(*clusterConfig) + + if disallowPrompt { + if clusterConfig.Region == nil { + clusterConfig.Region = defaults.Region + } + return nil + } + regionPrompt := &cr.PromptValidation{ SkipNonNilFields: true, PromptItemValidations: []*cr.PromptItemValidation{ @@ -610,7 +618,7 @@ func RegionPrompt(clusterConfig *Config) error { return nil } -func InstallPrompt(clusterConfig *Config, awsClient *aws.Client) error { +func InstallPrompt(clusterConfig *Config, awsClient *aws.Client, disallowPrompt bool) error { defaults := applyPromptDefaults(*clusterConfig) accountID, _, err := awsClient.GetCachedAccountID() if err != nil { @@ -626,6 +634,22 @@ func InstallPrompt(clusterConfig *Config, awsClient *aws.Client) error { defaultBucket = defaultBucket[:len(defaultBucket)-1] } + if disallowPrompt { + if clusterConfig.Bucket == "" { + clusterConfig.Bucket = defaultBucket + } + if clusterConfig.InstanceType == nil { + clusterConfig.InstanceType = defaults.InstanceType + } + if clusterConfig.MinInstances == nil { + clusterConfig.MinInstances = defaults.MinInstances + } + if clusterConfig.MaxInstances == nil { + clusterConfig.MaxInstances = defaults.MaxInstances + } + return nil + } + remainingPrompts := &cr.PromptValidation{ SkipNonNilFields: true, SkipNonEmptyFields: true, @@ -686,10 +710,28 @@ func InstallPrompt(clusterConfig *Config, awsClient *aws.Client) error { return nil } -func UpdatePromptValidation(skipPopulatedFields bool, userClusterConfig *Config) *cr.PromptValidation { - defaults := applyPromptDefaults(*userClusterConfig) +func UpdatePrompt(userClusterConfig *Config, cachedClusterConfig *Config, skipPopulatedFields bool, disallowPrompt bool) error { + defaults := applyPromptDefaults(*cachedClusterConfig) - return &cr.PromptValidation{ + if disallowPrompt { + if userClusterConfig.MinInstances == nil { + if cachedClusterConfig.MinInstances != nil { + userClusterConfig.MinInstances = cachedClusterConfig.MinInstances + } else { + userClusterConfig.MinInstances = defaults.MinInstances + } + } + if userClusterConfig.MaxInstances == nil { + if cachedClusterConfig.MaxInstances != nil { + userClusterConfig.MaxInstances = cachedClusterConfig.MaxInstances + } else { + userClusterConfig.MaxInstances = defaults.MaxInstances + } + } + return nil + } + + remainingPrompts := &cr.PromptValidation{ SkipNonNilFields: skipPopulatedFields, PromptItemValidations: []*cr.PromptItemValidation{ { @@ -716,6 +758,13 @@ func UpdatePromptValidation(skipPopulatedFields bool, userClusterConfig *Config) }, }, } + + err := cr.ReadPrompt(userClusterConfig, remainingPrompts) + if err != nil { + return err + } + + return nil } var AccessPromptValidation = &cr.PromptValidation{