diff --git a/pkg/cmd/configs.go b/pkg/cmd/configs.go index 99224b44..3a4875fc 100644 --- a/pkg/cmd/configs.go +++ b/pkg/cmd/configs.go @@ -61,9 +61,19 @@ var configsDeleteCmd = &cobra.Command{ var configsUpdateCmd = &cobra.Command{ Use: "update [config]", Short: "Update a config", + Long: "Update properties about a config, such as its name, inheritability flag, and inheritances", Args: cobra.MaximumNArgs(1), ValidArgsFunction: configNamesValidArgs, Run: updateConfigs, + Example: `Updating a config's name +$ doppler configs update --project proj --config dev_branch --name dev_branch2 + +Enabling a config to be inherited +$ doppler configs update --project proj --config dev --inheritable true + +Configuring which configs the given config inherits +Note: The inherits flag accepts a comma separated list of PROJ_NAME.CONF_NAME +$ doppler configs update --project proj --config dev --inherits "shared-db.dev,shared-api.dev"`, } var configsLockCmd = &cobra.Command{ @@ -196,34 +206,70 @@ func deleteConfigs(cmd *cobra.Command, args []string) { func updateConfigs(cmd *cobra.Command, args []string) { jsonFlag := utils.OutputJSON + + nameSet := cmd.Flags().Changed("name") + inheritableSet := cmd.Flags().Changed("inheritable") + inheritsSet := cmd.Flags().Changed("inherits") + + if (nameSet && (inheritableSet || inheritsSet)) || (inheritableSet && inheritsSet) { + utils.HandleError(fmt.Errorf("Exactly one of name, inheritable, and inherits must be specified")) + } + name := cmd.Flag("name").Value.String() + inheritable := utils.GetBoolFlag(cmd, "inheritable") + inherits := cmd.Flag("inherits").Value.String() yes := utils.GetBoolFlag(cmd, "yes") localConfig := configuration.LocalConfig(cmd) utils.RequireValue("token", localConfig.Token.Value) - utils.RequireValue("name", name) config := localConfig.EnclaveConfig.Value if len(args) > 0 { config = args[0] } - if !yes { - utils.PrintWarning("Renaming this config may break your current deploys.") - if !utils.ConfirmationPrompt("Continue?", false) { - utils.Log("Aborting") - return + if nameSet { + if !yes { + utils.PrintWarning("Renaming this config may break your current deploys.") + if !utils.ConfirmationPrompt("Continue?", false) { + utils.Log("Aborting") + return + } } + + info, err := http.UpdateConfig(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, config, name) + if !err.IsNil() { + utils.HandleError(err.Unwrap(), err.Message) + } + + if !utils.Silent { + printer.ConfigInfo(info, jsonFlag) + } + } - info, err := http.UpdateConfig(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, config, name) - if !err.IsNil() { - utils.HandleError(err.Unwrap(), err.Message) + if inheritableSet { + info, err := http.UpdateConfigInheritable(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, config, inheritable) + if !err.IsNil() { + utils.HandleError(err.Unwrap(), err.Message) + } + + if !utils.Silent { + printer.ConfigInfo(info, jsonFlag) + } } - if !utils.Silent { - printer.ConfigInfo(info, jsonFlag) + if inheritsSet { + info, err := http.UpdateConfigInherits(localConfig.APIHost.Value, utils.GetBool(localConfig.VerifyTLS.Value, true), localConfig.Token.Value, localConfig.EnclaveProject.Value, config, inherits) + if !err.IsNil() { + utils.HandleError(err.Unwrap(), err.Message) + } + + if !utils.Silent { + printer.ConfigInfo(info, jsonFlag) + } } + } func lockConfigs(cmd *cobra.Command, args []string) { @@ -395,9 +441,8 @@ func init() { utils.HandleError(err) } configsUpdateCmd.Flags().String("name", "", "config name") - if err := configsUpdateCmd.MarkFlagRequired("name"); err != nil { - utils.HandleError(err) - } + configsUpdateCmd.Flags().Bool("inheritable", false, "toggle config inheritability") + configsUpdateCmd.Flags().String("inherits", "", "configs to inherit (e.g. \"proj2.prd,shared.prd\")") configsUpdateCmd.Flags().BoolP("yes", "y", false, "proceed without confirmation") configsCmd.AddCommand(configsUpdateCmd) diff --git a/pkg/http/api.go b/pkg/http/api.go index 99128026..92f7dd99 100644 --- a/pkg/http/api.go +++ b/pkg/http/api.go @@ -1050,6 +1050,90 @@ func UpdateConfig(host string, verifyTLS bool, apiKey string, project string, co return info, Error{} } +func UpdateConfigInheritable(host string, verifyTLS bool, apiKey string, project string, config string, inheritable bool) (models.ConfigInfo, Error) { + postBody := map[string]interface{}{"inheritable": inheritable} + body, err := json.Marshal(postBody) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Invalid config info"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + url, err := generateURL(host, "/v3/configs/config/inheritable", params) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to generate url"} + } + + statusCode, _, response, err := PostRequest(url, verifyTLS, apiKeyHeader(apiKey), body) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to update config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + configInfo, ok := result["config"].(map[string]interface{}) + if !ok { + return models.ConfigInfo{}, Error{Err: fmt.Errorf("Unexpected type parsing config info, expected map[string]interface{}, got %T", result["config"]), Message: "Unable to parse API response", Code: statusCode} + } + info := models.ParseConfigInfo(configInfo) + return info, Error{} +} + +func UpdateConfigInherits(host string, verifyTLS bool, apiKey string, project string, config string, inherits string) (models.ConfigInfo, Error) { + inheritsObj := []models.ConfigDescriptor{} + + if len(inherits) > 0 { + configDescriptors := strings.Split(inherits, ",") + for _, cd := range configDescriptors { + parts := strings.SplitN(cd, ".", 2) + if len(parts) != 2 { + return models.ConfigInfo{}, Error{Message: "Config descriptors must match the format \"projectSlug.configName\""} + } + inheritsObj = append(inheritsObj, models.ConfigDescriptor{ProjectSlug: parts[0], ConfigName: parts[1]}) + } + } + + postBody := map[string]interface{}{"inherits": inheritsObj} + body, err := json.Marshal(postBody) + + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Invalid config info"} + } + + var params []queryParam + params = append(params, queryParam{Key: "project", Value: project}) + params = append(params, queryParam{Key: "config", Value: config}) + + url, err := generateURL(host, "/v3/configs/config/inherits", params) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to generate url"} + } + + statusCode, _, response, err := PostRequest(url, verifyTLS, apiKeyHeader(apiKey), body) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to update config", Code: statusCode} + } + + var result map[string]interface{} + err = json.Unmarshal(response, &result) + if err != nil { + return models.ConfigInfo{}, Error{Err: err, Message: "Unable to parse API response", Code: statusCode} + } + + configInfo, ok := result["config"].(map[string]interface{}) + if !ok { + return models.ConfigInfo{}, Error{Err: fmt.Errorf("Unexpected type parsing config info, expected map[string]interface{}, got %T", result["config"]), Message: "Unable to parse API response", Code: statusCode} + } + info := models.ParseConfigInfo(configInfo) + return info, Error{} +} + // GetActivityLogs get activity logs func GetActivityLogs(host string, verifyTLS bool, apiKey string, page int, number int) ([]models.ActivityLog, Error) { var params []queryParam