From 71a9da8bc82c7d8c54feee5e0f2b3630804a2c4d Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Thu, 4 Mar 2021 20:52:50 +0200 Subject: [PATCH 01/10] Remove cluster prompts for AWS --- Makefile | 4 +- cli/cmd/cluster.go | 146 ++++---- cli/cmd/errors.go | 16 +- cli/cmd/lib_cluster_config_aws.go | 230 ++++--------- cli/cmd/lib_manager.go | 6 +- docs/clients/cli.md | 8 +- pkg/operator/config/config.go | 6 +- pkg/operator/endpoints/info.go | 2 +- pkg/operator/operator/cron.go | 20 +- pkg/types/clusterconfig/availability_zones.go | 10 +- pkg/types/clusterconfig/cluster_config_aws.go | 324 ++++-------------- pkg/types/clusterconfig/errors.go | 8 + pkg/types/clusterstate/clusterstate.go | 10 +- 13 files changed, 250 insertions(+), 540 deletions(-) diff --git a/Makefile b/Makefile index 15eda8ac5f..b2b0e56232 100644 --- a/Makefile +++ b/Makefile @@ -68,7 +68,7 @@ cluster-up-aws: @$(MAKE) images-all-aws @$(MAKE) cli @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true - @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster up --config=./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" + @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster up ./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" @$(MAKE) kubectl-aws cluster-up-gcp: @$(MAKE) images-all-gcp @@ -81,7 +81,7 @@ cluster-up-aws-y: @$(MAKE) images-all-aws @$(MAKE) cli @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true - @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster up --config=./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" --yes + @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster up ./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" --yes @$(MAKE) kubectl-aws cluster-up-gcp-y: @$(MAKE) images-all-gcp diff --git a/cli/cmd/cluster.go b/cli/cmd/cluster.go index 56fed938f4..8e30e43674 100644 --- a/cli/cmd/cluster.go +++ b/cli/cmd/cluster.go @@ -67,7 +67,6 @@ var ( func clusterInit() { _clusterUpCmd.Flags().SortFlags = false - addClusterConfigFlag(_clusterUpCmd) addAWSCredentialsFlags(_clusterUpCmd) addClusterAWSCredentialsFlags(_clusterUpCmd) _clusterUpCmd.Flags().StringVarP(&_flagClusterUpEnv, "configure-env", "e", "aws", "name of environment to configure") @@ -85,7 +84,6 @@ func clusterInit() { _clusterCmd.AddCommand(_clusterInfoCmd) _clusterConfigureCmd.Flags().SortFlags = false - addClusterConfigFlag(_clusterConfigureCmd) addAWSCredentialsFlags(_clusterConfigureCmd) addClusterAWSCredentialsFlags(_clusterConfigureCmd) _clusterConfigureCmd.Flags().StringVarP(&_flagClusterConfigureEnv, "configure-env", "e", "", "name of environment to configure") @@ -138,12 +136,14 @@ var _clusterCmd = &cobra.Command{ } var _clusterUpCmd = &cobra.Command{ - Use: "up", + Use: "up [CLUSTER_CONFIG_FILE]", Short: "spin up a cluster on aws", - Args: cobra.NoArgs, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { telemetry.EventNotify("cli.cluster.up", map[string]interface{}{"provider": types.AWSProviderType}) + clusterConfigFile := args[0] + envExists, err := isEnvConfigured(_flagClusterUpEnv) if err != nil { exit.Error(err) @@ -160,24 +160,22 @@ var _clusterUpCmd = &cobra.Command{ exit.Error(err) } - if _flagClusterConfig != "" { - // Deprecation: specifying aws creds in cluster configuration is no longer supported - if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { - exit.Error(err) - } + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, clusterConfigFile); err != nil { + exit.Error(err) } - accessConfig, err := getNewClusterAccessConfig(_flagClusterDisallowPrompt) + accessConfig, err := getNewClusterAccessConfig(clusterConfigFile) if err != nil { exit.Error(err) } - awsClient, err := newAWSClient(*accessConfig.Region) + awsClient, err := newAWSClient(accessConfig.Region) if err != nil { exit.Error(err) } - clusterConfig, err := getInstallClusterConfig(awsClient, *accessConfig, _flagClusterDisallowPrompt) + clusterConfig, err := getInstallClusterConfig(awsClient, *accessConfig, clusterConfigFile, _flagClusterDisallowPrompt) if err != nil { exit.Error(err) } @@ -187,7 +185,7 @@ var _clusterUpCmd = &cobra.Command{ exit.Error(err) } - err = clusterstate.AssertClusterStatus(*accessConfig.ClusterName, *accessConfig.Region, clusterState.Status, clusterstate.StatusNotFound, clusterstate.StatusDeleteComplete) + err = clusterstate.AssertClusterStatus(accessConfig.ClusterName, accessConfig.Region, clusterState.Status, clusterstate.StatusNotFound, clusterstate.StatusDeleteComplete) if err != nil { exit.Error(err) } @@ -211,7 +209,7 @@ var _clusterUpCmd = &cobra.Command{ ClusterName: clusterConfig.ClusterName, LogGroup: clusterConfig.ClusterName, Bucket: clusterConfig.Bucket, - Region: *clusterConfig.Region, + Region: clusterConfig.Region, SQSPrefix: clusterconfig.SQSNamePrefix(clusterConfig.ClusterName), AccountID: accountID, }) @@ -227,7 +225,7 @@ var _clusterUpCmd = &cobra.Command{ eksCluster, err := awsClient.EKSClusterOrNil(clusterConfig.ClusterName) if err != nil { helpStr := "\ndebugging tips (may or may not apply to this error):" - helpStr += fmt.Sprintf("\n* if your cluster started spinning up but was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", *clusterConfig.Region) + helpStr += fmt.Sprintf("\n* if your cluster started spinning up but was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", clusterConfig.Region) helpStr += "\n* if your cluster started spinning up, please run `cortex cluster down` to delete the cluster before trying to create this cluster again" fmt.Println(helpStr) exit.Error(ErrorClusterUp(out + helpStr)) @@ -242,7 +240,7 @@ var _clusterUpCmd = &cobra.Command{ asgs, err := awsClient.AutoscalingGroups(clusterTags) if err != nil { helpStr := "\ndebugging tips (may or may not apply to this error):" - helpStr += fmt.Sprintf("\n* if your cluster was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", *clusterConfig.Region) + helpStr += fmt.Sprintf("\n* if your cluster was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", clusterConfig.Region) helpStr += "\n* please run `cortex cluster down` to delete the cluster before trying to create this cluster again" fmt.Println(helpStr) exit.Error(ErrorClusterUp(out + helpStr)) @@ -259,7 +257,7 @@ var _clusterUpCmd = &cobra.Command{ activity, err := awsClient.MostRecentASGActivity(*asg.AutoScalingGroupName) if err != nil { helpStr := "\ndebugging tips (may or may not apply to this error):" - helpStr += fmt.Sprintf("\n* if your cluster was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", *clusterConfig.Region) + helpStr += fmt.Sprintf("\n* if your cluster was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", clusterConfig.Region) helpStr += "\n* please run `cortex cluster down` to delete the cluster before trying to create this cluster again" fmt.Println(helpStr) exit.Error(ErrorClusterUp(out + helpStr)) @@ -277,7 +275,7 @@ var _clusterUpCmd = &cobra.Command{ helpStr := "\nyour cluster was unable to provision EC2 instances; here is one of the encountered errors:" helpStr += fmt.Sprintf("\n\n> status: %s\n> description: %s", status, description) - helpStr += fmt.Sprintf("\n\nadditional error information might be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", *clusterConfig.Region) + helpStr += fmt.Sprintf("\n\nadditional error information might be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", clusterConfig.Region) helpStr += "\n\nplease run `cortex cluster down` to delete the cluster before trying to create this cluster again" fmt.Println(helpStr) exit.Error(ErrorClusterUp(out + helpStr)) @@ -315,29 +313,29 @@ var _clusterUpCmd = &cobra.Command{ } var _clusterConfigureCmd = &cobra.Command{ - Use: "configure", + Use: "configure [CLUSTER_CONFIG_FILE]", Short: "update a cluster's configuration", - Args: cobra.NoArgs, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { telemetry.Event("cli.cluster.configure", map[string]interface{}{"provider": types.AWSProviderType}) + clusterConfigFile := args[0] + if _, err := docker.GetDockerClient(); err != nil { exit.Error(err) } - if _flagClusterConfig != "" { - // Deprecation: specifying aws creds in cluster configuration is no longer supported - if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { - exit.Error(err) - } + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, clusterConfigFile); err != nil { + exit.Error(err) } - accessConfig, err := getClusterAccessConfigWithCache(_flagClusterDisallowPrompt) + accessConfig, err := getClusterAccessConfigWithCache() if err != nil { exit.Error(err) } - awsClient, err := newAWSClient(*accessConfig.Region) + awsClient, err := newAWSClient(accessConfig.Region) if err != nil { exit.Error(err) } @@ -347,14 +345,14 @@ var _clusterConfigureCmd = &cobra.Command{ exit.Error(err) } - err = clusterstate.AssertClusterStatus(*accessConfig.ClusterName, *accessConfig.Region, clusterState.Status, clusterstate.StatusCreateComplete) + err = clusterstate.AssertClusterStatus(accessConfig.ClusterName, accessConfig.Region, clusterState.Status, clusterstate.StatusCreateComplete) if err != nil { exit.Error(err) } cachedClusterConfig := refreshCachedClusterConfig(*awsClient, accessConfig, _flagClusterDisallowPrompt) - clusterConfig, err := getConfigureClusterConfig(cachedClusterConfig, _flagClusterDisallowPrompt) + clusterConfig, err := getConfigureClusterConfig(cachedClusterConfig, clusterConfigFile, _flagClusterDisallowPrompt) if err != nil { exit.Error(err) } @@ -365,7 +363,7 @@ var _clusterConfigureCmd = &cobra.Command{ } if exitCode == nil || *exitCode != 0 { helpStr := "\ndebugging tips (may or may not apply to this error):" - helpStr += fmt.Sprintf("\n* if your cluster was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", *clusterConfig.Region) + helpStr += fmt.Sprintf("\n* if your cluster was unable to provision instances, additional error information may be found in the activity history of your cluster's autoscaling groups (select each autoscaling group and click the \"Activity\" or \"Activity History\" tab): https://console.aws.amazon.com/ec2/autoscaling/home?region=%s#AutoScalingGroups:", clusterConfig.Region) fmt.Println(helpStr) exit.Error(ErrorClusterConfigure(out + helpStr)) } @@ -395,19 +393,17 @@ var _clusterInfoCmd = &cobra.Command{ exit.Error(err) } - if _flagClusterConfig != "" { - // Deprecation: specifying aws creds in cluster configuration is no longer supported - if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { - exit.Error(err) - } + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { + exit.Error(err) } - accessConfig, err := getClusterAccessConfigWithCache(_flagClusterDisallowPrompt) + accessConfig, err := getClusterAccessConfigWithCache() if err != nil { exit.Error(err) } - awsClient, err := newAWSClient(*accessConfig.Region) + awsClient, err := newAWSClient(accessConfig.Region) if err != nil { exit.Error(err) } @@ -431,20 +427,18 @@ var _clusterDownCmd = &cobra.Command{ exit.Error(err) } - if _flagClusterConfig != "" { - // Deprecation: specifying aws creds in cluster configuration is no longer supported - if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { - exit.Error(err) - } + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { + exit.Error(err) } - accessConfig, err := getClusterAccessConfigWithCache(_flagClusterDisallowPrompt) + accessConfig, err := getClusterAccessConfigWithCache() if err != nil { exit.Error(err) } // Check AWS access - awsClient, err := newAWSClient(*accessConfig.Region) + awsClient, err := newAWSClient(accessConfig.Region) if err != nil { exit.Error(err) } @@ -463,25 +457,25 @@ var _clusterDownCmd = &cobra.Command{ if err == nil { switch clusterState.Status { case clusterstate.StatusNotFound: - exit.Error(clusterstate.ErrorClusterDoesNotExist(*accessConfig.ClusterName, *accessConfig.Region)) + exit.Error(clusterstate.ErrorClusterDoesNotExist(accessConfig.ClusterName, accessConfig.Region)) case clusterstate.StatusDeleteComplete: - exit.Error(clusterstate.ErrorClusterAlreadyDeleted(*accessConfig.ClusterName, *accessConfig.Region)) + exit.Error(clusterstate.ErrorClusterAlreadyDeleted(accessConfig.ClusterName, accessConfig.Region)) } } // updating CLI env is best-effort, so ignore errors - loadBalancer, _ := getAWSOperatorLoadBalancer(*accessConfig.ClusterName, awsClient) + loadBalancer, _ := getAWSOperatorLoadBalancer(accessConfig.ClusterName, awsClient) if _flagClusterDisallowPrompt { - fmt.Printf("your cluster named \"%s\" in %s will be spun down and all apis will be deleted\n\n", *accessConfig.ClusterName, *accessConfig.Region) + fmt.Printf("your cluster named \"%s\" in %s will be spun down and all apis will be deleted\n\n", accessConfig.ClusterName, accessConfig.Region) } else { - prompt.YesOrExit(fmt.Sprintf("your cluster named \"%s\" in %s will be spun down and all apis will be deleted, are you sure you want to continue?", *accessConfig.ClusterName, *accessConfig.Region), "", "") + prompt.YesOrExit(fmt.Sprintf("your cluster named \"%s\" in %s will be spun down and all apis will be deleted, are you sure you want to continue?", accessConfig.ClusterName, accessConfig.Region), "", "") } fmt.Print("○ deleting sqs queues ") - err = awsClient.DeleteQueuesWithPrefix(clusterconfig.SQSNamePrefix(*accessConfig.ClusterName)) + err = awsClient.DeleteQueuesWithPrefix(clusterconfig.SQSNamePrefix(accessConfig.ClusterName)) if err != nil { - fmt.Printf("\n\nfailed to delete all sqs queues; please delete queues starting with the name %s via the cloudwatch console: https://%s.console.aws.amazon.com/sqs/v2/home", clusterconfig.SQSNamePrefix(*accessConfig.ClusterName), *accessConfig.Region) + fmt.Printf("\n\nfailed to delete all sqs queues; please delete queues starting with the name %s via the cloudwatch console: https://%s.console.aws.amazon.com/sqs/v2/home", clusterconfig.SQSNamePrefix(accessConfig.ClusterName), accessConfig.Region) errors.PrintError(err) fmt.Println() } else { @@ -500,13 +494,13 @@ var _clusterDownCmd = &cobra.Command{ errors.PrintError(err) fmt.Println() } else if exitCode == nil || *exitCode != 0 { - helpStr := fmt.Sprintf("\nNote: if this error cannot be resolved, please ensure that all CloudFormation stacks for this cluster eventually become fully deleted (%s). If the stack deletion process has failed, please delete the stacks directly from the AWS console (this may require manually deleting particular AWS resources that are blocking the stack deletion)", clusterstate.CloudFormationURL(*accessConfig.ClusterName, *accessConfig.Region)) + helpStr := fmt.Sprintf("\nNote: if this error cannot be resolved, please ensure that all CloudFormation stacks for this cluster eventually become fully deleted (%s). If the stack deletion process has failed, please delete the stacks directly from the AWS console (this may require manually deleting particular AWS resources that are blocking the stack deletion)", clusterstate.CloudFormationURL(accessConfig.ClusterName, accessConfig.Region)) fmt.Println(helpStr) exit.Error(ErrorClusterDown(out + helpStr)) } // delete policy after spinning down the cluster (which deletes the roles) because policies can't be deleted if they are attached to roles - policyARN := clusterconfig.DefaultPolicyARN(accountID, *accessConfig.ClusterName, *accessConfig.Region) + policyARN := clusterconfig.DefaultPolicyARN(accountID, accessConfig.ClusterName, accessConfig.Region) fmt.Printf("○ deleting auto-generated iam policy %s ", policyARN) err = awsClient.DeletePolicy(policyARN) if err != nil { @@ -540,9 +534,9 @@ var _clusterDownCmd = &cobra.Command{ } } - fmt.Printf("\nplease check CloudFormation to ensure that all resources for the %s cluster eventually become successfully deleted: %s\n", *accessConfig.ClusterName, clusterstate.CloudFormationURL(*accessConfig.ClusterName, *accessConfig.Region)) + fmt.Printf("\nplease check CloudFormation to ensure that all resources for the %s cluster eventually become successfully deleted: %s\n", accessConfig.ClusterName, clusterstate.CloudFormationURL(accessConfig.ClusterName, accessConfig.Region)) - cachedClusterConfigPath := cachedClusterConfigPath(*accessConfig.ClusterName, *accessConfig.Region) + cachedClusterConfigPath := cachedClusterConfigPath(accessConfig.ClusterName, accessConfig.Region) os.Remove(cachedClusterConfigPath) }, } @@ -554,20 +548,18 @@ var _clusterExportCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { telemetry.Event("cli.cluster.export", map[string]interface{}{"provider": types.AWSProviderType}) - if _flagClusterConfig != "" { - // Deprecation: specifying aws creds in cluster configuration is no longer supported - if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { - exit.Error(err) - } + // Deprecation: specifying aws creds in cluster configuration is no longer supported + if err := detectAWSCredsInConfigFile(cmd.Use, _flagClusterConfig); err != nil { + exit.Error(err) } - accessConfig, err := getClusterAccessConfigWithCache(_flagClusterDisallowPrompt) + accessConfig, err := getClusterAccessConfigWithCache() if err != nil { exit.Error(err) } // Check AWS access - awsClient, err := newAWSClient(*accessConfig.Region) + awsClient, err := newAWSClient(accessConfig.Region) if err != nil { exit.Error(err) } @@ -578,12 +570,12 @@ var _clusterExportCmd = &cobra.Command{ exit.Error(err) } - err = clusterstate.AssertClusterStatus(*accessConfig.ClusterName, *accessConfig.Region, clusterState.Status, clusterstate.StatusCreateComplete) + err = clusterstate.AssertClusterStatus(accessConfig.ClusterName, accessConfig.Region, clusterState.Status, clusterstate.StatusCreateComplete) if err != nil { exit.Error(err) } - loadBalancer, err := getAWSOperatorLoadBalancer(*accessConfig.ClusterName, awsClient) + loadBalancer, err := getAWSOperatorLoadBalancer(accessConfig.ClusterName, awsClient) if err != nil { exit.Error(err) } @@ -607,7 +599,7 @@ var _clusterExportCmd = &cobra.Command{ exit.Error(err) } if len(apisResponse) == 0 { - fmt.Println(fmt.Sprintf("no apis found in your cluster named %s in %s", *accessConfig.ClusterName, *accessConfig.Region)) + fmt.Println(fmt.Sprintf("no apis found in your cluster named %s in %s", accessConfig.ClusterName, accessConfig.Region)) exit.Ok() } } else if len(args) == 1 { @@ -622,7 +614,7 @@ var _clusterExportCmd = &cobra.Command{ } } - exportPath := fmt.Sprintf("export-%s-%s", *accessConfig.Region, *accessConfig.ClusterName) + exportPath := fmt.Sprintf("export-%s-%s", accessConfig.Region, accessConfig.ClusterName) err = files.CreateDir(exportPath) if err != nil { @@ -715,11 +707,11 @@ func printInfoClusterState(awsClient *aws.Client, accessConfig *clusterconfig.Ac fmt.Println(clusterState.TableString()) if clusterState.Status == clusterstate.StatusCreateFailed || clusterState.Status == clusterstate.StatusDeleteFailed { - fmt.Println(fmt.Sprintf("more information can be found in your AWS console: %s", clusterstate.CloudFormationURL(*accessConfig.ClusterName, *accessConfig.Region))) + fmt.Println(fmt.Sprintf("more information can be found in your AWS console: %s", clusterstate.CloudFormationURL(accessConfig.ClusterName, accessConfig.Region))) fmt.Println() } - err = clusterstate.AssertClusterStatus(*accessConfig.ClusterName, *accessConfig.Region, clusterState.Status, clusterstate.StatusCreateComplete) + err = clusterstate.AssertClusterStatus(accessConfig.ClusterName, accessConfig.Region, clusterState.Status, clusterstate.StatusCreateComplete) if err != nil { return err } @@ -766,15 +758,15 @@ func printInfoPricing(infoResponse *schema.InfoResponse, clusterConfig clusterco totalAPIInstancePrice += nodeInfo.Price } - eksPrice := aws.EKSPrices[*clusterConfig.Region] - operatorInstancePrice := aws.InstanceMetadatas[*clusterConfig.Region]["t3.medium"].Price - operatorEBSPrice := aws.EBSMetadatas[*clusterConfig.Region]["gp2"].PriceGB * 20 / 30 / 24 - metricsEBSPrice := aws.EBSMetadatas[*clusterConfig.Region]["gp2"].PriceGB * 40 / 30 / 24 - nlbPrice := aws.NLBMetadatas[*clusterConfig.Region].Price - natUnitPrice := aws.NATMetadatas[*clusterConfig.Region].Price - apiEBSPrice := aws.EBSMetadatas[*clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceGB * float64(clusterConfig.InstanceVolumeSize) / 30 / 24 + eksPrice := aws.EKSPrices[clusterConfig.Region] + operatorInstancePrice := aws.InstanceMetadatas[clusterConfig.Region]["t3.medium"].Price + operatorEBSPrice := aws.EBSMetadatas[clusterConfig.Region]["gp2"].PriceGB * 20 / 30 / 24 + metricsEBSPrice := aws.EBSMetadatas[clusterConfig.Region]["gp2"].PriceGB * 40 / 30 / 24 + nlbPrice := aws.NLBMetadatas[clusterConfig.Region].Price + natUnitPrice := aws.NATMetadatas[clusterConfig.Region].Price + apiEBSPrice := aws.EBSMetadatas[clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceGB * float64(clusterConfig.InstanceVolumeSize) / 30 / 24 if clusterConfig.InstanceVolumeType.String() == "io1" && clusterConfig.InstanceVolumeIOPS != nil { - apiEBSPrice += aws.EBSMetadatas[*clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceIOPS * float64(*clusterConfig.InstanceVolumeIOPS) / 30 / 24 + apiEBSPrice += aws.EBSMetadatas[clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceIOPS * float64(*clusterConfig.InstanceVolumeIOPS) / 30 / 24 } var natTotalPrice float64 @@ -943,7 +935,7 @@ func cmdDebug(awsClient *aws.Client, accessConfig *clusterconfig.AccessConfig) { func refreshCachedClusterConfig(awsClient aws.Client, accessConfig *clusterconfig.AccessConfig, disallowPrompt bool) clusterconfig.Config { // add empty file if cached cluster doesn't exist so that the file output by manager container maintains current user permissions - cachedClusterConfigPath := cachedClusterConfigPath(*accessConfig.ClusterName, *accessConfig.Region) + cachedClusterConfigPath := cachedClusterConfigPath(accessConfig.ClusterName, accessConfig.Region) containerConfigPath := fmt.Sprintf("/out/%s", filepath.Base(cachedClusterConfigPath)) copyFromPaths := []dockerCopyFromPath{ diff --git a/cli/cmd/errors.go b/cli/cmd/errors.go index 22fa59253e..56b9b5396c 100644 --- a/cli/cmd/errors.go +++ b/cli/cmd/errors.go @@ -69,8 +69,7 @@ const ( ErrClusterRefresh = "cli.cluster_refresh" ErrClusterDown = "cli.cluster_down" ErrDuplicateCLIEnvNames = "cli.duplicate_cli_env_names" - ErrClusterConfigOrPromptsRequired = "cli.cluster_config_or_prompts_required" - ErrClusterAccessConfigOrPromptsRequired = "cli.cluster_access_config_or_prompts_required" + ErrClusterAccessConfigRequired = "cli.cluster_access_config_or_prompts_required" ErrGCPClusterAccessConfigOrPromptsRequired = "cli.gcp_cluster_access_config_or_prompts_required" ErrShellCompletionNotSupported = "cli.shell_completion_not_supported" ErrNoTerminalWidth = "cli.no_terminal_width" @@ -293,17 +292,10 @@ func ErrorClusterDown(out string) error { }) } -func ErrorClusterConfigOrPromptsRequired() error { +func ErrorClusterAccessConfigRequired() error { return errors.WithStack(&errors.Error{ - Kind: ErrClusterConfigOrPromptsRequired, - Message: "this command requires either a cluster configuration file (e.g. via `--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. via `--config cluster.yaml`) or enable prompts (i.e. omit the `--yes` flag)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey), + Kind: ErrClusterAccessConfigRequired, + Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s` and `%s` (e.g. via `--config cluster.yaml`) or use the CLI flags to specify the cluster (e.g. via `--name` or `--region`)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey), }) } diff --git a/cli/cmd/lib_cluster_config_aws.go b/cli/cmd/lib_cluster_config_aws.go index d30f59f3d9..30915056c7 100644 --- a/cli/cmd/lib_cluster_config_aws.go +++ b/cli/cmd/lib_cluster_config_aws.go @@ -67,8 +67,8 @@ func readCachedClusterConfigFile(clusterConfig *clusterconfig.Config, filePath s return nil } -func readUserClusterConfigFile(clusterConfig *clusterconfig.Config) error { - errs := cr.ParseYAMLFile(clusterConfig, clusterconfig.FullManagedValidation, _flagClusterConfig) +func readUserClusterConfigFile(clusterConfig *clusterconfig.Config, filePath string) error { + errs := cr.ParseYAMLFile(clusterConfig, clusterconfig.FullManagedValidation, filePath) if errors.HasError(errs) { return errors.Append(errors.FirstError(errs...), fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) } @@ -76,46 +76,20 @@ func readUserClusterConfigFile(clusterConfig *clusterconfig.Config) error { return nil } -func getNewClusterAccessConfig(disallowPrompt bool) (*clusterconfig.AccessConfig, error) { - accessConfig, err := clusterconfig.DefaultAccessConfig() - if err != nil { - return nil, err - } - - if _flagClusterConfig != "" { - errs := cr.ParseYAMLFile(accessConfig, clusterconfig.AccessValidation, _flagClusterConfig) - if errors.HasError(errs) { - return nil, errors.Append(errors.FirstError(errs...), fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) - } - } - - if _flagClusterName != "" { - accessConfig.ClusterName = pointer.String(_flagClusterName) - } - if _flagClusterRegion != "" { - accessConfig.Region = pointer.String(_flagClusterRegion) - } - - if accessConfig.ClusterName != nil && accessConfig.Region != nil { - return accessConfig, nil - } - - if disallowPrompt { - return nil, ErrorClusterAccessConfigOrPromptsRequired() - } +func getNewClusterAccessConfig(clusterConfigFile string) (*clusterconfig.AccessConfig, error) { + accessConfig := &clusterconfig.AccessConfig{} - err = cr.ReadPrompt(accessConfig, clusterconfig.AccessPromptValidation) - if err != nil { - return nil, err + errs := cr.ParseYAMLFile(accessConfig, clusterconfig.AccessValidation, clusterConfigFile) + if errors.HasError(errs) { + return nil, errors.Append(errors.FirstError(errs...), fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) } return accessConfig, nil } -func getClusterAccessConfigWithCache(disallowPrompt bool) (*clusterconfig.AccessConfig, error) { - accessConfig, err := clusterconfig.DefaultAccessConfig() - if err != nil { - return nil, err +func getClusterAccessConfigWithCache() (*clusterconfig.AccessConfig, error) { + accessConfig := &clusterconfig.AccessConfig{ + ImageManager: "quay.io/cortexlabs/manager:" + consts.CortexVersion, } if _flagClusterConfig != "" { @@ -126,65 +100,40 @@ func getClusterAccessConfigWithCache(disallowPrompt bool) (*clusterconfig.Access } if _flagClusterName != "" { - accessConfig.ClusterName = pointer.String(_flagClusterName) + accessConfig.ClusterName = _flagClusterName } if _flagClusterRegion != "" { - accessConfig.Region = pointer.String(_flagClusterRegion) - } - - if accessConfig.ClusterName != nil && accessConfig.Region != nil { - return accessConfig, nil + accessConfig.Region = _flagClusterRegion } cachedPaths := existingCachedClusterConfigPaths() if len(cachedPaths) == 1 { cachedAccessConfig := &clusterconfig.AccessConfig{} cr.ParseYAMLFile(cachedAccessConfig, clusterconfig.AccessValidation, cachedPaths[0]) - if accessConfig.ClusterName == nil { - accessConfig.ClusterName = cachedAccessConfig.ClusterName - } - if accessConfig.Region == nil { - accessConfig.Region = cachedAccessConfig.Region - } - } - - if disallowPrompt { - return nil, ErrorClusterAccessConfigOrPromptsRequired() + accessConfig.ClusterName = cachedAccessConfig.ClusterName + accessConfig.Region = cachedAccessConfig.Region } - err = cr.ReadPrompt(accessConfig, clusterconfig.AccessPromptValidation) - if err != nil { - return nil, err + if accessConfig.ClusterName == "" || accessConfig.Region == "" { + // won't execute for cluster configure commands + return nil, ErrorClusterAccessConfigRequired() } - return accessConfig, nil } -func getInstallClusterConfig(awsClient *aws.Client, accessConfig clusterconfig.AccessConfig, disallowPrompt bool) (*clusterconfig.Config, error) { +func getInstallClusterConfig(awsClient *aws.Client, accessConfig clusterconfig.AccessConfig, clusterConfigFile string, disallowPrompt bool) (*clusterconfig.Config, error) { clusterConfig := &clusterconfig.Config{} - err := clusterconfig.SetDefaults(clusterConfig) + err := readUserClusterConfigFile(clusterConfig, clusterConfigFile) if err != nil { return nil, err } - if _flagClusterConfig != "" { - err := readUserClusterConfigFile(clusterConfig) - if err != nil { - return nil, err - } - } - - clusterConfig.ClusterName = *accessConfig.ClusterName + clusterConfig.ClusterName = accessConfig.ClusterName clusterConfig.Region = accessConfig.Region promptIfNotAdmin(awsClient, disallowPrompt) - err = clusterconfig.InstallPrompt(clusterConfig, disallowPrompt) - if err != nil { - return nil, err - } - clusterConfig.Telemetry, err = readTelemetryConfig() if err != nil { return nil, err @@ -193,10 +142,7 @@ func getInstallClusterConfig(awsClient *aws.Client, accessConfig clusterconfig.A err = clusterConfig.Validate(awsClient) if err != nil { err = errors.Append(err, fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) - if _flagClusterConfig != "" { - err = errors.Wrap(err, _flagClusterConfig) - } - return nil, err + return nil, errors.Wrap(err, clusterConfigFile) } confirmInstallClusterConfig(clusterConfig, awsClient, disallowPrompt) @@ -204,53 +150,28 @@ func getInstallClusterConfig(awsClient *aws.Client, accessConfig clusterconfig.A return clusterConfig, nil } -func getConfigureClusterConfig(cachedClusterConfig clusterconfig.Config, disallowPrompt bool) (*clusterconfig.Config, error) { +func getConfigureClusterConfig(cachedClusterConfig clusterconfig.Config, clusterConfigFile string, disallowPrompt bool) (*clusterconfig.Config, error) { userClusterConfig := &clusterconfig.Config{} var awsClient *aws.Client - if _flagClusterConfig == "" { - if disallowPrompt { - return nil, ErrorClusterConfigOrPromptsRequired() - } - - userClusterConfig = &cachedClusterConfig - err := clusterconfig.ConfigurePrompt(userClusterConfig, &cachedClusterConfig, false, disallowPrompt) - if err != nil { - return nil, err - } - - awsClient, err = newAWSClient(*userClusterConfig.Region) - if err != nil { - return nil, err - } - promptIfNotAdmin(awsClient, disallowPrompt) - - } else { - err := readUserClusterConfigFile(userClusterConfig) - if err != nil { - return nil, err - } - - userClusterConfig.ClusterName = cachedClusterConfig.ClusterName - userClusterConfig.Region = cachedClusterConfig.Region - awsClient, err = newAWSClient(*userClusterConfig.Region) - if err != nil { - return nil, err - } - promptIfNotAdmin(awsClient, disallowPrompt) + err := readUserClusterConfigFile(userClusterConfig, clusterConfigFile) + if err != nil { + return nil, err + } - err = setConfigFieldsFromCached(userClusterConfig, &cachedClusterConfig) - if err != nil { - return nil, err - } + userClusterConfig.ClusterName = cachedClusterConfig.ClusterName + userClusterConfig.Region = cachedClusterConfig.Region + awsClient, err = newAWSClient(userClusterConfig.Region) + if err != nil { + return nil, err + } + promptIfNotAdmin(awsClient, disallowPrompt) - err = clusterconfig.ConfigurePrompt(userClusterConfig, &cachedClusterConfig, true, disallowPrompt) - if err != nil { - return nil, err - } + err = setConfigFieldsFromCached(userClusterConfig, &cachedClusterConfig) + if err != nil { + return nil, err } - var err error userClusterConfig.Telemetry, err = readTelemetryConfig() if err != nil { return nil, err @@ -259,10 +180,7 @@ func getConfigureClusterConfig(cachedClusterConfig clusterconfig.Config, disallo err = userClusterConfig.Validate(awsClient) if err != nil { err = errors.Append(err, fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) - if _flagClusterConfig != "" { - err = errors.Wrap(err, _flagClusterConfig) - } - return nil, err + return nil, errors.Wrap(err, clusterConfigFile) } confirmConfigureClusterConfig(*userClusterConfig, disallowPrompt) @@ -276,8 +194,8 @@ func setConfigFieldsFromCached(userClusterConfig *clusterconfig.Config, cachedCl } userClusterConfig.Bucket = cachedClusterConfig.Bucket - if userClusterConfig.InstanceType != nil && *userClusterConfig.InstanceType != *cachedClusterConfig.InstanceType { - return clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.InstanceTypeKey, *cachedClusterConfig.InstanceType) + if userClusterConfig.InstanceType != cachedClusterConfig.InstanceType { + return clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.InstanceTypeKey, cachedClusterConfig.InstanceType) } userClusterConfig.InstanceType = cachedClusterConfig.InstanceType @@ -448,12 +366,11 @@ func setConfigFieldsFromCached(userClusterConfig *clusterconfig.Config, cachedCl return clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.ImageEventExporterKey, cachedClusterConfig.ImageEventExporter) } - if userClusterConfig.Spot != nil && *userClusterConfig.Spot != *cachedClusterConfig.Spot { - return clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.SpotKey, *cachedClusterConfig.Spot) + if userClusterConfig.Spot != cachedClusterConfig.Spot { + return clusterconfig.ErrorConfigCannotBeChangedOnUpdate(clusterconfig.SpotKey, cachedClusterConfig.Spot) } userClusterConfig.Spot = cachedClusterConfig.Spot - - if userClusterConfig.Spot != nil && *userClusterConfig.Spot { + if userClusterConfig.Spot { userClusterConfig.FillEmptySpotFields() } @@ -492,16 +409,16 @@ func setConfigFieldsFromCached(userClusterConfig *clusterconfig.Config, cachedCl } func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsClient *aws.Client, disallowPrompt bool) { - eksPrice := aws.EKSPrices[*clusterConfig.Region] - operatorInstancePrice := aws.InstanceMetadatas[*clusterConfig.Region]["t3.medium"].Price - operatorEBSPrice := aws.EBSMetadatas[*clusterConfig.Region]["gp2"].PriceGB * 20 / 30 / 24 - metricsEBSPrice := aws.EBSMetadatas[*clusterConfig.Region]["gp2"].PriceGB * 40 / 30 / 24 - nlbPrice := aws.NLBMetadatas[*clusterConfig.Region].Price - natUnitPrice := aws.NATMetadatas[*clusterConfig.Region].Price - apiInstancePrice := aws.InstanceMetadatas[*clusterConfig.Region][*clusterConfig.InstanceType].Price - apiEBSPrice := aws.EBSMetadatas[*clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceGB * float64(clusterConfig.InstanceVolumeSize) / 30 / 24 + eksPrice := aws.EKSPrices[clusterConfig.Region] + operatorInstancePrice := aws.InstanceMetadatas[clusterConfig.Region]["t3.medium"].Price + operatorEBSPrice := aws.EBSMetadatas[clusterConfig.Region]["gp2"].PriceGB * 20 / 30 / 24 + metricsEBSPrice := aws.EBSMetadatas[clusterConfig.Region]["gp2"].PriceGB * 40 / 30 / 24 + nlbPrice := aws.NLBMetadatas[clusterConfig.Region].Price + natUnitPrice := aws.NATMetadatas[clusterConfig.Region].Price + apiInstancePrice := aws.InstanceMetadatas[clusterConfig.Region][clusterConfig.InstanceType].Price + apiEBSPrice := aws.EBSMetadatas[clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceGB * float64(clusterConfig.InstanceVolumeSize) / 30 / 24 if clusterConfig.InstanceVolumeType.String() == "io1" && clusterConfig.InstanceVolumeIOPS != nil { - apiEBSPrice += aws.EBSMetadatas[*clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceIOPS * float64(*clusterConfig.InstanceVolumeIOPS) / 30 / 24 + apiEBSPrice += aws.EBSMetadatas[clusterConfig.Region][clusterConfig.InstanceVolumeType.String()].PriceIOPS * float64(*clusterConfig.InstanceVolumeIOPS) / 30 / 24 } var natTotalPrice float64 @@ -512,8 +429,8 @@ func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsClient } fixedPrice := eksPrice + 2*operatorInstancePrice + operatorEBSPrice + metricsEBSPrice + 2*nlbPrice + natTotalPrice - totalMinPrice := fixedPrice + float64(*clusterConfig.MinInstances)*(apiInstancePrice+apiEBSPrice) - totalMaxPrice := fixedPrice + float64(*clusterConfig.MaxInstances)*(apiInstancePrice+apiEBSPrice) + totalMinPrice := fixedPrice + float64(clusterConfig.MinInstances)*(apiInstancePrice+apiEBSPrice) + totalMaxPrice := fixedPrice + float64(clusterConfig.MaxInstances)*(apiInstancePrice+apiEBSPrice) headers := []table.Header{ {Title: "aws resource"}, @@ -525,25 +442,24 @@ func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsClient instanceStr := "instances" volumeStr := "volumes" - if *clusterConfig.MinInstances == 1 && *clusterConfig.MaxInstances == 1 { + if clusterConfig.MinInstances == 1 && clusterConfig.MaxInstances == 1 { instanceStr = "instance" volumeStr = "volume" } - workerInstanceStr := fmt.Sprintf("%d - %d %s %s for your apis", *clusterConfig.MinInstances, *clusterConfig.MaxInstances, *clusterConfig.InstanceType, instanceStr) - ebsInstanceStr := fmt.Sprintf("%d - %d %dgb ebs %s for your apis", *clusterConfig.MinInstances, *clusterConfig.MaxInstances, clusterConfig.InstanceVolumeSize, volumeStr) - if *clusterConfig.MinInstances == *clusterConfig.MaxInstances { - workerInstanceStr = fmt.Sprintf("%d %s %s for your apis", *clusterConfig.MinInstances, *clusterConfig.InstanceType, instanceStr) - ebsInstanceStr = fmt.Sprintf("%d %dgb ebs %s for your apis", *clusterConfig.MinInstances, clusterConfig.InstanceVolumeSize, volumeStr) + workerInstanceStr := fmt.Sprintf("%d - %d %s %s for your apis", clusterConfig.MinInstances, clusterConfig.MaxInstances, clusterConfig.InstanceType, instanceStr) + ebsInstanceStr := fmt.Sprintf("%d - %d %dgb ebs %s for your apis", clusterConfig.MinInstances, clusterConfig.MaxInstances, clusterConfig.InstanceVolumeSize, volumeStr) + if clusterConfig.MinInstances == clusterConfig.MaxInstances { + workerInstanceStr = fmt.Sprintf("%d %s %s for your apis", clusterConfig.MinInstances, clusterConfig.InstanceType, instanceStr) + ebsInstanceStr = fmt.Sprintf("%d %dgb ebs %s for your apis", clusterConfig.MinInstances, clusterConfig.InstanceVolumeSize, volumeStr) } workerPriceStr := s.DollarsMaxPrecision(apiInstancePrice) + " each" - isSpot := clusterConfig.Spot != nil && *clusterConfig.Spot - if isSpot { - spotPrice, err := awsClient.SpotInstancePrice(*clusterConfig.InstanceType) + if clusterConfig.Spot { + spotPrice, err := awsClient.SpotInstancePrice(clusterConfig.InstanceType) workerPriceStr += " (spot pricing unavailable)" if err == nil && spotPrice != 0 { workerPriceStr = fmt.Sprintf("%s - %s each (varies based on spot price)", s.DollarsMaxPrecision(spotPrice), s.DollarsMaxPrecision(apiInstancePrice)) - totalMinPrice = fixedPrice + float64(*clusterConfig.MinInstances)*(spotPrice+apiEBSPrice) + totalMinPrice = fixedPrice + float64(clusterConfig.MinInstances)*(spotPrice+apiEBSPrice) } } @@ -571,11 +487,11 @@ func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsClient if totalMinPrice != totalMaxPrice { priceStr = fmt.Sprintf("%s - %s", s.DollarsAndCents(totalMinPrice), s.DollarsAndCents(totalMaxPrice)) - if isSpot && *clusterConfig.MinInstances != *clusterConfig.MaxInstances { + if clusterConfig.Spot && clusterConfig.MinInstances != clusterConfig.MaxInstances { suffix = " based on cluster size and spot instance pricing/availability" - } else if isSpot && *clusterConfig.MinInstances == *clusterConfig.MaxInstances { + } else if clusterConfig.Spot && clusterConfig.MinInstances == clusterConfig.MaxInstances { suffix = " based on spot instance pricing/availability" - } else if !isSpot && *clusterConfig.MinInstances != *clusterConfig.MaxInstances { + } else if !clusterConfig.Spot && clusterConfig.MinInstances != clusterConfig.MaxInstances { suffix = " based on cluster size" } } @@ -596,7 +512,7 @@ func confirmInstallClusterConfig(clusterConfig *clusterconfig.Config, awsClient fmt.Print("warning: you've configured your cluster to be installed in an existing VPC; if your cluster doesn't spin up or function as expected, please double-check your VPC configuration (here are the requirements: https://eksctl.io/usage/vpc-networking/#use-existing-vpc-other-custom-configuration)\n\n") } - if isSpot && clusterConfig.SpotConfig.OnDemandBackup != nil && !*clusterConfig.SpotConfig.OnDemandBackup { + if clusterConfig.Spot && clusterConfig.SpotConfig.OnDemandBackup != nil && !*clusterConfig.SpotConfig.OnDemandBackup { if *clusterConfig.SpotConfig.OnDemandBaseCapacity == 0 && *clusterConfig.SpotConfig.OnDemandPercentageAboveBaseCapacity == 0 { fmt.Printf("warning: you've disabled on-demand instances (%s=0 and %s=0); spot instances are not guaranteed to be available so please take that into account for production clusters; see https://docs.cortex.dev/v/%s/ for more information\n\n", clusterconfig.OnDemandBaseCapacityKey, clusterconfig.OnDemandPercentageAboveBaseCapacityKey, consts.CortexVersionMinor) } else { @@ -615,7 +531,7 @@ func confirmConfigureClusterConfig(clusterConfig clusterconfig.Config, disallowP if !disallowPrompt { exitMessage := fmt.Sprintf("cluster configuration can be modified via the cluster config file; see https://docs.cortex.dev/v/%s/ for more information", consts.CortexVersionMinor) - prompt.YesOrExit(fmt.Sprintf("your cluster named \"%s\" in %s will be updated according to the configuration above, are you sure you want to continue?", clusterConfig.ClusterName, *clusterConfig.Region), "", exitMessage) + prompt.YesOrExit(fmt.Sprintf("your cluster named \"%s\" in %s will be updated according to the configuration above, are you sure you want to continue?", clusterConfig.ClusterName, clusterConfig.Region), "", exitMessage) } } @@ -634,9 +550,9 @@ func clusterConfigConfirmationStr(clusterConfig clusterconfig.Config) string { items.Add(clusterconfig.BucketUserKey, clusterConfig.Bucket) items.Add(clusterconfig.ClusterNameUserKey, clusterConfig.ClusterName) - items.Add(clusterconfig.InstanceTypeUserKey, *clusterConfig.InstanceType) - items.Add(clusterconfig.MinInstancesUserKey, *clusterConfig.MinInstances) - items.Add(clusterconfig.MaxInstancesUserKey, *clusterConfig.MaxInstances) + items.Add(clusterconfig.InstanceTypeUserKey, clusterConfig.InstanceType) + items.Add(clusterconfig.MinInstancesUserKey, clusterConfig.MinInstances) + items.Add(clusterconfig.MaxInstancesUserKey, clusterConfig.MaxInstances) items.Add(clusterconfig.TagsUserKey, s.ObjFlatNoQuotes(clusterConfig.Tags)) if clusterConfig.SSLCertificateARN != nil { items.Add(clusterconfig.SSLCertificateARNUserKey, *clusterConfig.SSLCertificateARN) @@ -668,12 +584,12 @@ func clusterConfigConfirmationStr(clusterConfig clusterconfig.Config) string { items.Add(clusterconfig.OperatorLoadBalancerSchemeUserKey, clusterConfig.OperatorLoadBalancerScheme) } - if clusterConfig.Spot != nil && *clusterConfig.Spot != *defaultConfig.Spot { - items.Add(clusterconfig.SpotUserKey, s.YesNo(clusterConfig.Spot != nil && *clusterConfig.Spot)) + if clusterConfig.Spot != defaultConfig.Spot { + items.Add(clusterconfig.SpotUserKey, s.YesNo(clusterConfig.Spot)) if clusterConfig.SpotConfig != nil { defaultSpotConfig := clusterconfig.SpotConfig{} - clusterconfig.AutoGenerateSpotConfig(&defaultSpotConfig, *clusterConfig.Region, *clusterConfig.InstanceType) + clusterconfig.AutoGenerateSpotConfig(&defaultSpotConfig, clusterConfig.Region, clusterConfig.InstanceType) if !strset.New(clusterConfig.SpotConfig.InstanceDistribution...).IsEqual(strset.New(defaultSpotConfig.InstanceDistribution...)) { items.Add(clusterconfig.InstanceDistributionUserKey, clusterConfig.SpotConfig.InstanceDistribution) diff --git a/cli/cmd/lib_manager.go b/cli/cmd/lib_manager.go index 220d6a5f5d..6d46ece768 100644 --- a/cli/cmd/lib_manager.go +++ b/cli/cmd/lib_manager.go @@ -166,7 +166,7 @@ func runManagerWithClusterConfig(entrypoint string, clusterConfig *clusterconfig return "", nil, errors.WithStack(err) } - cachedClusterConfigPath := cachedClusterConfigPath(clusterConfig.ClusterName, *clusterConfig.Region) + cachedClusterConfigPath := cachedClusterConfigPath(clusterConfig.ClusterName, clusterConfig.Region) if err := files.WriteFile(clusterConfigBytes, cachedClusterConfigPath); err != nil { return "", nil, err } @@ -294,8 +294,8 @@ func runManagerAccessCommand(entrypoint string, accessConfig clusterconfig.Acces "CORTEX_PROVIDER=aws", "AWS_ACCESS_KEY_ID=" + *awsClient.AccessKeyID(), "AWS_SECRET_ACCESS_KEY=" + *awsClient.SecretAccessKey(), - "CORTEX_CLUSTER_NAME=" + *accessConfig.ClusterName, - "CORTEX_REGION=" + *accessConfig.Region, + "CORTEX_CLUSTER_NAME=" + accessConfig.ClusterName, + "CORTEX_REGION=" + accessConfig.Region, "CORTEX_TELEMETRY_DISABLE=" + os.Getenv("CORTEX_TELEMETRY_DISABLE"), "CORTEX_TELEMETRY_SENTRY_DSN=" + os.Getenv("CORTEX_TELEMETRY_SENTRY_DSN"), "CORTEX_TELEMETRY_SEGMENT_WRITE_KEY=" + os.Getenv("CORTEX_TELEMETRY_SEGMENT_WRITE_KEY"), diff --git a/docs/clients/cli.md b/docs/clients/cli.md index f21fb6ec43..5b5b04a8c8 100644 --- a/docs/clients/cli.md +++ b/docs/clients/cli.md @@ -98,10 +98,9 @@ Flags: spin up a cluster on aws Usage: - cortex cluster up [flags] + cortex cluster up [CLUSTER_CONFIG_FILE] [flags] Flags: - -c, --config string path to a cluster configuration file --aws-key string aws access key id --aws-secret string aws secret access key --cluster-aws-key string aws access key id to be used by the cluster @@ -137,10 +136,9 @@ Flags: update a cluster's configuration Usage: - cortex cluster configure [flags] + cortex cluster configure [CLUSTER_CONFIG_FILE] [flags] Flags: - -c, --config string path to a cluster configuration file --aws-key string aws access key id --aws-secret string aws secret access key --cluster-aws-key string aws access key id to be used by the cluster @@ -165,6 +163,7 @@ Flags: --aws-key string aws access key id --aws-secret string aws secret access key -y, --yes skip prompts + --keep-volumes keep cortex provisioned persistent volumes -h, --help help for down ``` @@ -233,6 +232,7 @@ Flags: -p, --project string gcp project id -z, --zone string gcp zone of the cluster -y, --yes skip prompts + --keep-volumes keep cortex provisioned persistent volumes -h, --help help for down ``` diff --git a/pkg/operator/config/config.go b/pkg/operator/config/config.go index 9788f5388f..a73aeddb82 100644 --- a/pkg/operator/config/config.go +++ b/pkg/operator/config/config.go @@ -109,11 +109,11 @@ func Init() error { if errors.HasError(errs) { return errors.FirstError(errs...) } - awsInstanceMetadata := aws.InstanceMetadatas[*CoreConfig.Region][*managedConfig.InstanceType] + awsInstanceMetadata := aws.InstanceMetadatas[CoreConfig.Region][managedConfig.InstanceType] instanceMetadata = &awsInstanceMetadata } - AWS, err = aws.NewForRegion(*CoreConfig.Region) + AWS, err = aws.NewForRegion(CoreConfig.Region) if err != nil { return err } @@ -126,7 +126,7 @@ func Init() error { OperatorMetadata = &clusterconfig.OperatorMetadata{ APIVersion: consts.CortexVersion, OperatorID: hashedAccountID, - ClusterID: hash.String(CoreConfig.ClusterName + *CoreConfig.Region + hashedAccountID), + ClusterID: hash.String(CoreConfig.ClusterName + CoreConfig.Region + hashedAccountID), IsOperatorInCluster: strings.ToLower(os.Getenv("CORTEX_OPERATOR_IN_CLUSTER")) != "false", } diff --git a/pkg/operator/endpoints/info.go b/pkg/operator/endpoints/info.go index 7915610117..54fd3c8b71 100644 --- a/pkg/operator/endpoints/info.go +++ b/pkg/operator/endpoints/info.go @@ -97,7 +97,7 @@ func getNodeInfos() ([]schema.NodeInfo, int, error) { instanceType := node.Labels["beta.kubernetes.io/instance-type"] isSpot := strings.Contains(strings.ToLower(node.Labels["lifecycle"]), "spot") - price := aws.InstanceMetadatas[*config.CoreConfig.Region][instanceType].Price + price := aws.InstanceMetadatas[config.CoreConfig.Region][instanceType].Price if isSpot { if spotPrice, ok := spotPriceCache[instanceType]; ok { price = spotPrice diff --git a/pkg/operator/operator/cron.go b/pkg/operator/operator/cron.go index d355fe94c9..1a0b7ca2a2 100644 --- a/pkg/operator/operator/cron.go +++ b/pkg/operator/operator/cron.go @@ -129,7 +129,7 @@ func managedClusterTelemetry() (map[string]interface{}, error) { continue } - onDemandPrice := aws.InstanceMetadatas[*config.CoreConfig.Region][instanceType].Price + onDemandPrice := aws.InstanceMetadatas[config.CoreConfig.Region][instanceType].Price price := onDemandPrice if isSpot { spotPrice, err := config.AWS.SpotInstancePrice(instanceType) @@ -163,9 +163,9 @@ func managedClusterTelemetry() (map[string]interface{}, error) { instanceInfos[instanceInfosKey] = &info } - apiEBSPrice := aws.EBSMetadatas[*config.CoreConfig.Region][managedConfig.InstanceVolumeType.String()].PriceGB * float64(managedConfig.InstanceVolumeSize) / 30 / 24 + apiEBSPrice := aws.EBSMetadatas[config.CoreConfig.Region][managedConfig.InstanceVolumeType.String()].PriceGB * float64(managedConfig.InstanceVolumeSize) / 30 / 24 if managedConfig.InstanceVolumeType.String() == "io1" && managedConfig.InstanceVolumeIOPS != nil { - apiEBSPrice += aws.EBSMetadatas[*config.CoreConfig.Region][managedConfig.InstanceVolumeType.String()].PriceIOPS * float64(*managedConfig.InstanceVolumeIOPS) / 30 / 24 + apiEBSPrice += aws.EBSMetadatas[config.CoreConfig.Region][managedConfig.InstanceVolumeType.String()].PriceIOPS * float64(*managedConfig.InstanceVolumeIOPS) / 30 / 24 } var totalInstancePrice float64 @@ -178,7 +178,7 @@ func managedClusterTelemetry() (map[string]interface{}, error) { fixedPrice := clusterFixedPriceAWS() return map[string]interface{}{ - "region": *config.CoreConfig.Region, + "region": config.CoreConfig.Region, "instance_count": totalInstances, "instances": instanceInfos, "fixed_price": fixedPrice, @@ -190,12 +190,12 @@ func managedClusterTelemetry() (map[string]interface{}, error) { } func clusterFixedPriceAWS() float64 { - eksPrice := aws.EKSPrices[*config.CoreConfig.Region] - operatorInstancePrice := aws.InstanceMetadatas[*config.CoreConfig.Region]["t3.medium"].Price - operatorEBSPrice := aws.EBSMetadatas[*config.CoreConfig.Region]["gp2"].PriceGB * 20 / 30 / 24 - metricsEBSPrice := aws.EBSMetadatas[*config.CoreConfig.Region]["gp2"].PriceGB * 40 / 30 / 24 - nlbPrice := aws.NLBMetadatas[*config.CoreConfig.Region].Price - natUnitPrice := aws.NATMetadatas[*config.CoreConfig.Region].Price + eksPrice := aws.EKSPrices[config.CoreConfig.Region] + operatorInstancePrice := aws.InstanceMetadatas[config.CoreConfig.Region]["t3.medium"].Price + operatorEBSPrice := aws.EBSMetadatas[config.CoreConfig.Region]["gp2"].PriceGB * 20 / 30 / 24 + metricsEBSPrice := aws.EBSMetadatas[config.CoreConfig.Region]["gp2"].PriceGB * 40 / 30 / 24 + nlbPrice := aws.NLBMetadatas[config.CoreConfig.Region].Price + natUnitPrice := aws.NATMetadatas[config.CoreConfig.Region].Price var natTotalPrice float64 managedConfig := config.ManagedConfigOrNil() diff --git a/pkg/types/clusterconfig/availability_zones.go b/pkg/types/clusterconfig/availability_zones.go index c89141774d..fe73523958 100644 --- a/pkg/types/clusterconfig/availability_zones.go +++ b/pkg/types/clusterconfig/availability_zones.go @@ -41,7 +41,7 @@ func (cc *Config) setAvailabilityZones(awsClient *aws.Client) error { } func (cc *Config) setDefaultAvailabilityZones(awsClient *aws.Client, extraInstances ...string) error { - zones, err := awsClient.ListSupportedAvailabilityZones(*cc.InstanceType, extraInstances...) + zones, err := awsClient.ListSupportedAvailabilityZones(cc.InstanceType, extraInstances...) if err != nil { // Try again without checking instance types zones, err = awsClient.ListAvailabilityZonesInRegion() @@ -53,7 +53,7 @@ func (cc *Config) setDefaultAvailabilityZones(awsClient *aws.Client, extraInstan zones.Subtract(_azBlacklist) if len(zones) < 2 { - return ErrorNotEnoughDefaultSupportedZones(awsClient.Region, zones, *cc.InstanceType, extraInstances...) + return ErrorNotEnoughDefaultSupportedZones(awsClient.Region, zones, cc.InstanceType, extraInstances...) } // See https://github.com/weaveworks/eksctl/blob/master/pkg/eks/api.go @@ -80,7 +80,7 @@ func (cc *Config) validateUserAvailabilityZones(awsClient *aws.Client, extraInst } } - supportedZones, err := awsClient.ListSupportedAvailabilityZones(*cc.InstanceType, extraInstances...) + supportedZones, err := awsClient.ListSupportedAvailabilityZones(cc.InstanceType, extraInstances...) if err != nil { // Skip validation instance-based validation supportedZones = strset.Difference(allZones, _azBlacklist) @@ -88,7 +88,7 @@ func (cc *Config) validateUserAvailabilityZones(awsClient *aws.Client, extraInst for _, userZone := range cc.AvailabilityZones { if !supportedZones.Has(userZone) { - return ErrorUnsupportedAvailabilityZone(userZone, *cc.InstanceType, extraInstances...) + return ErrorUnsupportedAvailabilityZone(userZone, cc.InstanceType, extraInstances...) } } @@ -109,7 +109,7 @@ func (cc *Config) validateSubnets(awsClient *aws.Client) error { for i, subnetConfig := range cc.Subnets { if !allZones.Has(subnetConfig.AvailabilityZone) { - return errors.Wrap(ErrorInvalidAvailabilityZone(subnetConfig.AvailabilityZone, allZones, *cc.Region), s.Index(i), AvailabilityZoneKey) + return errors.Wrap(ErrorInvalidAvailabilityZone(subnetConfig.AvailabilityZone, allZones, cc.Region), s.Index(i), AvailabilityZoneKey) } if userZones.Has(subnetConfig.AvailabilityZone) { return ErrorAvailabilityZoneSpecifiedTwice(subnetConfig.AvailabilityZone) diff --git a/pkg/types/clusterconfig/cluster_config_aws.go b/pkg/types/clusterconfig/cluster_config_aws.go index dae26b0f61..3e9e2e0ec2 100644 --- a/pkg/types/clusterconfig/cluster_config_aws.go +++ b/pkg/types/clusterconfig/cluster_config_aws.go @@ -33,7 +33,6 @@ import ( "github.com/cortexlabs/cortex/pkg/lib/hash" libmath "github.com/cortexlabs/cortex/pkg/lib/math" "github.com/cortexlabs/cortex/pkg/lib/pointer" - "github.com/cortexlabs/cortex/pkg/lib/prompt" "github.com/cortexlabs/cortex/pkg/lib/sets/strset" s "github.com/cortexlabs/cortex/pkg/lib/strings" "github.com/cortexlabs/cortex/pkg/lib/table" @@ -56,7 +55,7 @@ var ( type CoreConfig struct { Bucket string `json:"bucket" yaml:"bucket"` ClusterName string `json:"cluster_name" yaml:"cluster_name"` - Region *string `json:"region" yaml:"region"` + Region string `json:"region" yaml:"region"` Provider types.ProviderType `json:"provider" yaml:"provider"` Telemetry bool `json:"telemetry" yaml:"telemetry"` IsManaged bool `json:"is_managed" yaml:"is_managed"` @@ -88,14 +87,14 @@ type CoreConfig struct { } type ManagedConfig struct { - InstanceType *string `json:"instance_type" yaml:"instance_type"` - MinInstances *int64 `json:"min_instances" yaml:"min_instances"` - MaxInstances *int64 `json:"max_instances" yaml:"max_instances"` + InstanceType string `json:"instance_type" yaml:"instance_type"` + MinInstances int64 `json:"min_instances" yaml:"min_instances"` + MaxInstances int64 `json:"max_instances" yaml:"max_instances"` InstanceVolumeSize int64 `json:"instance_volume_size" yaml:"instance_volume_size"` InstanceVolumeType VolumeType `json:"instance_volume_type" yaml:"instance_volume_type"` InstanceVolumeIOPS *int64 `json:"instance_volume_iops" yaml:"instance_volume_iops"` Tags map[string]string `json:"tags" yaml:"tags"` - Spot *bool `json:"spot" yaml:"spot"` + Spot bool `json:"spot" yaml:"spot"` SpotConfig *SpotConfig `json:"spot_config" yaml:"spot_config"` AvailabilityZones []string `json:"availability_zones" yaml:"availability_zones"` SSLCertificateARN *string `json:"ssl_certificate_arn,omitempty" yaml:"ssl_certificate_arn,omitempty"` @@ -146,9 +145,9 @@ type InternalConfig struct { // The bare minimum to identify a cluster type AccessConfig struct { - ClusterName *string `json:"cluster_name" yaml:"cluster_name"` - Region *string `json:"region" yaml:"region"` - ImageManager string `json:"image_manager" yaml:"image_manager"` + ClusterName string `json:"cluster_name" yaml:"cluster_name"` + Region string `json:"region" yaml:"region"` + ImageManager string `json:"image_manager" yaml:"image_manager"` } func ValidateRegion(region string) error { @@ -159,6 +158,9 @@ func ValidateRegion(region string) error { } func RegionValidator(region string) (string, error) { + if region == "" { + return "", ErrorFieldCannotBeEmpty(RegionKey) + } if err := ValidateRegion(region); err != nil { return "", err } @@ -187,7 +189,7 @@ var CoreConfigStructFieldValidations = []*cr.StructFieldValidation{ }, { StructField: "Region", - StringPtrValidation: &cr.StringPtrValidation{ + StringValidation: &cr.StringValidation{ Validator: RegionValidator, }, }, @@ -382,19 +384,21 @@ var CoreConfigStructFieldValidations = []*cr.StructFieldValidation{ var ManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ { StructField: "InstanceType", - StringPtrValidation: &cr.StringPtrValidation{ + StringValidation: &cr.StringValidation{ Validator: validateInstanceType, }, }, { StructField: "MinInstances", - Int64PtrValidation: &cr.Int64PtrValidation{ + Int64Validation: &cr.Int64Validation{ + Default: int64(1), GreaterThanOrEqualTo: pointer.Int64(0), }, }, { StructField: "MaxInstances", - Int64PtrValidation: &cr.Int64PtrValidation{ + Int64Validation: &cr.Int64Validation{ + Default: int64(5), GreaterThan: pointer.Int64(0), }, }, @@ -472,8 +476,8 @@ var ManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ }, { StructField: "Spot", - BoolPtrValidation: &cr.BoolPtrValidation{ - Default: pointer.Bool(false), + BoolValidation: &cr.BoolValidation{ + Default: false, }, }, { @@ -643,7 +647,7 @@ var AccessValidation = &cr.StructValidation{ StructFieldValidations: []*cr.StructFieldValidation{ { StructField: "ClusterName", - StringPtrValidation: &cr.StringPtrValidation{ + StringValidation: &cr.StringValidation{ MaxLength: 63, MinLength: 3, Validator: validateClusterName, @@ -651,7 +655,7 @@ var AccessValidation = &cr.StructValidation{ }, { StructField: "Region", - StringPtrValidation: &cr.StringPtrValidation{ + StringValidation: &cr.StringValidation{ Validator: RegionValidator, }, }, @@ -666,11 +670,9 @@ var AccessValidation = &cr.StructValidation{ } func (cc *Config) ToAccessConfig() AccessConfig { - clusterName := cc.ClusterName - region := *cc.Region return AccessConfig{ - ClusterName: &clusterName, - Region: ®ion, + ClusterName: cc.ClusterName, + Region: cc.Region, ImageManager: cc.ImageManager, } } @@ -694,8 +696,8 @@ func (cc *CoreConfig) SQSNamePrefix() string { func (cc *Config) Validate(awsClient *aws.Client) error { fmt.Print("verifying your configuration ...\n\n") - if *cc.MinInstances > *cc.MaxInstances { - return ErrorMinInstancesGreaterThanMax(*cc.MinInstances, *cc.MaxInstances) + if cc.MinInstances > cc.MaxInstances { + return ErrorMinInstancesGreaterThanMax(cc.MinInstances, cc.MaxInstances) } if len(cc.AvailabilityZones) > 0 && len(cc.Subnets) > 0 { @@ -716,7 +718,7 @@ func (cc *Config) Validate(awsClient *aws.Client) error { } if cc.Bucket == "" { - bucketID := hash.String(accountID + *cc.Region)[:10] + bucketID := hash.String(accountID + cc.Region)[:10] defaultBucket := cc.ClusterName + "-" + bucketID if len(defaultBucket) > 63 { @@ -729,12 +731,12 @@ func (cc *Config) Validate(awsClient *aws.Client) error { cc.Bucket = defaultBucket } else { bucketRegion, _ := aws.GetBucketRegion(cc.Bucket) - if bucketRegion != "" && bucketRegion != *cc.Region { // if the bucket didn't exist, we will create it in the correct region, so there is no error - return ErrorS3RegionDiffersFromCluster(cc.Bucket, bucketRegion, *cc.Region) + if bucketRegion != "" && bucketRegion != cc.Region { // if the bucket didn't exist, we will create it in the correct region, so there is no error + return ErrorS3RegionDiffersFromCluster(cc.Bucket, bucketRegion, cc.Region) } } - cc.CortexPolicyARN = DefaultPolicyARN(accountID, cc.ClusterName, *cc.Region) + cc.CortexPolicyARN = DefaultPolicyARN(accountID, cc.ClusterName, cc.Region) for _, policyARN := range cc.IAMPolicyARNs { _, err := awsClient.IAM().GetPolicy(&iam.GetPolicyInput{ @@ -748,9 +750,9 @@ func (cc *Config) Validate(awsClient *aws.Client) error { } } - primaryInstanceType := *cc.InstanceType - if _, ok := aws.InstanceMetadatas[*cc.Region][primaryInstanceType]; !ok { - return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(primaryInstanceType, *cc.Region), InstanceTypeKey) + primaryInstanceType := cc.InstanceType + if _, ok := aws.InstanceMetadatas[cc.Region][primaryInstanceType]; !ok { + return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(primaryInstanceType, cc.Region), InstanceTypeKey) } if cc.SSLCertificateARN != nil { @@ -760,7 +762,7 @@ func (cc *Config) Validate(awsClient *aws.Client) error { } if !exists { - return errors.Wrap(ErrorSSLCertificateARNNotFound(*cc.SSLCertificateARN, *cc.Region), SSLCertificateARNKey) + return errors.Wrap(ErrorSSLCertificateARNNotFound(*cc.SSLCertificateARN, cc.Region), SSLCertificateARNKey) } } @@ -775,7 +777,7 @@ func (cc *Config) Validate(awsClient *aws.Client) error { } } - if aws.EBSMetadatas[*cc.Region][cc.InstanceVolumeType.String()].IOPSConfigurable && cc.InstanceVolumeIOPS == nil { + if aws.EBSMetadatas[cc.Region][cc.InstanceVolumeType.String()].IOPSConfigurable && cc.InstanceVolumeIOPS == nil { cc.InstanceVolumeIOPS = pointer.Int64(libmath.MinInt64(cc.InstanceVolumeSize*50, 3000)) } @@ -819,20 +821,20 @@ func (cc *Config) Validate(awsClient *aws.Client) error { } } - if cc.Spot != nil && *cc.Spot { + if cc.Spot { cc.FillEmptySpotFields() - primaryInstance := aws.InstanceMetadatas[*cc.Region][primaryInstanceType] + primaryInstance := aws.InstanceMetadatas[cc.Region][primaryInstanceType] for _, instanceType := range cc.SpotConfig.InstanceDistribution { if instanceType == primaryInstanceType { continue } - if _, ok := aws.InstanceMetadatas[*cc.Region][instanceType]; !ok { - return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(instanceType, *cc.Region), SpotConfigKey, InstanceDistributionKey) + if _, ok := aws.InstanceMetadatas[cc.Region][instanceType]; !ok { + return errors.Wrap(ErrorInstanceTypeNotSupportedInRegion(instanceType, cc.Region), SpotConfigKey, InstanceDistributionKey) } - instanceMetadata := aws.InstanceMetadatas[*cc.Region][instanceType] + instanceMetadata := aws.InstanceMetadatas[cc.Region][instanceType] err := CheckSpotInstanceCompatibility(primaryInstance, instanceMetadata) if err != nil { return errors.Wrap(err, SpotConfigKey, InstanceDistributionKey) @@ -846,8 +848,8 @@ func (cc *Config) Validate(awsClient *aws.Client) error { } } - if cc.SpotConfig.OnDemandBaseCapacity != nil && *cc.SpotConfig.OnDemandBaseCapacity > *cc.MaxInstances { - return ErrorOnDemandBaseCapacityGreaterThanMax(*cc.SpotConfig.OnDemandBaseCapacity, *cc.MaxInstances) + if cc.SpotConfig.OnDemandBaseCapacity != nil && *cc.SpotConfig.OnDemandBaseCapacity > cc.MaxInstances { + return ErrorOnDemandBaseCapacityGreaterThanMax(*cc.SpotConfig.OnDemandBaseCapacity, cc.MaxInstances) } } else { if cc.SpotConfig != nil { @@ -982,186 +984,7 @@ func (cc *Config) FillEmptySpotFields() { if cc.SpotConfig == nil { cc.SpotConfig = &SpotConfig{} } - AutoGenerateSpotConfig(cc.SpotConfig, *cc.Region, *cc.InstanceType) -} - -func applyPromptDefaults(defaults Config) *Config { - defaultConfig := &Config{ - CoreConfig: CoreConfig{ - Region: pointer.String("us-east-1"), - }, - ManagedConfig: ManagedConfig{ - InstanceType: pointer.String("m5.large"), - MinInstances: pointer.Int64(1), - MaxInstances: pointer.Int64(5), - }, - } - - if defaults.Region != nil { - defaultConfig.Region = defaults.Region - } - if defaults.InstanceType != nil { - defaultConfig.InstanceType = defaults.InstanceType - } - if defaults.MinInstances != nil { - defaultConfig.MinInstances = defaults.MinInstances - } - if defaults.MaxInstances != nil { - defaultConfig.MaxInstances = defaults.MaxInstances - } - - return defaultConfig -} - -func InstallPrompt(clusterConfig *Config, disallowPrompt bool) error { - defaults := applyPromptDefaults(*clusterConfig) - - if disallowPrompt { - if clusterConfig.Region == nil { - clusterConfig.Region = defaults.Region - } - 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{ - SkipNonEmptyFields: true, - PromptItemValidations: []*cr.PromptItemValidation{ - { - StructField: "InstanceType", - PromptOpts: &prompt.Options{ - Prompt: "instance type", - }, - StringPtrValidation: &cr.StringPtrValidation{ - Required: true, - Default: defaults.InstanceType, - Validator: validateInstanceType, - }, - }, - { - StructField: "MinInstances", - PromptOpts: &prompt.Options{ - Prompt: "min instances", - }, - Int64PtrValidation: &cr.Int64PtrValidation{ - Required: true, - Default: defaults.MinInstances, - GreaterThanOrEqualTo: pointer.Int64(0), - }, - }, - { - StructField: "MaxInstances", - PromptOpts: &prompt.Options{ - Prompt: "max instances", - }, - Int64PtrValidation: &cr.Int64PtrValidation{ - Required: true, - Default: defaults.MaxInstances, - GreaterThan: pointer.Int64(0), - }, - }, - }, - } - - err := cr.ReadPrompt(clusterConfig, remainingPrompts) - if err != nil { - return err - } - - return nil -} - -func ConfigurePrompt(userClusterConfig *Config, cachedClusterConfig *Config, skipPopulatedFields bool, disallowPrompt bool) error { - defaults := applyPromptDefaults(*cachedClusterConfig) - - 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{ - { - StructField: "MinInstances", - PromptOpts: &prompt.Options{ - Prompt: "min instances", - }, - Int64PtrValidation: &cr.Int64PtrValidation{ - Required: true, - Default: defaults.MinInstances, - GreaterThanOrEqualTo: pointer.Int64(0), - }, - }, - { - StructField: "MaxInstances", - PromptOpts: &prompt.Options{ - Prompt: "max instances", - }, - Int64PtrValidation: &cr.Int64PtrValidation{ - Required: true, - Default: defaults.MaxInstances, - GreaterThan: pointer.Int64(0), - }, - }, - }, - } - - err := cr.ReadPrompt(userClusterConfig, remainingPrompts) - if err != nil { - return err - } - - return nil -} - -var AccessPromptValidation = &cr.PromptValidation{ - SkipNonNilFields: true, - PromptItemValidations: []*cr.PromptItemValidation{ - { - StructField: "ClusterName", - PromptOpts: &prompt.Options{ - Prompt: ClusterNameUserKey, - }, - StringPtrValidation: &cr.StringPtrValidation{ - Default: pointer.String("cortex"), - MaxLength: 63, - MinLength: 3, - Validator: validateClusterName, - }, - }, - { - StructField: "Region", - PromptOpts: &prompt.Options{ - Prompt: RegionUserKey, - }, - StringPtrValidation: &cr.StringPtrValidation{ - Validator: RegionValidator, - Default: pointer.String("us-east-1"), - }, - }, - }, + AutoGenerateSpotConfig(cc.SpotConfig, cc.Region, cc.InstanceType) } func validateBucketNameOrEmpty(bucket string) (string, error) { @@ -1188,6 +1011,10 @@ func validateVPCCIDR(cidr string) (string, error) { } func validateInstanceType(instanceType string) (string, error) { + if instanceType == "" { + return "", ErrorFieldCannotBeEmpty(RegionKey) + } + var foundInstance *aws.InstanceMetadata for _, instanceMap := range aws.InstanceMetadatas { if instanceMetadata, ok := instanceMap[instanceType]; ok { @@ -1250,35 +1077,25 @@ func DefaultAccessConfig() (*AccessConfig, error) { } func (cc *Config) MaxPossibleOnDemandInstances() int64 { - if cc.MaxInstances == nil { - return 0 // unexpected - } - - if cc.Spot == nil || *cc.Spot == false || cc.SpotConfig == nil || cc.SpotConfig.OnDemandBackup == nil || *cc.SpotConfig.OnDemandBackup == true { - return *cc.MaxInstances + if cc.Spot == false || cc.SpotConfig == nil || cc.SpotConfig.OnDemandBackup == nil || *cc.SpotConfig.OnDemandBackup == true { + return cc.MaxInstances } onDemandBaseCap, onDemandPctAboveBaseCap := cc.SpotConfigOnDemandValues() - - return onDemandBaseCap + int64(math.Ceil(float64(onDemandPctAboveBaseCap)/100*float64(*cc.MaxInstances-onDemandBaseCap))) + return onDemandBaseCap + int64(math.Ceil(float64(onDemandPctAboveBaseCap)/100*float64(cc.MaxInstances-onDemandBaseCap))) } func (cc *Config) MaxPossibleSpotInstances() int64 { - if cc.MaxInstances == nil { - return 0 // unexpected - } - - if cc.Spot == nil || *cc.Spot == false { + if cc.Spot == false { return 0 } if cc.SpotConfig == nil { - return *cc.MaxInstances + return cc.MaxInstances } onDemandBaseCap, onDemandPctAboveBaseCap := cc.SpotConfigOnDemandValues() - - return *cc.MaxInstances - onDemandBaseCap - int64(math.Floor(float64(onDemandPctAboveBaseCap)/100*float64(*cc.MaxInstances-onDemandBaseCap))) + return cc.MaxInstances - onDemandBaseCap - int64(math.Floor(float64(onDemandPctAboveBaseCap)/100*float64(cc.MaxInstances-onDemandBaseCap))) } func (cc *Config) SpotConfigOnDemandValues() (int64, int64) { @@ -1314,7 +1131,7 @@ func (cc *CoreConfig) UserTable() table.KeyValuePairs { var items table.KeyValuePairs items.Add(ClusterNameUserKey, cc.ClusterName) - items.Add(RegionUserKey, *cc.Region) + items.Add(RegionUserKey, cc.Region) items.Add(BucketUserKey, cc.Bucket) items.Add(TelemetryUserKey, cc.Telemetry) items.Add(ImageOperatorUserKey, cc.ImageOperator) @@ -1352,9 +1169,9 @@ func (mc *ManagedConfig) UserTable() table.KeyValuePairs { for _, subnetConfig := range mc.Subnets { items.Add("subnet in "+subnetConfig.AvailabilityZone, subnetConfig.SubnetID) } - items.Add(InstanceTypeUserKey, *mc.InstanceType) - items.Add(MinInstancesUserKey, *mc.MinInstances) - items.Add(MaxInstancesUserKey, *mc.MaxInstances) + items.Add(InstanceTypeUserKey, mc.InstanceType) + items.Add(MinInstancesUserKey, mc.MinInstances) + items.Add(MaxInstancesUserKey, mc.MaxInstances) items.Add(TagsUserKey, s.ObjFlat(mc.Tags)) if mc.SSLCertificateARN != nil { items.Add(SSLCertificateARNUserKey, *mc.SSLCertificateARN) @@ -1365,8 +1182,8 @@ func (mc *ManagedConfig) UserTable() table.KeyValuePairs { items.Add(InstanceVolumeSizeUserKey, mc.InstanceVolumeSize) items.Add(InstanceVolumeTypeUserKey, mc.InstanceVolumeType) items.Add(InstanceVolumeIOPSUserKey, mc.InstanceVolumeIOPS) - items.Add(SpotUserKey, s.YesNo(*mc.Spot)) - if mc.Spot != nil && *mc.Spot { + items.Add(SpotUserKey, s.YesNo(mc.Spot)) + if mc.Spot { items.Add(InstanceDistributionUserKey, mc.SpotConfig.InstanceDistribution) items.Add(OnDemandBaseCapacityUserKey, *mc.SpotConfig.OnDemandBaseCapacity) items.Add(OnDemandPercentageAboveBaseCapacityUserKey, *mc.SpotConfig.OnDemandPercentageAboveBaseCapacity) @@ -1417,10 +1234,7 @@ func (cc *CoreConfig) TelemetryEvent() map[string]interface{} { event["istio_namespace._is_custom"] = true } - if cc.Region != nil { - event["region._is_defined"] = true - event["region"] = *cc.Region - } + event["region"] = cc.Region if !strings.HasPrefix(cc.ImageOperator, "cortexlabs/") { event["image_operator._is_custom"] = true @@ -1495,18 +1309,9 @@ func (cc *CoreConfig) TelemetryEvent() map[string]interface{} { func (mc *ManagedConfig) TelemetryEvent() map[string]interface{} { event := map[string]interface{}{} - if mc.InstanceType != nil { - event["instance_type._is_defined"] = true - event["instance_type"] = *mc.InstanceType - } - if mc.MinInstances != nil { - event["min_instances._is_defined"] = true - event["min_instances"] = *mc.MinInstances - } - if mc.MaxInstances != nil { - event["max_instances._is_defined"] = true - event["max_instances"] = *mc.MaxInstances - } + event["instance_type"] = mc.InstanceType + event["min_instances"] = mc.MinInstances + event["max_instances"] = mc.MaxInstances event["instance_volume_size"] = mc.InstanceVolumeSize event["instance_volume_type"] = mc.InstanceVolumeType if mc.InstanceVolumeIOPS != nil { @@ -1545,10 +1350,7 @@ func (mc *ManagedConfig) TelemetryEvent() map[string]interface{} { if mc.VPCCIDR != nil { event["vpc_cidr._is_defined"] = true } - if mc.Spot != nil { - event["spot._is_defined"] = true - event["spot"] = *mc.Spot - } + event["spot"] = mc.Spot if mc.SpotConfig != nil { event["spot_config._is_defined"] = true if len(mc.SpotConfig.InstanceDistribution) > 0 { diff --git a/pkg/types/clusterconfig/errors.go b/pkg/types/clusterconfig/errors.go index a9f39b89b5..3497e94717 100644 --- a/pkg/types/clusterconfig/errors.go +++ b/pkg/types/clusterconfig/errors.go @@ -28,6 +28,7 @@ import ( ) const ( + ErrFieldCannotBeEmpty = "clusterconfig.field_cannot_be_empty" ErrInvalidRegion = "clusterconfig.invalid_region" ErrInstanceTypeTooSmall = "clusterconfig.instance_type_too_small" ErrMinInstancesGreaterThanMax = "clusterconfig.min_instances_greater_than_max" @@ -71,6 +72,13 @@ const ( ErrGCPIncompatibleInstanceTypeWithAccelerator = "clusterconfig.gcp_incompatible_instance_type_with_accelerator" ) +func ErrorFieldCannotBeEmpty(fieldName string) error { + return errors.WithStack(&errors.Error{ + Kind: ErrFieldCannotBeEmpty, + Message: fmt.Sprintf("%s field cannot be empty", fieldName), + }) +} + func ErrorInvalidRegion(region string) error { return errors.WithStack(&errors.Error{ Kind: ErrInvalidRegion, diff --git a/pkg/types/clusterstate/clusterstate.go b/pkg/types/clusterstate/clusterstate.go index 62cdafd67d..074104be9f 100644 --- a/pkg/types/clusterstate/clusterstate.go +++ b/pkg/types/clusterstate/clusterstate.go @@ -166,10 +166,10 @@ func getStatus(statusMap map[string]string, controlPlane string, clusterName str } func GetClusterState(awsClient *aws.Client, accessConfig *clusterconfig.AccessConfig) (*ClusterState, error) { - controlPlaneStackName := fmt.Sprintf(controlPlaneTemplate, *accessConfig.ClusterName) - operatorStackName := fmt.Sprintf(operatorTemplate, *accessConfig.ClusterName) - spotStackName := fmt.Sprintf(spotTemplate, *accessConfig.ClusterName) - onDemandStackName := fmt.Sprintf(onDemandTemplate, *accessConfig.ClusterName) + controlPlaneStackName := fmt.Sprintf(controlPlaneTemplate, accessConfig.ClusterName) + operatorStackName := fmt.Sprintf(operatorTemplate, accessConfig.ClusterName) + spotStackName := fmt.Sprintf(spotTemplate, accessConfig.ClusterName) + onDemandStackName := fmt.Sprintf(onDemandTemplate, accessConfig.ClusterName) nodeGroupStackNamesSet := strset.New(operatorStackName, spotStackName, onDemandStackName) @@ -200,7 +200,7 @@ func GetClusterState(awsClient *aws.Client, accessConfig *clusterconfig.AccessCo statusMap[operatorStackName] = string(StatusCreateFailedTimedOut) } - status, err := getStatus(statusMap, controlPlaneStackName, *accessConfig.ClusterName, *accessConfig.Region) + status, err := getStatus(statusMap, controlPlaneStackName, accessConfig.ClusterName, accessConfig.Region) if err != nil { return nil, err } From a33bbcd663510a21c8f422490d8813fe4c17c5c2 Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Thu, 4 Mar 2021 23:21:35 +0200 Subject: [PATCH 02/10] Remove cluster prompts for GCP --- Makefile | 12 +- cli/cmd/cluster.go | 2 +- cli/cmd/cluster_gcp.go | 55 ++--- cli/cmd/errors.go | 7 - cli/cmd/lib_cluster_config_aws.go | 5 +- cli/cmd/lib_cluster_config_gcp.go | 106 ++------- cli/cmd/lib_manager.go | 12 +- docs/clients/cli.md | 3 +- pkg/operator/config/config.go | 4 +- pkg/operator/operator/cron.go | 2 +- pkg/types/clusterconfig/cluster_config.go | 4 + pkg/types/clusterconfig/cluster_config_gcp.go | 223 +++++------------- 12 files changed, 126 insertions(+), 309 deletions(-) diff --git a/Makefile b/Makefile index b2b0e56232..df72788d8e 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ cluster-up-gcp: @$(MAKE) images-all-gcp @$(MAKE) cli @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true - @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp up --config=./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" + @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp up ./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" @$(MAKE) kubectl-gcp cluster-up-aws-y: @@ -87,7 +87,7 @@ cluster-up-gcp-y: @$(MAKE) images-all-gcp @$(MAKE) cli @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true - @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp up --config=./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" --yes + @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp up ./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" --yes @$(MAKE) kubectl-gcp cluster-down-aws: @@ -125,23 +125,23 @@ cluster-configure-aws: @$(MAKE) images-all-aws @$(MAKE) cli @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true - @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster configure --config=./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" + @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster configure ./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" # cluster-configure-gcp: # @$(MAKE) images-all-gcp # @$(MAKE) cli # @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true -# @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp configure --config=./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" +# @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp configure ./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" cluster-configure-aws-y: @$(MAKE) images-all-aws @$(MAKE) cli @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true - @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster configure --config=./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" --yes + @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-aws.yaml) && ./bin/cortex cluster configure ./dev/config/cluster-aws.yaml --configure-env="$$CORTEX_CLUSTER_NAME-aws" --aws-key="$$AWS_ACCESS_KEY_ID" --aws-secret="$$AWS_SECRET_ACCESS_KEY" --cluster-aws-key="$$CLUSTER_AWS_ACCESS_KEY_ID" --cluster-aws-secret="$$CLUSTER_AWS_SECRET_ACCESS_KEY" --yes # cluster-configure-gcp-y: # @$(MAKE) images-all-gcp # @$(MAKE) cli # @kill $(shell pgrep -f rerun) >/dev/null 2>&1 || true -# @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp configure --config=./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" --yes +# @eval $$(python3 ./manager/cluster_config_env.py ./dev/config/cluster-gcp.yaml) && ./bin/cortex cluster-gcp configure ./dev/config/cluster-gcp.yaml --configure-env="$$CORTEX_CLUSTER_NAME-gcp" --yes # stop the in-cluster operator operator-stop-aws: diff --git a/cli/cmd/cluster.go b/cli/cmd/cluster.go index 8e30e43674..560ef67a07 100644 --- a/cli/cmd/cluster.go +++ b/cli/cmd/cluster.go @@ -175,7 +175,7 @@ var _clusterUpCmd = &cobra.Command{ exit.Error(err) } - clusterConfig, err := getInstallClusterConfig(awsClient, *accessConfig, clusterConfigFile, _flagClusterDisallowPrompt) + clusterConfig, err := getInstallClusterConfig(awsClient, clusterConfigFile, _flagClusterDisallowPrompt) if err != nil { exit.Error(err) } diff --git a/cli/cmd/cluster_gcp.go b/cli/cmd/cluster_gcp.go index 9fd2c98935..e6db19ff7e 100644 --- a/cli/cmd/cluster_gcp.go +++ b/cli/cmd/cluster_gcp.go @@ -53,7 +53,6 @@ var ( func clusterGCPInit() { _clusterGCPUpCmd.Flags().SortFlags = false - addClusterGCPConfigFlag(_clusterGCPUpCmd) _clusterGCPUpCmd.Flags().StringVarP(&_flagClusterGCPUpEnv, "configure-env", "e", "gcp", "name of environment to configure") addClusterGCPDisallowPromptFlag(_clusterGCPUpCmd) _clusterGCPCmd.AddCommand(_clusterGCPUpCmd) @@ -105,12 +104,14 @@ var _clusterGCPCmd = &cobra.Command{ } var _clusterGCPUpCmd = &cobra.Command{ - Use: "up", + Use: "up [CLUSTER_CONFIG_FILE]", Short: "spin up a cluster on gcp", - Args: cobra.NoArgs, + Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { telemetry.EventNotify("cli.cluster.up", map[string]interface{}{"provider": types.GCPProviderType}) + clusterConfigFile := args[0] + envExists, err := isEnvConfigured(_flagClusterGCPUpEnv) if err != nil { exit.Error(err) @@ -127,32 +128,32 @@ var _clusterGCPUpCmd = &cobra.Command{ exit.Error(err) } - accessConfig, err := getNewGCPClusterAccessConfig(_flagClusterGCPDisallowPrompt) + accessConfig, err := getNewGCPClusterAccessConfig(clusterConfigFile) if err != nil { exit.Error(err) } - gcpClient, err := gcp.NewFromEnvCheckProjectID(*accessConfig.Project) + gcpClient, err := gcp.NewFromEnvCheckProjectID(accessConfig.Project) if err != nil { exit.Error(err) } - clusterConfig, err := getGCPInstallClusterConfig(gcpClient, *accessConfig, _flagClusterGCPDisallowPrompt) + clusterConfig, err := getGCPInstallClusterConfig(gcpClient, clusterConfigFile, _flagClusterGCPDisallowPrompt) if err != nil { exit.Error(err) } - gkeClusterName := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", *clusterConfig.Project, *clusterConfig.Zone, clusterConfig.ClusterName) + gkeClusterName := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", clusterConfig.Project, clusterConfig.Zone, clusterConfig.ClusterName) clusterExists, err := gcpClient.ClusterExists(gkeClusterName) if err != nil { exit.Error(err) } if clusterExists { - exit.Error(ErrorGCPClusterAlreadyExists(clusterConfig.ClusterName, *clusterConfig.Zone, *clusterConfig.Project)) + exit.Error(ErrorGCPClusterAlreadyExists(clusterConfig.ClusterName, clusterConfig.Zone, clusterConfig.Project)) } - err = createGSBucketIfNotFound(gcpClient, clusterConfig.Bucket, gcp.ZoneToRegion(*accessConfig.Zone)) + err = createGSBucketIfNotFound(gcpClient, clusterConfig.Bucket, gcp.ZoneToRegion(accessConfig.Zone)) if err != nil { exit.Error(err) } @@ -208,7 +209,7 @@ var _clusterGCPInfoCmd = &cobra.Command{ } // need to ensure that the google creds are configured for the manager - _, err = gcp.NewFromEnvCheckProjectID(*accessConfig.Project) + _, err = gcp.NewFromEnvCheckProjectID(accessConfig.Project) if err != nil { exit.Error(err) } @@ -237,13 +238,13 @@ var _clusterGCPDownCmd = &cobra.Command{ exit.Error(err) } - gcpClient, err := gcp.NewFromEnvCheckProjectID(*accessConfig.Project) + gcpClient, err := gcp.NewFromEnvCheckProjectID(accessConfig.Project) if err != nil { exit.Error(err) } - gkeClusterName := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", *accessConfig.Project, *accessConfig.Zone, *accessConfig.ClusterName) - bucketName := clusterconfig.GCPBucketName(*accessConfig.ClusterName, *accessConfig.Project, *accessConfig.Zone) + gkeClusterName := fmt.Sprintf("projects/%s/locations/%s/clusters/%s", accessConfig.Project, accessConfig.Zone, accessConfig.ClusterName) + bucketName := clusterconfig.GCPBucketName(accessConfig.ClusterName, accessConfig.Project, accessConfig.Zone) clusterExists, err := gcpClient.ClusterExists(gkeClusterName) if err != nil { @@ -251,16 +252,16 @@ var _clusterGCPDownCmd = &cobra.Command{ } if !clusterExists { gcpClient.DeleteBucket(bucketName) // silently try to delete the bucket in case it got left behind - exit.Error(ErrorGCPClusterDoesntExist(*accessConfig.ClusterName, *accessConfig.Zone, *accessConfig.Project)) + exit.Error(ErrorGCPClusterDoesntExist(accessConfig.ClusterName, accessConfig.Zone, accessConfig.Project)) } // updating CLI env is best-effort, so ignore errors operatorLoadBalancerIP, _ := getGCPOperatorLoadBalancerIP(gkeClusterName, gcpClient) if _flagClusterGCPDisallowPrompt { - fmt.Printf("your cluster named \"%s\" in %s (zone: %s) will be spun down and all apis will be deleted\n\n", *accessConfig.ClusterName, *accessConfig.Project, *accessConfig.Zone) + fmt.Printf("your cluster named \"%s\" in %s (zone: %s) will be spun down and all apis will be deleted\n\n", accessConfig.ClusterName, accessConfig.Project, accessConfig.Zone) } else { - prompt.YesOrExit(fmt.Sprintf("your cluster named \"%s\" in %s (zone: %s) will be spun down and all apis will be deleted, are you sure you want to continue?", *accessConfig.ClusterName, *accessConfig.Project, *accessConfig.Zone), "", "") + prompt.YesOrExit(fmt.Sprintf("your cluster named \"%s\" in %s (zone: %s) will be spun down and all apis will be deleted, are you sure you want to continue?", accessConfig.ClusterName, accessConfig.Project, accessConfig.Zone), "", "") } fmt.Print("○ spinning down the cluster ") @@ -276,12 +277,12 @@ var _clusterGCPDownCmd = &cobra.Command{ } fmt.Print("\n") - gkePvcDiskPrefix := fmt.Sprintf("gke-%s", *accessConfig.ClusterName) + gkePvcDiskPrefix := fmt.Sprintf("gke-%s", accessConfig.ClusterName) if err != nil { - fmt.Print(fmt.Sprintf("○ failed to delete persistent disks from storage, please visit https://console.cloud.google.com/compute/disks?project=%s to manually delete the disks starting with the %s prefix: %s", *accessConfig.Project, gkePvcDiskPrefix, err.Error())) + fmt.Print(fmt.Sprintf("○ failed to delete persistent disks from storage, please visit https://console.cloud.google.com/compute/disks?project=%s to manually delete the disks starting with the %s prefix: %s", accessConfig.Project, gkePvcDiskPrefix, err.Error())) telemetry.Error(ErrorClusterDown(err.Error())) } else { - fmt.Print(fmt.Sprintf("○ failed to delete persistent disks from storage, please visit https://console.cloud.google.com/compute/disks?project=%s to manually delete the disks starting with the %s prefix", *accessConfig.Project, gkePvcDiskPrefix)) + fmt.Print(fmt.Sprintf("○ failed to delete persistent disks from storage, please visit https://console.cloud.google.com/compute/disks?project=%s to manually delete the disks starting with the %s prefix", accessConfig.Project, gkePvcDiskPrefix)) telemetry.Error(ErrorClusterDown(output)) } @@ -320,7 +321,7 @@ var _clusterGCPDownCmd = &cobra.Command{ } } - cachedClusterConfigPath := cachedGCPClusterConfigPath(*accessConfig.ClusterName, *accessConfig.Project, *accessConfig.Zone) + cachedClusterConfigPath := cachedGCPClusterConfigPath(accessConfig.ClusterName, accessConfig.Project, accessConfig.Zone) os.Remove(cachedClusterConfigPath) }, } @@ -458,12 +459,12 @@ func createGKECluster(clusterConfig *clusterconfig.GCPConfig, gcpClient *gcp.Cli nodeLabels["nvidia.com/gpu"] = "present" } - gkeClusterParent := fmt.Sprintf("projects/%s/locations/%s", *clusterConfig.Project, *clusterConfig.Zone) + gkeClusterParent := fmt.Sprintf("projects/%s/locations/%s", clusterConfig.Project, clusterConfig.Zone) gkeClusterName := fmt.Sprintf("%s/clusters/%s", gkeClusterParent, clusterConfig.ClusterName) initialNodeCount := int64(1) - if *clusterConfig.MinInstances > 0 { - initialNodeCount = *clusterConfig.MinInstances + if clusterConfig.MinInstances > 0 { + initialNodeCount = clusterConfig.MinInstances } gkeClusterConfig := containerpb.Cluster{ @@ -484,14 +485,14 @@ func createGKECluster(clusterConfig *clusterconfig.GCPConfig, gcpClient *gcp.Cli InitialNodeCount: 2, }, }, - Locations: []string{*clusterConfig.Zone}, + Locations: []string{clusterConfig.Zone}, } if clusterConfig.Preemptible { gkeClusterConfig.NodePools = append(gkeClusterConfig.NodePools, &containerpb.NodePool{ Name: "ng-cortex-wk-preemp", Config: &containerpb.NodeConfig{ - MachineType: *clusterConfig.InstanceType, + MachineType: clusterConfig.InstanceType, Labels: nodeLabels, Taints: []*containerpb.NodeTaint{ { @@ -515,7 +516,7 @@ func createGKECluster(clusterConfig *clusterconfig.GCPConfig, gcpClient *gcp.Cli gkeClusterConfig.NodePools = append(gkeClusterConfig.NodePools, &containerpb.NodePool{ Name: "ng-cortex-wk-on-dmd", Config: &containerpb.NodeConfig{ - MachineType: *clusterConfig.InstanceType, + MachineType: clusterConfig.InstanceType, Labels: nodeLabels, Taints: []*containerpb.NodeTaint{ { @@ -566,7 +567,7 @@ func createGKECluster(clusterConfig *clusterconfig.GCPConfig, gcpClient *gcp.Cli if cluster.Status == containerpb.Cluster_ERROR { fmt.Println(" ✗") helpStr := fmt.Sprintf("\nyour cluster couldn't be spun up; here is the error that was encountered: %s", cluster.StatusMessage) - helpStr += fmt.Sprintf("\nadditional error information may be found on the cluster's page in the GCP console: https://console.cloud.google.com/kubernetes/clusters/details/%s/%s?project=%s", *clusterConfig.Zone, clusterConfig.ClusterName, *clusterConfig.Project) + helpStr += fmt.Sprintf("\nadditional error information may be found on the cluster's page in the GCP console: https://console.cloud.google.com/kubernetes/clusters/details/%s/%s?project=%s", clusterConfig.Zone, clusterConfig.ClusterName, clusterConfig.Project) fmt.Println(helpStr) exit.Error(ErrorClusterUp(cluster.StatusMessage)) } diff --git a/cli/cmd/errors.go b/cli/cmd/errors.go index 56b9b5396c..70590a8ad3 100644 --- a/cli/cmd/errors.go +++ b/cli/cmd/errors.go @@ -299,13 +299,6 @@ func ErrorClusterAccessConfigRequired() error { }) } -func ErrorGCPClusterAccessConfigOrPromptsRequired() error { - return errors.WithStack(&errors.Error{ - Kind: ErrGCPClusterAccessConfigOrPromptsRequired, - Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s`, `%s`, and `%s` (e.g. via `--config cluster.yaml`) or enable prompts (i.e. omit the `--yes` flag)", clusterconfig.ClusterNameKey, clusterconfig.ZoneKey, clusterconfig.ProjectKey), - }) -} - func ErrorShellCompletionNotSupported(shell string) error { return errors.WithStack(&errors.Error{ Kind: ErrShellCompletionNotSupported, diff --git a/cli/cmd/lib_cluster_config_aws.go b/cli/cmd/lib_cluster_config_aws.go index 30915056c7..7384c30082 100644 --- a/cli/cmd/lib_cluster_config_aws.go +++ b/cli/cmd/lib_cluster_config_aws.go @@ -121,7 +121,7 @@ func getClusterAccessConfigWithCache() (*clusterconfig.AccessConfig, error) { return accessConfig, nil } -func getInstallClusterConfig(awsClient *aws.Client, accessConfig clusterconfig.AccessConfig, clusterConfigFile string, disallowPrompt bool) (*clusterconfig.Config, error) { +func getInstallClusterConfig(awsClient *aws.Client, clusterConfigFile string, disallowPrompt bool) (*clusterconfig.Config, error) { clusterConfig := &clusterconfig.Config{} err := readUserClusterConfigFile(clusterConfig, clusterConfigFile) @@ -129,9 +129,6 @@ func getInstallClusterConfig(awsClient *aws.Client, accessConfig clusterconfig.A return nil, err } - clusterConfig.ClusterName = accessConfig.ClusterName - clusterConfig.Region = accessConfig.Region - promptIfNotAdmin(awsClient, disallowPrompt) clusterConfig.Telemetry, err = readTelemetryConfig() diff --git a/cli/cmd/lib_cluster_config_gcp.go b/cli/cmd/lib_cluster_config_gcp.go index 74ea115a53..8587e2edf5 100644 --- a/cli/cmd/lib_cluster_config_gcp.go +++ b/cli/cmd/lib_cluster_config_gcp.go @@ -27,7 +27,6 @@ import ( "github.com/cortexlabs/cortex/pkg/lib/errors" "github.com/cortexlabs/cortex/pkg/lib/files" "github.com/cortexlabs/cortex/pkg/lib/gcp" - "github.com/cortexlabs/cortex/pkg/lib/pointer" "github.com/cortexlabs/cortex/pkg/lib/prompt" "github.com/cortexlabs/cortex/pkg/types/clusterconfig" ) @@ -54,8 +53,8 @@ func existingCachedGCPClusterConfigPaths() []string { return matches } -func readUserGCPClusterConfigFile(clusterConfig *clusterconfig.GCPConfig) error { - errs := cr.ParseYAMLFile(clusterConfig, clusterconfig.GCPFullManagedValidation, _flagClusterGCPConfig) +func readUserGCPClusterConfigFile(clusterConfig *clusterconfig.GCPConfig, filePath string) error { + errs := cr.ParseYAMLFile(clusterConfig, clusterconfig.GCPFullManagedValidation, filePath) if errors.HasError(errs) { return errors.Append(errors.FirstError(errs...), fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) } @@ -63,49 +62,20 @@ func readUserGCPClusterConfigFile(clusterConfig *clusterconfig.GCPConfig) error return nil } -func getNewGCPClusterAccessConfig(disallowPrompt bool) (*clusterconfig.GCPAccessConfig, error) { - accessConfig, err := clusterconfig.DefaultGCPAccessConfig() - if err != nil { - return nil, err - } - - if _flagClusterGCPConfig != "" { - errs := cr.ParseYAMLFile(accessConfig, clusterconfig.GCPAccessValidation, _flagClusterGCPConfig) - if errors.HasError(errs) { - return nil, errors.Append(errors.FirstError(errs...), fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) - } - } +func getNewGCPClusterAccessConfig(clusterConfigFile string) (*clusterconfig.GCPAccessConfig, error) { + accessConfig := &clusterconfig.GCPAccessConfig{} - if _flagClusterGCPName != "" { - accessConfig.ClusterName = pointer.String(_flagClusterGCPName) - } - if _flagClusterGCPZone != "" { - accessConfig.Zone = pointer.String(_flagClusterGCPZone) - } - if _flagClusterGCPProject != "" { - accessConfig.Project = pointer.String(_flagClusterGCPProject) - } - - if accessConfig.ClusterName != nil && accessConfig.Zone != nil && accessConfig.Project != nil { - return accessConfig, nil - } - - if disallowPrompt { - return nil, ErrorGCPClusterAccessConfigOrPromptsRequired() - } - - err = cr.ReadPrompt(accessConfig, clusterconfig.GCPAccessPromptValidation) - if err != nil { - return nil, err + errs := cr.ParseYAMLFile(accessConfig, clusterconfig.GCPAccessValidation, clusterConfigFile) + if errors.HasError(errs) { + return nil, errors.Append(errors.FirstError(errs...), fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) } return accessConfig, nil } func getGCPClusterAccessConfigWithCache(disallowPrompt bool) (*clusterconfig.GCPAccessConfig, error) { - accessConfig, err := clusterconfig.DefaultGCPAccessConfig() - if err != nil { - return nil, err + accessConfig := &clusterconfig.GCPAccessConfig{ + ImageManager: "quay.io/cortexlabs/manager:" + consts.CortexVersion, } if _flagClusterGCPConfig != "" { @@ -116,66 +86,35 @@ func getGCPClusterAccessConfigWithCache(disallowPrompt bool) (*clusterconfig.GCP } if _flagClusterGCPName != "" { - accessConfig.ClusterName = pointer.String(_flagClusterGCPName) + accessConfig.ClusterName = _flagClusterGCPName } if _flagClusterGCPZone != "" { - accessConfig.Zone = pointer.String(_flagClusterGCPZone) + accessConfig.Zone = _flagClusterGCPZone } if _flagClusterGCPProject != "" { - accessConfig.Project = pointer.String(_flagClusterGCPProject) - } - - if accessConfig.ClusterName != nil && accessConfig.Zone != nil && accessConfig.Project != nil { - return accessConfig, nil + accessConfig.Project = _flagClusterGCPProject } cachedPaths := existingCachedGCPClusterConfigPaths() if len(cachedPaths) == 1 { cachedAccessConfig := &clusterconfig.GCPAccessConfig{} cr.ParseYAMLFile(cachedAccessConfig, clusterconfig.GCPAccessValidation, cachedPaths[0]) - if accessConfig.ClusterName == nil { - accessConfig.ClusterName = cachedAccessConfig.ClusterName - } - if accessConfig.Project == nil { - accessConfig.Project = cachedAccessConfig.Project - } - if accessConfig.Zone == nil { - accessConfig.Zone = cachedAccessConfig.Zone - } - } - - if disallowPrompt { - return nil, ErrorGCPClusterAccessConfigOrPromptsRequired() + accessConfig.ClusterName = cachedAccessConfig.ClusterName + accessConfig.Project = cachedAccessConfig.Project + accessConfig.Zone = cachedAccessConfig.Zone } - err = cr.ReadPrompt(accessConfig, clusterconfig.GCPAccessPromptValidation) - if err != nil { - return nil, err + if accessConfig.ClusterName == "" || accessConfig.Project == "" || accessConfig.Zone == "" { + return nil, ErrorClusterAccessConfigRequired() } return accessConfig, nil } -func getGCPInstallClusterConfig(gcpClient *gcp.Client, accessConfig clusterconfig.GCPAccessConfig, disallowPrompt bool) (*clusterconfig.GCPConfig, error) { +func getGCPInstallClusterConfig(gcpClient *gcp.Client, clusterConfigFile string, disallowPrompt bool) (*clusterconfig.GCPConfig, error) { clusterConfig := &clusterconfig.GCPConfig{} - err := clusterconfig.SetGCPDefaults(clusterConfig) - if err != nil { - return nil, err - } - - if _flagClusterGCPConfig != "" { - err := readUserGCPClusterConfigFile(clusterConfig) - if err != nil { - return nil, err - } - } - - clusterConfig.ClusterName = *accessConfig.ClusterName - clusterConfig.Zone = accessConfig.Zone - clusterConfig.Project = accessConfig.Project - - err = clusterconfig.InstallGCPPrompt(clusterConfig, disallowPrompt) + err := readUserGCPClusterConfigFile(clusterConfig, clusterConfigFile) if err != nil { return nil, err } @@ -188,10 +127,7 @@ func getGCPInstallClusterConfig(gcpClient *gcp.Client, accessConfig clusterconfi err = clusterConfig.Validate(gcpClient) if err != nil { err = errors.Append(err, fmt.Sprintf("\n\ncluster configuration schema can be found at https://docs.cortex.dev/v/%s/", consts.CortexVersionMinor)) - if _flagClusterGCPConfig != "" { - err = errors.Wrap(err, _flagClusterGCPConfig) - } - return nil, err + return nil, errors.Wrap(err, clusterConfigFile) } confirmGCPInstallClusterConfig(clusterConfig, disallowPrompt) @@ -200,7 +136,7 @@ func getGCPInstallClusterConfig(gcpClient *gcp.Client, accessConfig clusterconfi } func confirmGCPInstallClusterConfig(clusterConfig *clusterconfig.GCPConfig, disallowPrompt bool) { - fmt.Printf("a cluster named \"%s\" will be created in %s (zone: %s)\n\n", clusterConfig.ClusterName, *clusterConfig.Project, *clusterConfig.Zone) + fmt.Printf("a cluster named \"%s\" will be created in %s (zone: %s)\n\n", clusterConfig.ClusterName, clusterConfig.Project, clusterConfig.Zone) if !disallowPrompt { exitMessage := fmt.Sprintf("cluster configuration can be modified via the cluster config file; see https://docs.cortex.dev/v/%s/ for more information", consts.CortexVersionMinor) diff --git a/cli/cmd/lib_manager.go b/cli/cmd/lib_manager.go index 6d46ece768..4539821e80 100644 --- a/cli/cmd/lib_manager.go +++ b/cli/cmd/lib_manager.go @@ -221,7 +221,7 @@ func runGCPManagerWithClusterConfig(entrypoint string, clusterConfig *clustercon return "", nil, errors.WithStack(err) } - clusterConfigPath := cachedGCPClusterConfigPath(clusterConfig.ClusterName, *clusterConfig.Project, *clusterConfig.Zone) + clusterConfigPath := cachedGCPClusterConfigPath(clusterConfig.ClusterName, clusterConfig.Project, clusterConfig.Zone) if err := files.WriteFile(clusterConfigBytes, clusterConfigPath); err != nil { return "", nil, err } @@ -264,8 +264,8 @@ func runGCPManagerWithClusterConfig(entrypoint string, clusterConfig *clustercon Env: []string{ "CORTEX_PROVIDER=gcp", "GOOGLE_APPLICATION_CREDENTIALS=" + gcpCredsPath, - "CORTEX_GCP_PROJECT=" + *clusterConfig.Project, - "CORTEX_GCP_ZONE=" + *clusterConfig.Zone, + "CORTEX_GCP_PROJECT=" + clusterConfig.Project, + "CORTEX_GCP_ZONE=" + clusterConfig.Zone, "CORTEX_TELEMETRY_DISABLE=" + os.Getenv("CORTEX_TELEMETRY_DISABLE"), "CORTEX_TELEMETRY_SENTRY_DSN=" + os.Getenv("CORTEX_TELEMETRY_SENTRY_DSN"), "CORTEX_TELEMETRY_SEGMENT_WRITE_KEY=" + os.Getenv("CORTEX_TELEMETRY_SEGMENT_WRITE_KEY"), @@ -341,9 +341,9 @@ func runGCPManagerAccessCommand(entrypoint string, accessConfig clusterconfig.GC Env: []string{ "CORTEX_PROVIDER=gcp", "GOOGLE_APPLICATION_CREDENTIALS=" + gcpCredsPath, - "CORTEX_CLUSTER_NAME=" + *accessConfig.ClusterName, - "CORTEX_GCP_PROJECT=" + *accessConfig.Project, - "CORTEX_GCP_ZONE=" + *accessConfig.Zone, + "CORTEX_CLUSTER_NAME=" + accessConfig.ClusterName, + "CORTEX_GCP_PROJECT=" + accessConfig.Project, + "CORTEX_GCP_ZONE=" + accessConfig.Zone, "CORTEX_TELEMETRY_DISABLE=" + os.Getenv("CORTEX_TELEMETRY_DISABLE"), "CORTEX_TELEMETRY_SENTRY_DSN=" + os.Getenv("CORTEX_TELEMETRY_SENTRY_DSN"), "CORTEX_TELEMETRY_SEGMENT_WRITE_KEY=" + os.Getenv("CORTEX_TELEMETRY_SEGMENT_WRITE_KEY"), diff --git a/docs/clients/cli.md b/docs/clients/cli.md index 5b5b04a8c8..72da827da7 100644 --- a/docs/clients/cli.md +++ b/docs/clients/cli.md @@ -190,10 +190,9 @@ Flags: spin up a cluster on gcp Usage: - cortex cluster-gcp up [flags] + cortex cluster-gcp up [CLUSTER_CONFIG_FILE] [flags] Flags: - -c, --config string path to a cluster configuration file -e, --configure-env string name of environment to configure (default "gcp") -y, --yes skip prompts -h, --help help for up diff --git a/pkg/operator/config/config.go b/pkg/operator/config/config.go index a73aeddb82..d62b29701b 100644 --- a/pkg/operator/config/config.go +++ b/pkg/operator/config/config.go @@ -163,7 +163,7 @@ func Init() error { } } - GCP, err = gcp.NewFromEnvCheckProjectID(*GCPCoreConfig.Project) + GCP, err = gcp.NewFromEnvCheckProjectID(GCPCoreConfig.Project) if err != nil { return err } @@ -171,7 +171,7 @@ func Init() error { OperatorMetadata = &clusterconfig.OperatorMetadata{ APIVersion: consts.CortexVersion, OperatorID: GCP.HashedProjectID, - ClusterID: hash.String(GCPCoreConfig.ClusterName + *GCPCoreConfig.Project + *GCPCoreConfig.Zone), + ClusterID: hash.String(GCPCoreConfig.ClusterName + GCPCoreConfig.Project + GCPCoreConfig.Zone), IsOperatorInCluster: strings.ToLower(os.Getenv("CORTEX_OPERATOR_IN_CLUSTER")) != "false", } diff --git a/pkg/operator/operator/cron.go b/pkg/operator/operator/cron.go index 1a0b7ca2a2..eb84e2abf9 100644 --- a/pkg/operator/operator/cron.go +++ b/pkg/operator/operator/cron.go @@ -268,7 +268,7 @@ func gcpManagedClusterTelemetry() (map[string]interface{}, error) { } properties := map[string]interface{}{ - "zone": *config.GCPCoreConfig.Zone, + "zone": config.GCPCoreConfig.Zone, "instance_count": totalInstances, "instances": instanceInfos, } diff --git a/pkg/types/clusterconfig/cluster_config.go b/pkg/types/clusterconfig/cluster_config.go index 1c76009e6e..7339114ece 100644 --- a/pkg/types/clusterconfig/cluster_config.go +++ b/pkg/types/clusterconfig/cluster_config.go @@ -75,6 +75,10 @@ func specificProviderTypeValidator(expectedProvider types.ProviderType) func(str } func validateClusterName(clusterName string) (string, error) { + if clusterName == "" { + return "", ErrorFieldCannotBeEmpty(ClusterNameKey) + } + if !_strictS3BucketRegex.MatchString(clusterName) { return "", errors.Wrap(ErrorDidNotMatchStrictS3Regex(), clusterName) } diff --git a/pkg/types/clusterconfig/cluster_config_gcp.go b/pkg/types/clusterconfig/cluster_config_gcp.go index fb10c50b7c..01c3fd7785 100644 --- a/pkg/types/clusterconfig/cluster_config_gcp.go +++ b/pkg/types/clusterconfig/cluster_config_gcp.go @@ -35,8 +35,8 @@ import ( type GCPCoreConfig struct { Provider types.ProviderType `json:"provider" yaml:"provider"` - Project *string `json:"project" yaml:"project"` - Zone *string `json:"zone" yaml:"zone"` + Project string `json:"project" yaml:"project"` + Zone string `json:"zone" yaml:"zone"` ClusterName string `json:"cluster_name" yaml:"cluster_name"` Telemetry bool `json:"telemetry" yaml:"telemetry"` Namespace string `json:"namespace" yaml:"namespace"` @@ -66,15 +66,15 @@ type GCPCoreConfig struct { } type GCPManagedConfig struct { - InstanceType *string `json:"instance_type" yaml:"instance_type"` + InstanceType string `json:"instance_type" yaml:"instance_type"` AcceleratorType *string `json:"accelerator_type" yaml:"accelerator_type"` AcceleratorsPerInstance *int64 `json:"accelerators_per_instance" yaml:"accelerators_per_instance"` Network *string `json:"network" yaml:"network"` Subnet *string `json:"subnet" yaml:"subnet"` APILoadBalancerScheme LoadBalancerScheme `json:"api_load_balancer_scheme" yaml:"api_load_balancer_scheme"` OperatorLoadBalancerScheme LoadBalancerScheme `json:"operator_load_balancer_scheme" yaml:"operator_load_balancer_scheme"` - MinInstances *int64 `json:"min_instances" yaml:"min_instances"` - MaxInstances *int64 `json:"max_instances" yaml:"max_instances"` + MinInstances int64 `json:"min_instances" yaml:"min_instances"` + MaxInstances int64 `json:"max_instances" yaml:"max_instances"` Preemptible bool `json:"preemptible" yaml:"preemptible"` OnDemandBackup bool `json:"on_demand_backup" yaml:"on_demand_backup"` } @@ -93,10 +93,10 @@ type InternalGCPConfig struct { // The bare minimum to identify a cluster type GCPAccessConfig struct { - ClusterName *string `json:"cluster_name" yaml:"cluster_name"` - Project *string `json:"project" yaml:"project"` - Zone *string `json:"zone" yaml:"zone"` - ImageManager string `json:"image_manager" yaml:"image_manager"` + ClusterName string `json:"cluster_name" yaml:"cluster_name"` + Project string `json:"project" yaml:"project"` + Zone string `json:"zone" yaml:"zone"` + ImageManager string `json:"image_manager" yaml:"image_manager"` } var GCPCoreConfigStructFieldValidations = []*cr.StructFieldValidation{ @@ -120,12 +120,12 @@ var GCPCoreConfigStructFieldValidations = []*cr.StructFieldValidation{ }, }, { - StructField: "Project", - StringPtrValidation: &cr.StringPtrValidation{}, + StructField: "Project", + StringValidation: &cr.StringValidation{}, }, { - StructField: "Zone", - StringPtrValidation: &cr.StringPtrValidation{}, + StructField: "Zone", + StringValidation: &cr.StringValidation{}, }, { StructField: "IsManaged", @@ -295,20 +295,16 @@ var GCPCoreConfigStructFieldValidations = []*cr.StructFieldValidation{ var GCPManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ { - StructField: "InstanceType", - StringPtrValidation: &cr.StringPtrValidation{}, + StructField: "InstanceType", + StringValidation: &cr.StringValidation{}, }, { - StructField: "AcceleratorType", - StringPtrValidation: &cr.StringPtrValidation{ - AllowExplicitNull: true, - }, + StructField: "AcceleratorType", + StringPtrValidation: &cr.StringPtrValidation{}, }, { - StructField: "AcceleratorsPerInstance", - Int64PtrValidation: &cr.Int64PtrValidation{ - AllowExplicitNull: true, - }, + StructField: "AcceleratorsPerInstance", + Int64PtrValidation: &cr.Int64PtrValidation{}, DefaultDependentFields: []string{"AcceleratorType"}, DefaultDependentFieldsFunc: func(vals []interface{}) interface{} { acceleratorType := vals[0].(*string) @@ -352,13 +348,15 @@ var GCPManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ }, { StructField: "MinInstances", - Int64PtrValidation: &cr.Int64PtrValidation{ + Int64Validation: &cr.Int64Validation{ + Default: int64(1), GreaterThanOrEqualTo: pointer.Int64(0), }, }, { StructField: "MaxInstances", - Int64PtrValidation: &cr.Int64PtrValidation{ + Int64Validation: &cr.Int64Validation{ + Default: int64(5), GreaterThan: pointer.Int64(0), }, }, @@ -383,19 +381,19 @@ var GCPAccessValidation = &cr.StructValidation{ StructFieldValidations: []*cr.StructFieldValidation{ { StructField: "ClusterName", - StringPtrValidation: &cr.StringPtrValidation{ + StringValidation: &cr.StringValidation{ MaxLength: 63, MinLength: 3, Validator: validateClusterName, }, }, { - StructField: "Zone", - StringPtrValidation: &cr.StringPtrValidation{}, + StructField: "Zone", + StringValidation: &cr.StringValidation{}, }, { - StructField: "Project", - StringPtrValidation: &cr.StringPtrValidation{}, + StructField: "Project", + StringValidation: &cr.StringValidation{}, }, { StructField: "ImageManager", @@ -408,13 +406,10 @@ var GCPAccessValidation = &cr.StructValidation{ } func (cc *GCPConfig) ToAccessConfig() GCPAccessConfig { - clusterName := cc.ClusterName - zone := *cc.Zone - project := *cc.Project return GCPAccessConfig{ - ClusterName: &clusterName, - Zone: &zone, - Project: &project, + ClusterName: cc.ClusterName, + Zone: cc.Zone, + Project: cc.Project, ImageManager: cc.ImageManager, } } @@ -483,31 +478,31 @@ func (cc *GCPConfig) Validate(GCP *gcp.Client) error { if validID, err := GCP.IsProjectIDValid(); err != nil { return err } else if !validID { - return ErrorGCPInvalidProjectID(*cc.Project) + return ErrorGCPInvalidProjectID(cc.Project) } - if validZone, err := GCP.IsZoneValid(*cc.Zone); err != nil { + if validZone, err := GCP.IsZoneValid(cc.Zone); err != nil { return err } else if !validZone { availableZones, err := GCP.GetAvailableZones() if err != nil { return err } - return ErrorGCPInvalidZone(*cc.Zone, availableZones...) + return ErrorGCPInvalidZone(cc.Zone, availableZones...) } if cc.Bucket == "" { - cc.Bucket = GCPBucketName(cc.ClusterName, *cc.Project, *cc.Zone) + cc.Bucket = GCPBucketName(cc.ClusterName, cc.Project, cc.Zone) } - if validInstanceType, err := GCP.IsInstanceTypeAvailable(*cc.InstanceType, *cc.Zone); err != nil { + if validInstanceType, err := GCP.IsInstanceTypeAvailable(cc.InstanceType, cc.Zone); err != nil { return err } else if !validInstanceType { - instanceTypes, err := GCP.GetAvailableInstanceTypes(*cc.Zone) + instanceTypes, err := GCP.GetAvailableInstanceTypes(cc.Zone) if err != nil { return err } - return ErrorGCPInvalidInstanceType(*cc.InstanceType, instanceTypes...) + return ErrorGCPInvalidInstanceType(cc.InstanceType, instanceTypes...) } if cc.AcceleratorType == nil && cc.AcceleratorsPerInstance != nil { @@ -518,10 +513,10 @@ func (cc *GCPConfig) Validate(GCP *gcp.Client) error { if cc.AcceleratorsPerInstance == nil { return ErrorDependentFieldMustBeSpecified(AcceleratorTypeKey, AcceleratorsPerInstanceKey) } - if validAccelerator, err := GCP.IsAcceleratorTypeAvailable(*cc.AcceleratorType, *cc.Zone); err != nil { + if validAccelerator, err := GCP.IsAcceleratorTypeAvailable(*cc.AcceleratorType, cc.Zone); err != nil { return err } else if !validAccelerator { - availableAcceleratorsInZone, err := GCP.GetAvailableAcceleratorTypes(*cc.Zone) + availableAcceleratorsInZone, err := GCP.GetAvailableAcceleratorTypes(cc.Zone) if err != nil { return err } @@ -537,22 +532,22 @@ func (cc *GCPConfig) Validate(GCP *gcp.Client) error { return err } } - return ErrorGCPInvalidAcceleratorType(*cc.AcceleratorType, *cc.Zone, availableAcceleratorsInZone, availableZonesForAccelerator) + return ErrorGCPInvalidAcceleratorType(*cc.AcceleratorType, cc.Zone, availableAcceleratorsInZone, availableZonesForAccelerator) } // according to https://cloud.google.com/kubernetes-engine/docs/how-to/gpus var compatibleInstances []string var err error if strings.HasSuffix(*cc.AcceleratorType, "a100") { - compatibleInstances, err = GCP.GetInstanceTypesWithPrefix("a2", *cc.Zone) + compatibleInstances, err = GCP.GetInstanceTypesWithPrefix("a2", cc.Zone) } else { - compatibleInstances, err = GCP.GetInstanceTypesWithPrefix("n1", *cc.Zone) + compatibleInstances, err = GCP.GetInstanceTypesWithPrefix("n1", cc.Zone) } if err != nil { return err } - if !slices.HasString(compatibleInstances, *cc.InstanceType) { - return ErrorGCPIncompatibleInstanceTypeWithAccelerator(*cc.InstanceType, *cc.AcceleratorType, *cc.Zone, compatibleInstances) + if !slices.HasString(compatibleInstances, cc.InstanceType) { + return ErrorGCPIncompatibleInstanceTypeWithAccelerator(cc.InstanceType, *cc.AcceleratorType, cc.Zone, compatibleInstances) } } @@ -563,103 +558,6 @@ func (cc *GCPConfig) Validate(GCP *gcp.Client) error { return nil } -func applyGCPPromptDefaults(defaults GCPConfig) *GCPConfig { - defaultConfig := &GCPConfig{ - GCPCoreConfig: GCPCoreConfig{ - Zone: pointer.String("us-east1-c"), - }, - GCPManagedConfig: GCPManagedConfig{ - InstanceType: pointer.String("n1-standard-2"), - MinInstances: pointer.Int64(1), - MaxInstances: pointer.Int64(5), - }, - } - - if defaults.Zone != nil { - defaultConfig.Zone = defaults.Zone - } - if defaults.InstanceType != nil { - defaultConfig.InstanceType = defaults.InstanceType - } - if defaults.MinInstances != nil { - defaultConfig.MinInstances = defaults.MinInstances - } - if defaults.MaxInstances != nil { - defaultConfig.MaxInstances = defaults.MaxInstances - } - - return defaultConfig -} - -func InstallGCPPrompt(clusterConfig *GCPConfig, disallowPrompt bool) error { - defaults := applyGCPPromptDefaults(*clusterConfig) - - if disallowPrompt { - if clusterConfig.Project == nil { - return ErrorGCPProjectMustBeSpecified() - } - - if clusterConfig.Zone == nil { - clusterConfig.Zone = defaults.Zone - } - 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{ - SkipNonEmptyFields: true, - PromptItemValidations: []*cr.PromptItemValidation{ - { - StructField: "InstanceType", - PromptOpts: &prompt.Options{ - Prompt: "instance type", - }, - StringPtrValidation: &cr.StringPtrValidation{ - Required: true, - Default: defaults.InstanceType, - }, - }, - { - StructField: "MinInstances", - PromptOpts: &prompt.Options{ - Prompt: "min instances", - }, - Int64PtrValidation: &cr.Int64PtrValidation{ - Required: true, - Default: defaults.MinInstances, - GreaterThanOrEqualTo: pointer.Int64(0), - }, - }, - { - StructField: "MaxInstances", - PromptOpts: &prompt.Options{ - Prompt: "max instances", - }, - Int64PtrValidation: &cr.Int64PtrValidation{ - Required: true, - Default: defaults.MaxInstances, - GreaterThan: pointer.Int64(0), - }, - }, - }, - } - - err := cr.ReadPrompt(clusterConfig, remainingPrompts) - if err != nil { - return err - } - - return nil -} - // This does not set defaults for fields that are prompted from the user func SetGCPDefaults(cc *GCPConfig) error { var emptyMap interface{} = map[interface{}]interface{}{} @@ -697,8 +595,8 @@ func (cc *GCPCoreConfig) UserTable() table.KeyValuePairs { var items table.KeyValuePairs items.Add(ClusterNameUserKey, cc.ClusterName) - items.Add(ProjectUserKey, *cc.Project) - items.Add(ZoneUserKey, *cc.Zone) + items.Add(ProjectUserKey, cc.Project) + items.Add(ZoneUserKey, cc.Zone) items.Add(TelemetryUserKey, cc.Telemetry) items.Add(ImageOperatorUserKey, cc.ImageOperator) items.Add(ImageManagerUserKey, cc.ImageManager) @@ -726,9 +624,9 @@ func (cc *GCPCoreConfig) UserTable() table.KeyValuePairs { func (cc *GCPManagedConfig) UserTable() table.KeyValuePairs { var items table.KeyValuePairs - items.Add(InstanceTypeUserKey, *cc.InstanceType) - items.Add(MinInstancesUserKey, *cc.MinInstances) - items.Add(MaxInstancesUserKey, *cc.MaxInstances) + items.Add(InstanceTypeUserKey, cc.InstanceType) + items.Add(MinInstancesUserKey, cc.MinInstances) + items.Add(MaxInstancesUserKey, cc.MaxInstances) if cc.AcceleratorType != nil { items.Add(AcceleratorTypeUserKey, *cc.AcceleratorType) } @@ -773,10 +671,8 @@ func (cc *GCPCoreConfig) TelemetryEvent() map[string]interface{} { event["cluster_name._is_custom"] = true } - if cc.Zone != nil { - event["zone._is_defined"] = true - event["zone"] = *cc.Zone - } + event["zone"] = cc.Zone + if cc.Namespace != "default" { event["namespace._is_custom"] = true } @@ -847,10 +743,7 @@ func (cc *GCPCoreConfig) TelemetryEvent() map[string]interface{} { func (cc *GCPManagedConfig) TelemetryEvent() map[string]interface{} { event := map[string]interface{}{} - if cc.InstanceType != nil { - event["instance_type._is_defined"] = true - event["instance_type"] = *cc.InstanceType - } + event["instance_type"] = cc.InstanceType if cc.AcceleratorType != nil { event["accelerator_type._is_defined"] = true event["accelerator_type"] = *cc.AcceleratorType @@ -867,14 +760,8 @@ func (cc *GCPManagedConfig) TelemetryEvent() map[string]interface{} { } event["api_load_balancer_scheme"] = cc.APILoadBalancerScheme event["operator_load_balancer_scheme"] = cc.OperatorLoadBalancerScheme - if cc.MinInstances != nil { - event["min_instances._is_defined"] = true - event["min_instances"] = *cc.MinInstances - } - if cc.MaxInstances != nil { - event["max_instances._is_defined"] = true - event["max_instances"] = *cc.MaxInstances - } + event["min_instances"] = cc.MinInstances + event["max_instances"] = cc.MaxInstances event["preemptible"] = cc.Preemptible event["on_demand_backup"] = cc.OnDemandBackup From 92365aa1b131516d6273f656f3a1c828ed54178f Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Fri, 5 Mar 2021 19:10:10 +0200 Subject: [PATCH 03/10] Address PR comments --- cli/cmd/errors.go | 8 ++++++++ cli/cmd/lib_cluster_config_gcp.go | 2 +- pkg/types/clusterconfig/cluster_config.go | 4 ---- pkg/types/clusterconfig/cluster_config_aws.go | 10 +++------- pkg/types/clusterconfig/errors.go | 8 -------- 5 files changed, 12 insertions(+), 20 deletions(-) diff --git a/cli/cmd/errors.go b/cli/cmd/errors.go index 70590a8ad3..0744aa6f8a 100644 --- a/cli/cmd/errors.go +++ b/cli/cmd/errors.go @@ -70,6 +70,7 @@ const ( ErrClusterDown = "cli.cluster_down" ErrDuplicateCLIEnvNames = "cli.duplicate_cli_env_names" ErrClusterAccessConfigRequired = "cli.cluster_access_config_or_prompts_required" + ErrGCPClusterAccessConfigRequired = "cli.gcp_cluster_access_config_or_prompts_required" ErrGCPClusterAccessConfigOrPromptsRequired = "cli.gcp_cluster_access_config_or_prompts_required" ErrShellCompletionNotSupported = "cli.shell_completion_not_supported" ErrNoTerminalWidth = "cli.no_terminal_width" @@ -299,6 +300,13 @@ func ErrorClusterAccessConfigRequired() error { }) } +func ErrorGCPClusterAccessConfigRequired() error { + return errors.WithStack(&errors.Error{ + Kind: ErrGCPClusterAccessConfigRequired, + Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s` and `%s` (e.g. via `--config cluster.yaml`) or use the CLI flags to specify the cluster (e.g. via `--name`, `--project` or `--zone`)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey), + }) +} + func ErrorShellCompletionNotSupported(shell string) error { return errors.WithStack(&errors.Error{ Kind: ErrShellCompletionNotSupported, diff --git a/cli/cmd/lib_cluster_config_gcp.go b/cli/cmd/lib_cluster_config_gcp.go index 8587e2edf5..143738284a 100644 --- a/cli/cmd/lib_cluster_config_gcp.go +++ b/cli/cmd/lib_cluster_config_gcp.go @@ -105,7 +105,7 @@ func getGCPClusterAccessConfigWithCache(disallowPrompt bool) (*clusterconfig.GCP } if accessConfig.ClusterName == "" || accessConfig.Project == "" || accessConfig.Zone == "" { - return nil, ErrorClusterAccessConfigRequired() + return nil, ErrorGCPClusterAccessConfigRequired() } return accessConfig, nil diff --git a/pkg/types/clusterconfig/cluster_config.go b/pkg/types/clusterconfig/cluster_config.go index 7339114ece..1c76009e6e 100644 --- a/pkg/types/clusterconfig/cluster_config.go +++ b/pkg/types/clusterconfig/cluster_config.go @@ -75,10 +75,6 @@ func specificProviderTypeValidator(expectedProvider types.ProviderType) func(str } func validateClusterName(clusterName string) (string, error) { - if clusterName == "" { - return "", ErrorFieldCannotBeEmpty(ClusterNameKey) - } - if !_strictS3BucketRegex.MatchString(clusterName) { return "", errors.Wrap(ErrorDidNotMatchStrictS3Regex(), clusterName) } diff --git a/pkg/types/clusterconfig/cluster_config_aws.go b/pkg/types/clusterconfig/cluster_config_aws.go index 3e9e2e0ec2..a14d18bc32 100644 --- a/pkg/types/clusterconfig/cluster_config_aws.go +++ b/pkg/types/clusterconfig/cluster_config_aws.go @@ -158,9 +158,6 @@ func ValidateRegion(region string) error { } func RegionValidator(region string) (string, error) { - if region == "" { - return "", ErrorFieldCannotBeEmpty(RegionKey) - } if err := ValidateRegion(region); err != nil { return "", err } @@ -190,6 +187,7 @@ var CoreConfigStructFieldValidations = []*cr.StructFieldValidation{ { StructField: "Region", StringValidation: &cr.StringValidation{ + MinLength: 1, Validator: RegionValidator, }, }, @@ -385,6 +383,7 @@ var ManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ { StructField: "InstanceType", StringValidation: &cr.StringValidation{ + MinLength: 1, Validator: validateInstanceType, }, }, @@ -656,6 +655,7 @@ var AccessValidation = &cr.StructValidation{ { StructField: "Region", StringValidation: &cr.StringValidation{ + MinLength: 1, Validator: RegionValidator, }, }, @@ -1011,10 +1011,6 @@ func validateVPCCIDR(cidr string) (string, error) { } func validateInstanceType(instanceType string) (string, error) { - if instanceType == "" { - return "", ErrorFieldCannotBeEmpty(RegionKey) - } - var foundInstance *aws.InstanceMetadata for _, instanceMap := range aws.InstanceMetadatas { if instanceMetadata, ok := instanceMap[instanceType]; ok { diff --git a/pkg/types/clusterconfig/errors.go b/pkg/types/clusterconfig/errors.go index 3497e94717..a9f39b89b5 100644 --- a/pkg/types/clusterconfig/errors.go +++ b/pkg/types/clusterconfig/errors.go @@ -28,7 +28,6 @@ import ( ) const ( - ErrFieldCannotBeEmpty = "clusterconfig.field_cannot_be_empty" ErrInvalidRegion = "clusterconfig.invalid_region" ErrInstanceTypeTooSmall = "clusterconfig.instance_type_too_small" ErrMinInstancesGreaterThanMax = "clusterconfig.min_instances_greater_than_max" @@ -72,13 +71,6 @@ const ( ErrGCPIncompatibleInstanceTypeWithAccelerator = "clusterconfig.gcp_incompatible_instance_type_with_accelerator" ) -func ErrorFieldCannotBeEmpty(fieldName string) error { - return errors.WithStack(&errors.Error{ - Kind: ErrFieldCannotBeEmpty, - Message: fmt.Sprintf("%s field cannot be empty", fieldName), - }) -} - func ErrorInvalidRegion(region string) error { return errors.WithStack(&errors.Error{ Kind: ErrInvalidRegion, From 62bac13402e6348f56f5201d3ffa30b86a389c67 Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Fri, 5 Mar 2021 19:22:25 +0200 Subject: [PATCH 04/10] Update docs --- docs/clients/environments.md | 8 ++++---- docs/clusters/aws/install.md | 2 +- docs/clusters/aws/networking/custom-domain.md | 2 +- docs/clusters/aws/update.md | 8 ++++---- docs/clusters/gcp/install.md | 2 +- docs/clusters/gcp/uninstall.md | 8 ++++---- docs/workloads/realtime/troubleshooting.md | 4 ++-- test/apis/model-caching/python/translator/README.md | 4 ++-- 8 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/clients/environments.md b/docs/clients/environments.md index 9cab39d773..2c8fcdbce7 100644 --- a/docs/clients/environments.md +++ b/docs/clients/environments.md @@ -7,8 +7,8 @@ You can list your environments with `cortex env list`, change the default enviro ## Multiple clusters ```bash -cortex cluster up --config cluster1.yaml --configure-env cluster1 # configures the cluster1 env -cortex cluster up --config cluster2.yaml --configure-env cluster2 # configures the cluster2 env +cortex cluster up cluster1.yaml --configure-env cluster1 # configures the cluster1 env +cortex cluster up cluster2.yaml --configure-env cluster2 # configures the cluster2 env cortex deploy --env cluster1 cortex logs my-api --env cluster1 @@ -22,8 +22,8 @@ cortex delete my-api --env cluster2 ## Multiple clusters, if you omitted the `--configure-env` on `cortex cluster up` ```bash -cortex cluster info --config cluster1.yaml --configure-env cluster1 # configures the cluster1 env -cortex cluster info --config cluster2.yaml --configure-env cluster2 # configures the cluster2 env +cortex cluster info cluster1.yaml --configure-env cluster1 # configures the cluster1 env +cortex cluster info cluster2.yaml --configure-env cluster2 # configures the cluster2 env cortex deploy --env cluster1 cortex logs my-api --env cluster1 diff --git a/docs/clusters/aws/install.md b/docs/clusters/aws/install.md index 482e8c3c2a..1b665c1cca 100644 --- a/docs/clusters/aws/install.md +++ b/docs/clusters/aws/install.md @@ -14,7 +14,7 @@ pip install cortex # spin up Cortex on your AWS account -cortex cluster up # or: cortex cluster up --config cluster.yaml (see configuration options below) +cortex cluster up cluster.yaml # (see configuration options below) ``` ## Configure Cortex diff --git a/docs/clusters/aws/networking/custom-domain.md b/docs/clusters/aws/networking/custom-domain.md index d8f4d14f15..9df9b1ebef 100644 --- a/docs/clusters/aws/networking/custom-domain.md +++ b/docs/clusters/aws/networking/custom-domain.md @@ -79,7 +79,7 @@ ssl_certificate_arn: Create a Cortex cluster: ```bash -cortex cluster up --config cluster.yaml +cortex cluster up cluster.yaml ``` ## Add DNS record diff --git a/docs/clusters/aws/update.md b/docs/clusters/aws/update.md index 30af4288b7..da218c89ce 100644 --- a/docs/clusters/aws/update.md +++ b/docs/clusters/aws/update.md @@ -3,14 +3,14 @@ ## Update Cortex configuration ```bash -cortex cluster configure # or: cortex cluster configure --config cluster.yaml +cortex cluster configure cluster.yaml ``` ## Upgrade to a newer version of Cortex ```bash # spin down your cluster -cortex cluster down +cortex cluster down --config cluster.yaml # or just pass in the name and region of the cluster # update your CLI to the latest version pip install --upgrade cortex @@ -19,14 +19,14 @@ pip install --upgrade cortex cortex version # spin up your cluster -cortex cluster up +cortex cluster up cluster.yaml ``` ## Upgrade without downtime In production environments, you can upgrade your cluster without downtime if you have a backend service or DNS in front of your Cortex cluster: -1. Spin up a new cluster. For example: `cortex cluster up --config new-cluster.yaml --configure-env new` (this will create a CLI environment named `new` for accessing the new cluster). +1. Spin up a new cluster. For example: `cortex cluster up new-cluster.yaml --configure-env new` (this will create a CLI environment named `new` for accessing the new cluster). 1. Re-deploy your APIs in your new cluster. For example, if the name of your CLI environment for your old cluster is `old`, you can use `cortex get --env old` to list all running APIs in your old cluster, and re-deploy them in the new cluster by changing directories to each API's project folder and running `cortex deploy --env new`. 1. Route requests to your new cluster. * If you are using a custom domain: update the A record in your Route 53 hosted zone to point to your new cluster's API load balancer. diff --git a/docs/clusters/gcp/install.md b/docs/clusters/gcp/install.md index 18ee459e1d..7b7166bccc 100644 --- a/docs/clusters/gcp/install.md +++ b/docs/clusters/gcp/install.md @@ -14,7 +14,7 @@ pip install cortex # spin up Cortex on your GCP account -cortex cluster-gcp up # or: cortex cluster-gcp up --config cluster.yaml (see configuration options below) +cortex cluster-gcp up cluster.yaml # (see configuration options below) ``` ## Configure Cortex diff --git a/docs/clusters/gcp/uninstall.md b/docs/clusters/gcp/uninstall.md index f23f8b238f..493f00469c 100644 --- a/docs/clusters/gcp/uninstall.md +++ b/docs/clusters/gcp/uninstall.md @@ -1,17 +1,17 @@ # Uninstall Since you may wish to have access to your data after spinning down your cluster, Cortex's bucket, stackdriver logs, and -Prometheus volume are not automatically deleted when running `cortex cluster-gcp down`. +Prometheus volume are not automatically deleted when running `cortex cluster-gcp down --config cluster.yaml`. ```bash -cortex cluster-gcp down +cortex cluster-gcp down --config cluster.yaml ``` -The `cortex cluster-gcp down` command doesn't wait for the cluster to spin down. You can ensure that the cluster has +The `cortex cluster-gcp down --config cluster.yaml` command doesn't wait for the cluster to spin down. You can ensure that the cluster has spun down by checking the GKE console. ## Keep Cortex Volumes The volumes used by Cortex's Prometheus and Grafana instances are deleted by default on a cluster down operation. If you want to keep the metrics and dashboards volumes for any reason, -you can pass the `--keep-volumes` flag to the `cortex cluster-gcp down` command. +you can pass the `--keep-volumes` flag to the `cortex cluster-gcp down --config cluster.yaml` command. diff --git a/docs/workloads/realtime/troubleshooting.md b/docs/workloads/realtime/troubleshooting.md index 184a08e10f..7ba71efccf 100644 --- a/docs/workloads/realtime/troubleshooting.md +++ b/docs/workloads/realtime/troubleshooting.md @@ -21,9 +21,9 @@ If no logs appear (e.g. it just says "fetching logs..."), continue down this lis When you created your Cortex cluster, you configured `max_instances` (either from the command prompts or via a cluster configuration file, e.g. `cluster.yaml`). If your cluster already has `min_instances` running instances, additional instances cannot be created and APIs may not be able to deploy, scale, or update. -You can check the current value of `max_instances` by running `cortex cluster info` (or `cortex cluster info --config cluster.yaml` if you have a cluster configuration file). +You can check the current value of `max_instances` by running `cortex cluster info --config cluster.yaml` (or `cortex cluster info --name --region ` if you have the name and region of the cluster). -You can update `max_instances` by running `cortex cluster configure` (or by modifying `max_instances` in your cluster configuration file and running `cortex cluster configure --config cluster.yaml`). +You can update `max_instances` by modifying `max_instances` in your cluster configuration file and running `cortex cluster configure --config cluster.yaml` (or `cortex cluster configure --name --region ` if you have the name and region of the cluster). ## Check your AWS auto scaling group activity history diff --git a/test/apis/model-caching/python/translator/README.md b/test/apis/model-caching/python/translator/README.md index 0c21a3df33..a42a2ccac1 100644 --- a/test/apis/model-caching/python/translator/README.md +++ b/test/apis/model-caching/python/translator/README.md @@ -22,10 +22,10 @@ This project uses pre-trained Opus MT neural machine translation models, trained ## How to deploy the API -To deploy the API, first spin up a Cortex cluster by running `$ cortex cluster up --config cortex.yaml`. Note that the configuration file we are providing Cortex with (accessible at `cortex.yaml`) requests a g4dn.xlarge GPU instance. If your AWS account does not have access to GPU instances, you can request an EC2 service quota increase easily [here](https://console.aws.amazon.com/servicequotas), or you can simply use CPU instances (CPU will still work, you will just likely experience higher latency). +To deploy the API, first spin up a Cortex cluster by running `$ cortex cluster up cortex.yaml`. Note that the configuration file we are providing Cortex with (accessible at `cortex.yaml`) requests a g4dn.xlarge GPU instance. If your AWS account does not have access to GPU instances, you can request an EC2 service quota increase easily [here](https://console.aws.amazon.com/servicequotas), or you can simply use CPU instances (CPU will still work, you will just likely experience higher latency). ```bash -$ cortex cluster up --config cortex.yaml +$ cortex cluster up cortex.yaml email address [press enter to skip]: From f9b8cf11f34bfabe393239b1522f318c81299330 Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Fri, 5 Mar 2021 19:23:49 +0200 Subject: [PATCH 05/10] Change or to and --- cli/cmd/errors.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/cmd/errors.go b/cli/cmd/errors.go index 0744aa6f8a..f0fa6ac3b5 100644 --- a/cli/cmd/errors.go +++ b/cli/cmd/errors.go @@ -296,14 +296,14 @@ func ErrorClusterDown(out string) error { func ErrorClusterAccessConfigRequired() error { return errors.WithStack(&errors.Error{ Kind: ErrClusterAccessConfigRequired, - Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s` and `%s` (e.g. via `--config cluster.yaml`) or use the CLI flags to specify the cluster (e.g. via `--name` or `--region`)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey), + Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s` and `%s` (e.g. via `--config cluster.yaml`) or use the CLI flags to specify the cluster (e.g. via `--name` and `--region`)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey), }) } func ErrorGCPClusterAccessConfigRequired() error { return errors.WithStack(&errors.Error{ Kind: ErrGCPClusterAccessConfigRequired, - Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s` and `%s` (e.g. via `--config cluster.yaml`) or use the CLI flags to specify the cluster (e.g. via `--name`, `--project` or `--zone`)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey), + Message: fmt.Sprintf("please provide a cluster configuration file which specifies `%s` and `%s` (e.g. via `--config cluster.yaml`) or use the CLI flags to specify the cluster (e.g. via `--name`, `--project` and `--zone`)", clusterconfig.ClusterNameKey, clusterconfig.RegionKey), }) } From 0c18b3c9f7b776aafa846e8ca484ef867ab8ef80 Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Mon, 8 Mar 2021 16:53:11 +0200 Subject: [PATCH 06/10] Address PR comments --- cli/cmd/lib_cluster_config_aws.go | 1 - pkg/types/clusterconfig/cluster_config_aws.go | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/cli/cmd/lib_cluster_config_aws.go b/cli/cmd/lib_cluster_config_aws.go index 7384c30082..59dc332f82 100644 --- a/cli/cmd/lib_cluster_config_aws.go +++ b/cli/cmd/lib_cluster_config_aws.go @@ -115,7 +115,6 @@ func getClusterAccessConfigWithCache() (*clusterconfig.AccessConfig, error) { } if accessConfig.ClusterName == "" || accessConfig.Region == "" { - // won't execute for cluster configure commands return nil, ErrorClusterAccessConfigRequired() } return accessConfig, nil diff --git a/pkg/types/clusterconfig/cluster_config_aws.go b/pkg/types/clusterconfig/cluster_config_aws.go index a14d18bc32..dbc020e49e 100644 --- a/pkg/types/clusterconfig/cluster_config_aws.go +++ b/pkg/types/clusterconfig/cluster_config_aws.go @@ -187,6 +187,7 @@ var CoreConfigStructFieldValidations = []*cr.StructFieldValidation{ { StructField: "Region", StringValidation: &cr.StringValidation{ + Required: true, MinLength: 1, Validator: RegionValidator, }, @@ -383,6 +384,7 @@ var ManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ { StructField: "InstanceType", StringValidation: &cr.StringValidation{ + Required: true, MinLength: 1, Validator: validateInstanceType, }, @@ -655,6 +657,7 @@ var AccessValidation = &cr.StructValidation{ { StructField: "Region", StringValidation: &cr.StringValidation{ + Required: true, MinLength: 1, Validator: RegionValidator, }, From 98d8d038e18bf049ac059a956d6088044a19deda Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Mon, 8 Mar 2021 16:56:12 +0200 Subject: [PATCH 07/10] Add required fields for GCP config as well --- pkg/types/clusterconfig/cluster_config_gcp.go | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/pkg/types/clusterconfig/cluster_config_gcp.go b/pkg/types/clusterconfig/cluster_config_gcp.go index 01c3fd7785..6789fa06ef 100644 --- a/pkg/types/clusterconfig/cluster_config_gcp.go +++ b/pkg/types/clusterconfig/cluster_config_gcp.go @@ -120,12 +120,16 @@ var GCPCoreConfigStructFieldValidations = []*cr.StructFieldValidation{ }, }, { - StructField: "Project", - StringValidation: &cr.StringValidation{}, + StructField: "Project", + StringValidation: &cr.StringValidation{ + Required: true, + }, }, { - StructField: "Zone", - StringValidation: &cr.StringValidation{}, + StructField: "Zone", + StringValidation: &cr.StringValidation{ + Required: true, + }, }, { StructField: "IsManaged", @@ -295,8 +299,10 @@ var GCPCoreConfigStructFieldValidations = []*cr.StructFieldValidation{ var GCPManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ { - StructField: "InstanceType", - StringValidation: &cr.StringValidation{}, + StructField: "InstanceType", + StringValidation: &cr.StringValidation{ + Required: true, + }, }, { StructField: "AcceleratorType", @@ -388,12 +394,16 @@ var GCPAccessValidation = &cr.StructValidation{ }, }, { - StructField: "Zone", - StringValidation: &cr.StringValidation{}, + StructField: "Zone", + StringValidation: &cr.StringValidation{ + Required: true, + }, }, { - StructField: "Project", - StringValidation: &cr.StringValidation{}, + StructField: "Project", + StringValidation: &cr.StringValidation{ + Required: true, + }, }, { StructField: "ImageManager", From f2e3682687185f757d2f091b7486361ee22e0fa5 Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Mon, 8 Mar 2021 18:23:57 +0200 Subject: [PATCH 08/10] Mark cached cluster config as being low priority & rm unnecessary functions --- cli/cmd/lib_cluster_config_aws.go | 16 ++++++------- cli/cmd/lib_cluster_config_gcp.go | 18 +++++++-------- pkg/types/clusterconfig/cluster_config_aws.go | 11 +-------- pkg/types/clusterconfig/cluster_config_gcp.go | 23 +------------------ 4 files changed, 19 insertions(+), 49 deletions(-) diff --git a/cli/cmd/lib_cluster_config_aws.go b/cli/cmd/lib_cluster_config_aws.go index 59dc332f82..0eb6772fb1 100644 --- a/cli/cmd/lib_cluster_config_aws.go +++ b/cli/cmd/lib_cluster_config_aws.go @@ -92,6 +92,14 @@ func getClusterAccessConfigWithCache() (*clusterconfig.AccessConfig, error) { ImageManager: "quay.io/cortexlabs/manager:" + consts.CortexVersion, } + cachedPaths := existingCachedClusterConfigPaths() + if len(cachedPaths) == 1 { + cachedAccessConfig := &clusterconfig.AccessConfig{} + cr.ParseYAMLFile(cachedAccessConfig, clusterconfig.AccessValidation, cachedPaths[0]) + accessConfig.ClusterName = cachedAccessConfig.ClusterName + accessConfig.Region = cachedAccessConfig.Region + } + if _flagClusterConfig != "" { errs := cr.ParseYAMLFile(accessConfig, clusterconfig.AccessValidation, _flagClusterConfig) if errors.HasError(errs) { @@ -106,14 +114,6 @@ func getClusterAccessConfigWithCache() (*clusterconfig.AccessConfig, error) { accessConfig.Region = _flagClusterRegion } - cachedPaths := existingCachedClusterConfigPaths() - if len(cachedPaths) == 1 { - cachedAccessConfig := &clusterconfig.AccessConfig{} - cr.ParseYAMLFile(cachedAccessConfig, clusterconfig.AccessValidation, cachedPaths[0]) - accessConfig.ClusterName = cachedAccessConfig.ClusterName - accessConfig.Region = cachedAccessConfig.Region - } - if accessConfig.ClusterName == "" || accessConfig.Region == "" { return nil, ErrorClusterAccessConfigRequired() } diff --git a/cli/cmd/lib_cluster_config_gcp.go b/cli/cmd/lib_cluster_config_gcp.go index 143738284a..fa4e151876 100644 --- a/cli/cmd/lib_cluster_config_gcp.go +++ b/cli/cmd/lib_cluster_config_gcp.go @@ -78,6 +78,15 @@ func getGCPClusterAccessConfigWithCache(disallowPrompt bool) (*clusterconfig.GCP ImageManager: "quay.io/cortexlabs/manager:" + consts.CortexVersion, } + cachedPaths := existingCachedGCPClusterConfigPaths() + if len(cachedPaths) == 1 { + cachedAccessConfig := &clusterconfig.GCPAccessConfig{} + cr.ParseYAMLFile(cachedAccessConfig, clusterconfig.GCPAccessValidation, cachedPaths[0]) + accessConfig.ClusterName = cachedAccessConfig.ClusterName + accessConfig.Project = cachedAccessConfig.Project + accessConfig.Zone = cachedAccessConfig.Zone + } + if _flagClusterGCPConfig != "" { errs := cr.ParseYAMLFile(accessConfig, clusterconfig.GCPAccessValidation, _flagClusterGCPConfig) if errors.HasError(errs) { @@ -95,15 +104,6 @@ func getGCPClusterAccessConfigWithCache(disallowPrompt bool) (*clusterconfig.GCP accessConfig.Project = _flagClusterGCPProject } - cachedPaths := existingCachedGCPClusterConfigPaths() - if len(cachedPaths) == 1 { - cachedAccessConfig := &clusterconfig.GCPAccessConfig{} - cr.ParseYAMLFile(cachedAccessConfig, clusterconfig.GCPAccessValidation, cachedPaths[0]) - accessConfig.ClusterName = cachedAccessConfig.ClusterName - accessConfig.Project = cachedAccessConfig.Project - accessConfig.Zone = cachedAccessConfig.Zone - } - if accessConfig.ClusterName == "" || accessConfig.Project == "" || accessConfig.Zone == "" { return nil, ErrorGCPClusterAccessConfigRequired() } diff --git a/pkg/types/clusterconfig/cluster_config_aws.go b/pkg/types/clusterconfig/cluster_config_aws.go index dbc020e49e..a2d233132a 100644 --- a/pkg/types/clusterconfig/cluster_config_aws.go +++ b/pkg/types/clusterconfig/cluster_config_aws.go @@ -649,6 +649,7 @@ var AccessValidation = &cr.StructValidation{ { StructField: "ClusterName", StringValidation: &cr.StringValidation{ + Default: "cortex", MaxLength: 63, MinLength: 3, Validator: validateClusterName, @@ -1065,16 +1066,6 @@ func GetDefaults() (*Config, error) { return cc, nil } -func DefaultAccessConfig() (*AccessConfig, error) { - accessConfig := &AccessConfig{} - var emptyMap interface{} = map[interface{}]interface{}{} - errs := cr.Struct(accessConfig, emptyMap, AccessValidation) - if errors.HasError(errs) { - return nil, errors.FirstError(errs...) - } - return accessConfig, nil -} - func (cc *Config) MaxPossibleOnDemandInstances() int64 { if cc.Spot == false || cc.SpotConfig == nil || cc.SpotConfig.OnDemandBackup == nil || *cc.SpotConfig.OnDemandBackup == true { return cc.MaxInstances diff --git a/pkg/types/clusterconfig/cluster_config_gcp.go b/pkg/types/clusterconfig/cluster_config_gcp.go index 6789fa06ef..83d1060565 100644 --- a/pkg/types/clusterconfig/cluster_config_gcp.go +++ b/pkg/types/clusterconfig/cluster_config_gcp.go @@ -22,7 +22,6 @@ import ( "github.com/cortexlabs/cortex/pkg/consts" cr "github.com/cortexlabs/cortex/pkg/lib/configreader" - "github.com/cortexlabs/cortex/pkg/lib/errors" "github.com/cortexlabs/cortex/pkg/lib/gcp" "github.com/cortexlabs/cortex/pkg/lib/hash" "github.com/cortexlabs/cortex/pkg/lib/pointer" @@ -388,6 +387,7 @@ var GCPAccessValidation = &cr.StructValidation{ { StructField: "ClusterName", StringValidation: &cr.StringValidation{ + Default: "cortex", MaxLength: 63, MinLength: 3, Validator: validateClusterName, @@ -568,27 +568,6 @@ func (cc *GCPConfig) Validate(GCP *gcp.Client) error { return nil } -// This does not set defaults for fields that are prompted from the user -func SetGCPDefaults(cc *GCPConfig) error { - var emptyMap interface{} = map[interface{}]interface{}{} - errs := cr.Struct(cc, emptyMap, GCPFullManagedValidation) - if errors.HasError(errs) { - return errors.FirstError(errs...) - } - - return nil -} - -func DefaultGCPAccessConfig() (*GCPAccessConfig, error) { - accessConfig := &GCPAccessConfig{} - var emptyMap interface{} = map[interface{}]interface{}{} - errs := cr.Struct(accessConfig, emptyMap, GCPAccessValidation) - if errors.HasError(errs) { - return nil, errors.FirstError(errs...) - } - return accessConfig, nil -} - func (cc *InternalGCPConfig) UserTable() table.KeyValuePairs { var items table.KeyValuePairs From 293fe28444801da944e0ae23fa922610d9c15b06 Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Mon, 8 Mar 2021 20:27:39 +0200 Subject: [PATCH 09/10] Fix GCP cluster up --- pkg/types/clusterconfig/cluster_config_gcp.go | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/types/clusterconfig/cluster_config_gcp.go b/pkg/types/clusterconfig/cluster_config_gcp.go index 83d1060565..8c262612a6 100644 --- a/pkg/types/clusterconfig/cluster_config_gcp.go +++ b/pkg/types/clusterconfig/cluster_config_gcp.go @@ -304,12 +304,16 @@ var GCPManagedConfigStructFieldValidations = []*cr.StructFieldValidation{ }, }, { - StructField: "AcceleratorType", - StringPtrValidation: &cr.StringPtrValidation{}, + StructField: "AcceleratorType", + StringPtrValidation: &cr.StringPtrValidation{ + AllowExplicitNull: true, + }, }, { - StructField: "AcceleratorsPerInstance", - Int64PtrValidation: &cr.Int64PtrValidation{}, + StructField: "AcceleratorsPerInstance", + Int64PtrValidation: &cr.Int64PtrValidation{ + AllowExplicitNull: true, + }, DefaultDependentFields: []string{"AcceleratorType"}, DefaultDependentFieldsFunc: func(vals []interface{}) interface{} { acceleratorType := vals[0].(*string) From da682c909dd2202670fd96efd1c314e5177b8754 Mon Sep 17 00:00:00 2001 From: Robert Lucian Chiriac Date: Mon, 8 Mar 2021 22:09:16 +0200 Subject: [PATCH 10/10] Mark accelerator_type/accelerators_per_instance with the omitempty flag --- pkg/types/clusterconfig/cluster_config_gcp.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/types/clusterconfig/cluster_config_gcp.go b/pkg/types/clusterconfig/cluster_config_gcp.go index 8c262612a6..d752b1ebf7 100644 --- a/pkg/types/clusterconfig/cluster_config_gcp.go +++ b/pkg/types/clusterconfig/cluster_config_gcp.go @@ -66,10 +66,10 @@ type GCPCoreConfig struct { type GCPManagedConfig struct { InstanceType string `json:"instance_type" yaml:"instance_type"` - AcceleratorType *string `json:"accelerator_type" yaml:"accelerator_type"` - AcceleratorsPerInstance *int64 `json:"accelerators_per_instance" yaml:"accelerators_per_instance"` - Network *string `json:"network" yaml:"network"` - Subnet *string `json:"subnet" yaml:"subnet"` + AcceleratorType *string `json:"accelerator_type,omitempty" yaml:"accelerator_type,omitempty"` + AcceleratorsPerInstance *int64 `json:"accelerators_per_instance,omitempty" yaml:"accelerators_per_instance,omitempty"` + Network *string `json:"network,omitempty" yaml:"network,omitempty"` + Subnet *string `json:"subnet,omitempty" yaml:"subnet,omitempty"` APILoadBalancerScheme LoadBalancerScheme `json:"api_load_balancer_scheme" yaml:"api_load_balancer_scheme"` OperatorLoadBalancerScheme LoadBalancerScheme `json:"operator_load_balancer_scheme" yaml:"operator_load_balancer_scheme"` MinInstances int64 `json:"min_instances" yaml:"min_instances"`