Repository: cortexlabs/cortex (language: go)

Issue:
Add CLI command to rename an environment
### Description

`cortex env rename OLD_NAME NEW_NAME`

### Implementation notes

* Make sure to check and update the default environment if necessary
* Update `update.md` to show that you can rename the env name of your new cluster once your old cluster has been deleted


Discussion / hints:

Fix patch:
diff --git a/cli/cmd/env.go b/cli/cmd/env.go
index 64359c4224..da85b9313e 100644
--- a/cli/cmd/env.go
+++ b/cli/cmd/env.go
@@ -45,6 +45,9 @@ func envInit() {
 	_envDefaultCmd.Flags().SortFlags = false
 	_envCmd.AddCommand(_envDefaultCmd)
 
+	_envRenameCmd.Flags().SortFlags = false
+	_envCmd.AddCommand(_envRenameCmd)
+
 	_envDeleteCmd.Flags().SortFlags = false
 	_envCmd.AddCommand(_envDeleteCmd)
 }
@@ -160,6 +163,24 @@ var _envDefaultCmd = &cobra.Command{
 	},
 }
 
+var _envRenameCmd = &cobra.Command{
+	Use:   "rename EXISTING_NAME NEW_NAME",
+	Short: "rename an environment",
+	Args:  cobra.ExactArgs(2),
+	Run: func(cmd *cobra.Command, args []string) {
+		telemetry.Event("cli.env.rename")
+
+		oldEnvName := args[0]
+		newEnvName := args[1]
+
+		if err := renameEnv(oldEnvName, newEnvName); err != nil {
+			exit.Error(err)
+		}
+
+		print.BoldFirstLine(fmt.Sprintf("renamed the %s environment to %s", oldEnvName, newEnvName))
+	},
+}
+
 var _envDeleteCmd = &cobra.Command{
 	Use:   "delete [ENVIRONMENT_NAME]",
 	Short: "delete an environment configuration",
diff --git a/cli/cmd/lib_cli_config.go b/cli/cmd/lib_cli_config.go
index ecb8490ed3..5778e4970c 100644
--- a/cli/cmd/lib_cli_config.go
+++ b/cli/cmd/lib_cli_config.go
@@ -299,6 +299,40 @@ func setDefaultEnv(envName string) error {
 	return nil
 }
 
+func renameEnv(oldEnvName string, newEnvName string) error {
+	cliConfig, err := readCLIConfig()
+	if err != nil {
+		return err
+	}
+
+	renamedEnv := false
+
+	for _, env := range cliConfig.Environments {
+		if env.Name == newEnvName {
+			return cliconfig.ErrorEnvironmentAlreadyConfigured(newEnvName)
+		}
+
+		if env.Name == oldEnvName {
+			env.Name = newEnvName
+			renamedEnv = true
+		}
+	}
+
+	if !renamedEnv {
+		return cliconfig.ErrorEnvironmentNotConfigured(oldEnvName)
+	}
+
+	if cliConfig.DefaultEnvironment != nil && *cliConfig.DefaultEnvironment == oldEnvName {
+		cliConfig.DefaultEnvironment = &newEnvName
+	}
+
+	if err := writeCLIConfig(cliConfig); err != nil {
+		return err
+	}
+
+	return nil
+}
+
 func readTelemetryConfig() (bool, error) {
 	cliConfig, err := readCLIConfig()
 	if err != nil {
diff --git a/cli/types/cliconfig/errors.go b/cli/types/cliconfig/errors.go
index 874f1e3660..c59853614a 100644
--- a/cli/types/cliconfig/errors.go
+++ b/cli/types/cliconfig/errors.go
@@ -24,14 +24,22 @@ import (
 )
 
 const (
-	ErrEnvironmentNotConfigured  = "cliconfig.environment_not_configured"
-	ErrDuplicateEnvironmentNames = "cliconfig.duplicate_environment_names"
+	ErrEnvironmentNotConfigured     = "cliconfig.environment_not_configured"
+	ErrEnvironmentAlreadyConfigured = "cliconfig.environment_already_configured"
+	ErrDuplicateEnvironmentNames    = "cliconfig.duplicate_environment_names"
 )
 
 func ErrorEnvironmentNotConfigured(envName string) error {
 	return errors.WithStack(&errors.Error{
 		Kind:    ErrEnvironmentNotConfigured,
-		Message: fmt.Sprintf("%s environment is not configured", envName),
+		Message: fmt.Sprintf("there is no environment named %s", envName),
+	})
+}
+
+func ErrorEnvironmentAlreadyConfigured(envName string) error {
+	return errors.WithStack(&errors.Error{
+		Kind:    ErrEnvironmentAlreadyConfigured,
+		Message: fmt.Sprintf("there is already an environment named %s", envName),
 	})
 }
 
diff --git a/dev/generate_cli_md.sh b/dev/generate_cli_md.sh
index 36d402a8fc..2d49524e9d 100755
--- a/dev/generate_cli_md.sh
+++ b/dev/generate_cli_md.sh
@@ -46,6 +46,7 @@ commands=(
   "env configure"
   "env list"
   "env default"
+  "env rename"
   "env delete"
   "version"
   "completion"
diff --git a/docs/clients/cli.md b/docs/clients/cli.md
index 4e60c08bd2..7179115a3b 100644
--- a/docs/clients/cli.md
+++ b/docs/clients/cli.md
@@ -225,6 +225,18 @@ Flags:
   -h, --help   help for default
 ```
 
+## env rename
+
+```text
+rename an environment
+
+Usage:
+  cortex env rename EXISTING_NAME NEW_NAME [flags]
+
+Flags:
+  -h, --help   help for rename
+```
+
 ## env delete
 
 ```text
diff --git a/docs/clusters/management/environments.md b/docs/clusters/management/environments.md
index 04e5ce2e81..26c5592c7e 100644
--- a/docs/clusters/management/environments.md
+++ b/docs/clusters/management/environments.md
@@ -2,7 +2,7 @@
 
 When you create a cluster with `cortex cluster up`, an environment with the same name as your cluster is automatically created to point to your cluster and is configured to be the default environment. You can name the environment something else via the `--configure-env` flag, e.g. `cortex cluster up --configure-env prod`. You can also use the `--configure-env` flag with `cortex cluster info` to create / update the specified environment.
 
-You can list your environments with `cortex env list`, change the default environment with `cortex env default`, delete an environment with `cortex env delete`, and create/update an environment with `cortex env configure`.
+You can list your environments with `cortex env list`, change the default environment with `cortex env default`, rename an environment with `cortex env rename`, delete an environment with `cortex env delete`, and create/update an environment with `cortex env configure`.
 
 ## Multiple clusters
 
diff --git a/docs/clusters/management/update.md b/docs/clusters/management/update.md
index be1115e41d..6b1f72afba 100644
--- a/docs/clusters/management/update.md
+++ b/docs/clusters/management/update.md
@@ -26,10 +26,11 @@ cortex cluster up cluster.yaml
 
 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 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 existing cluster is `previous`, you can use `cortex get --env previous` to list all running APIs in your cluster, and re-deploy them in the new cluster by changing directories to each API's project folder and running `cortex deploy --env new`. Alternatively, you can run `cortex cluster export --name <previous_cluster_name> --region <region>` to export all of your APIs (including configuration and application code), change directories into each API/ID subfolder that was exported, and run `cortex deploy --env new`.
+1. Spin up a new cluster. For example: `cortex cluster up new-cluster.yaml --configure-env cortex2` (this will create a CLI environment named `cortex2` 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 existing cluster is `cortex`, you can use `cortex get --env cortex` to list all running APIs in your cluster, and re-deploy them in the new cluster by changing directories to each API's project folder and running `cortex deploy --env cortex2`. Alternatively, you can run `cortex cluster export --name <previous_cluster_name> --region <region>` to export all of your APIs (including configuration and application code), change directories into each API/ID subfolder that was exported, and run `cortex deploy --env cortex2`.
 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.
     * If you have a backend service which makes requests to Cortex: update your backend service to make requests to the new cluster's endpoints.
     * If you have a self-managed API Gateway in front of your Cortex cluster: update the routes to use new cluster's endpoints.
 1. Spin down your previous cluster. If you updated DNS settings, wait 24-48 hours before spinning down your previous cluster to allow the DNS cache to be flushed.
+1. You may now rename your new CLI environment name if you'd like (e.g. to rename it back to "cortex": `cortex env rename cortex2 cortex`)


Test patch: