diff --git a/mmv1/products/kms/AutokeyConfig.yaml b/mmv1/products/kms/AutokeyConfig.yaml index fd428c40bd97..14aafa8a5a59 100644 --- a/mmv1/products/kms/AutokeyConfig.yaml +++ b/mmv1/products/kms/AutokeyConfig.yaml @@ -26,66 +26,90 @@ references: guides: 'Cloud KMS with Autokey': 'https://cloud.google.com/kms/docs/kms-with-autokey' api: 'https://cloud.google.com/kms/docs/reference/rest/v1/AutokeyConfig' -docs: -id_format: 'folders/{{folder}}/autokeyConfig' -base_url: 'folders/{{folder}}/autokeyConfig' -self_link: 'folders/{{folder}}/autokeyConfig' -# This is a singleton resource that is already created, so create -# is really an update, and therefore should be PATCHed. -create_url: 'folders/{{folder}}/autokeyConfig?updateMask=keyProject' -create_verb: 'PATCH' -update_url: 'folders/{{folder}}/autokeyConfig?updateMask=keyProject' -update_verb: 'PATCH' -delete_url: 'folders/{{folder}}/autokeyConfig?updateMask=keyProject' -delete_verb: 'PATCH' -import_format: - - 'folders/{{folder}}/autokeyConfig' -timeouts: - insert_minutes: 20 - update_minutes: 20 - delete_minutes: 20 -custom_code: - constants: 'templates/terraform/constants/autokey_config_folder_diff.go.tmpl' - pre_create: 'templates/terraform/pre_create/kms_autokey_config_folder.go.tmpl' - pre_read: 'templates/terraform/pre_read/kms_autokey_config_folder.go.tmpl' - pre_update: 'templates/terraform/pre_update/kms_autokey_config_folder.go.tmpl' - pre_delete: 'templates/terraform/pre_delete/kms_autokey_config_folder.go.tmpl' - post_create: 'templates/terraform/post_create/sleep.go.tmpl' - post_update: 'templates/terraform/post_create/sleep.go.tmpl' - test_check_destroy: 'templates/terraform/custom_check_destroy/kms_autokey_config.go.tmpl' -# Using a handwritten sweeper because of pre_delete. -exclude_sweeper: true -include_in_tgc_next: true -tgc_include_handwritten_tests: true -examples: - - name: 'kms_autokey_config_all' - primary_resource_id: 'example-autokeyconfig' - # test depends upon google_project_service_identity service which is still in beta, so we need to keep test limited to beta - min_version: 'beta' - vars: - folder_name: 'folder-cfg' - key_project_name: 'key-proj' - test_env_vars: - org_id: 'ORG_ID' - billing_account: 'BILLING_ACCT' - external_providers: ["random", "time"] + +# Base URL now needs to be dynamic +base_url: '{{parent}}/autokeyConfig' +self_link: '{{name}}' # This will be set by the decoder + parameters: - name: 'folder' type: String description: | The folder for which to retrieve config. url_param_only: true - required: true + required: false immutable: true + conflicts: + - 'project' diff_suppress_func: 'folderPrefixSuppress' + - name: 'project' + type: String + description: | + The project for which to retrieve config. + url_param_only: true + required: false + immutable: true + conflicts: + - 'folder' + diff_suppress_func: 'projectPrefixSuppress' + properties: + - name: 'name' + type: String + description: 'The resource name for the `AutokeyConfig` in the format `folders/{{folder_id}}/autokeyConfig` or `projects/{{project_id}}/autokeyConfig`.' + output: true - name: 'keyProject' type: String + api_name: keyProject description: | The target key project for a given folder where KMS Autokey will provision a CryptoKey for any new KeyHandle the Developer creates. Should have the form `projects/`. + - name: 'keyProjectResolutionMode' + type: Enum + api_name: keyProjectResolutionMode + description: 'How Autokey determines which project to use when provisioning CMEK keys.' + enum_values: + - 'KEY_PROJECT_RESOLUTION_MODE_UNSPECIFIED' + - 'DEDICATED_KEY_PROJECT' + - 'RESOURCE_PROJECT' + - 'DISABLED' - name: 'etag' type: String description: 'The etag of the AutokeyConfig for optimistic concurrency control.' output: true + +custom_code: + custom_import: templates/terraform/custom_import/kms_autokey_config.go.erb + encoder: templates/terraform/encoders/kms_autokey_config_update.go.erb + decoder: templates/terraform/decoders/kms_autokey_config.go.erb + custom_update: templates/terraform/custom_update/kms_autokey_config.go.erb + custom_create: templates/terraform/custom_create/kms_autokey_config.go.erb + pre_delete: templates/terraform/pre_delete/kms_autokey_config.go.erb + test_check_destroy: 'templates/terraform/custom_check_destroy/kms_autokey_config.go.tmpl' + constants: 'templates/terraform/constants/autokey_config_folder_diff.go.tmpl' + +timeouts: + insert_minutes: 20 + update_minutes: 20 + delete_minutes: 20 + +# Removed static create_url, update_url, delete_url, id_format, self_link from the top level +# These are now dynamic and handled by custom code. +exclude_sweeper: true +examples: + - name: 'kms_autokey_config_folder' # Example for folder + primary_resource_id: 'example-autokeyconfig-folder' + min_version: 'beta' + vars: + folder_name: 'my-folder' + key_project_name: 'key-proj' + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' + - name: 'kms_autokey_config_project' # Example for project + primary_resource_id: 'example-autokeyconfig-project' + min_version: 'beta' + vars: + project_name: 'my-resource-proj' + org_id: 'ORG_ID' + billing_account: 'BILLING_ACCT' diff --git a/mmv1/templates/terraform/constants/autokey_config_folder_diff.go.tmpl b/mmv1/templates/terraform/constants/autokey_config_folder_diff.go.tmpl index 530fc23be125..948a180de716 100644 --- a/mmv1/templates/terraform/constants/autokey_config_folder_diff.go.tmpl +++ b/mmv1/templates/terraform/constants/autokey_config_folder_diff.go.tmpl @@ -1,4 +1,58 @@ -func folderPrefixSuppress(_, old, new string, d *schema.ResourceData) bool { - prefix := "folders/" - return prefix+old == new || prefix+new == old +const ( + autokeyFolderPrefix = "folders/" + autokeyProjectPrefix = "projects/" + autokeyConfigSuffix = "/autokeyConfig" +) + +func autokeyHasPrefix(value, prefix string) bool { + return len(value) >= len(prefix) && value[:len(prefix)] == prefix } + +func autokeyTrimPrefix(value, prefix string) string { + if autokeyHasPrefix(value, prefix) { + return value[len(prefix):] + } + return value +} + +func autokeyTrimSuffix(value, suffix string) string { + if len(value) >= len(suffix) && value[len(value)-len(suffix):] == suffix { + return value[:len(value)-len(suffix)] + } + return value +} + +func autokeyJoin(fields []string) string { + result := "" + for i, field := range fields { + if i > 0 { + result += "," + } + result += field + } + return result +} + +func normalizeParent(parent, kind string) string { + if parent == "" { + return parent + } + switch kind { + case "folder": + if !autokeyHasPrefix(parent, autokeyFolderPrefix) { + return autokeyFolderPrefix + parent + } + case "project": + if !autokeyHasPrefix(parent, autokeyProjectPrefix) { + return autokeyProjectPrefix + parent + } + } + return parent +} + +func folderPrefixSuppress(k, old, new string, d *schema.ResourceData) bool { + return autokeyTrimPrefix(old, autokeyFolderPrefix) == autokeyTrimPrefix(new, autokeyFolderPrefix) +} +func projectPrefixSuppress(k, old, new string, d *schema.ResourceData) bool { + return autokeyTrimPrefix(old, autokeyProjectPrefix) == autokeyTrimPrefix(new, autokeyProjectPrefix) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/custom_create/kms_autokey_config.go.erb b/mmv1/templates/terraform/custom_create/kms_autokey_config.go.erb new file mode 100644 index 000000000000..f81b8f194edc --- /dev/null +++ b/mmv1/templates/terraform/custom_create/kms_autokey_config.go.erb @@ -0,0 +1,85 @@ +userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) +if err != nil { + return err +} + +var parent string +if v, ok := d.GetOk("folder"); ok { + parent = normalizeParent(v.(string), "folder") +} else if v, ok := d.GetOk("project"); ok { + parent = normalizeParent(v.(string), "project") +} else { + return fmt.Errorf("either folder or project must be set") +} + +billingProject := "" +if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp +} + +headers := make(http.Header) +obj := make(map[string]interface{}) +keyProjectProp, err := expandKMSAutokeyConfigKeyProject(d.Get("key_project"), d, config) +if err != nil { + return err +} else if v, ok := d.GetOkExists("key_project"); !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectProp)) && (ok || !reflect.DeepEqual(v, keyProjectProp)) { + obj["keyProject"] = keyProjectProp +} +keyProjectResolutionModeProp, err := expandKMSAutokeyConfigKeyProjectResolutionMode(d.Get("key_project_resolution_mode"), d, config) +if err != nil { + return err +} else if v, ok := d.GetOkExists("key_project_resolution_mode"); !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectResolutionModeProp)) && (ok || !reflect.DeepEqual(v, keyProjectResolutionModeProp)) { + obj["keyProjectResolutionMode"] = keyProjectResolutionModeProp +} +var updateMask []string +if !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectProp)) { + updateMask = append(updateMask, "keyProject") +} +if !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectResolutionModeProp)) { + updateMask = append(updateMask, "keyProjectResolutionMode") +} +var res map[string]interface{} +if len(updateMask) == 0 { + url := config.KMSBasePath + parent + "/autokeyConfig" + res, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + Headers: headers, + }) + if err != nil { + return fmt.Errorf("Error creating AutokeyConfig: %s", err) + } +} else { + url := config.KMSBasePath + parent + "/autokeyConfig?update_mask=" + autokeyJoin(updateMask) + res, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + Headers: headers, + }) + if err != nil { + return fmt.Errorf("Error creating AutokeyConfig: %s", err) + } +} +err = resourceKMSAutokeyConfigPostCreateSetComputedFields(d, meta, res) +if err != nil { + return fmt.Errorf("setting computed ID format fields: %w", err) +} +if nameVal, ok := d.Get("name").(string); ok && nameVal != "" { + d.SetId(nameVal) +} else { + return fmt.Errorf("Error constructing id: computed `name` not set") +} + +log.Printf("[DEBUG] Finished creating AutokeyConfig %q: %#v", d.Id(), res) + +return resourceKMSAutokeyConfigRead(d, meta) diff --git a/mmv1/templates/terraform/custom_import/kms_autokey_config.go.erb b/mmv1/templates/terraform/custom_import/kms_autokey_config.go.erb new file mode 100644 index 000000000000..34eba014a82e --- /dev/null +++ b/mmv1/templates/terraform/custom_import/kms_autokey_config.go.erb @@ -0,0 +1,9 @@ +id := d.Id() +if autokeyHasPrefix(id, autokeyFolderPrefix) { + d.Set("folder", id) +} else if autokeyHasPrefix(id, autokeyProjectPrefix) { + d.Set("project", id) +} else { + return nil, fmt.Errorf("invalid import id %q, expected folders/{folder_id} or projects/{project_id}", id) +} +return []*schema.ResourceData{d}, nil diff --git a/mmv1/templates/terraform/custom_update/kms_autokey_config.go.erb b/mmv1/templates/terraform/custom_update/kms_autokey_config.go.erb new file mode 100644 index 000000000000..e4b4ccb6427e --- /dev/null +++ b/mmv1/templates/terraform/custom_update/kms_autokey_config.go.erb @@ -0,0 +1,67 @@ +userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) +if err != nil { + return err +} + +billingProject := "" +if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp +} + +headers := make(http.Header) + +var parent string +if v, ok := d.GetOk("folder"); ok { + parent = normalizeParent(v.(string), "folder") +} else if v, ok := d.GetOk("project"); ok { + parent = normalizeParent(v.(string), "project") +} else { + return fmt.Errorf("either folder or project must be set") +} + +body := make(map[string]interface{}) +var updateMask []string + +if d.HasChange("key_project") { + updateMask = append(updateMask, "keyProject") + if !autokeyHasPrefix(parent, autokeyProjectPrefix) { + keyProjectProp, _ := expandKMSAutokeyConfigKeyProject(d.Get("key_project"), d, config) + if v, ok := d.GetOkExists("key_project"); !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectProp)) && (ok || !reflect.DeepEqual(v, keyProjectProp)) { + body["keyProject"] = keyProjectProp + } else { + body["keyProject"] = nil + } + } +} + +if d.HasChange("key_project_resolution_mode") { + updateMask = append(updateMask, "keyProjectResolutionMode") + keyProjectResolutionModeProp, _ := expandKMSAutokeyConfigKeyProjectResolutionMode(d.Get("key_project_resolution_mode"), d, config) + if v, ok := d.GetOkExists("key_project_resolution_mode"); !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectResolutionModeProp)) && (ok || !reflect.DeepEqual(v, keyProjectResolutionModeProp)) { + body["keyProjectResolutionMode"] = keyProjectResolutionModeProp + } else { + body["keyProjectResolutionMode"] = nil + } +} + +if len(updateMask) == 0 { + return resourceKMSAutokeyConfigRead(d, meta) +} +url := config.KMSBasePath + parent + "/autokeyConfig?update_mask=" + autokeyJoin(updateMask) + +_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: body, + Timeout: d.Timeout(schema.TimeoutUpdate), + Headers: headers, +}) +if err != nil { + return fmt.Errorf("Error updating AutokeyConfig: %s", err) +} + +return resourceKMSAutokeyConfigRead(d, meta) + diff --git a/mmv1/templates/terraform/decoders/kms_autokey_config.go.erb b/mmv1/templates/terraform/decoders/kms_autokey_config.go.erb new file mode 100644 index 000000000000..ddc5adc0a993 --- /dev/null +++ b/mmv1/templates/terraform/decoders/kms_autokey_config.go.erb @@ -0,0 +1,25 @@ +// Set the resource ID to the name returned from the API +if name, ok := res["name"].(string); ok { + d.SetId(name) + if autokeyHasPrefix(name, autokeyFolderPrefix) { + d.Set("folder", autokeyTrimSuffix(name, autokeyConfigSuffix)) + d.Set("project", nil) + } else if autokeyHasPrefix(name, autokeyProjectPrefix) { + d.Set("project", autokeyTrimSuffix(name, autokeyConfigSuffix)) + d.Set("folder", nil) + } +} + +if v, ok := res["keyProject"].(string); ok { + d.Set("key_project", v) +} else { + d.Set("key_project", nil) +} + +if v, ok := res["keyProjectResolutionMode"].(string); ok { + d.Set("key_project_resolution_mode", v) +} else { + d.Set("key_project_resolution_mode", nil) +} + +return res, nil diff --git a/mmv1/templates/terraform/encoders/kms_autokey_config_update.go.erb b/mmv1/templates/terraform/encoders/kms_autokey_config_update.go.erb new file mode 100644 index 000000000000..163a93eec9f0 --- /dev/null +++ b/mmv1/templates/terraform/encoders/kms_autokey_config_update.go.erb @@ -0,0 +1,23 @@ +config := meta.(*transport_tpg.Config) + +body := make(map[string]interface{}) + +if d.HasChange("key_project") { + keyProjectProp, _ := expandKMSAutokeyConfigKeyProject(d.Get("key_project"), d, config) + if v, ok := d.GetOkExists("key_project"); !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectProp)) && (ok || !reflect.DeepEqual(v, keyProjectProp)) { + body["keyProject"] = keyProjectProp + } else { + body["keyProject"] = nil + } +} + +if d.HasChange("key_project_resolution_mode") { + keyProjectResolutionModeProp, _ := expandKMSAutokeyConfigKeyProjectResolutionMode(d.Get("key_project_resolution_mode"), d, config) + if v, ok := d.GetOkExists("key_project_resolution_mode"); !tpgresource.IsEmptyValue(reflect.ValueOf(keyProjectResolutionModeProp)) && (ok || !reflect.DeepEqual(v, keyProjectResolutionModeProp)) { + body["keyProjectResolutionMode"] = keyProjectResolutionModeProp + } else { + body["keyProjectResolutionMode"] = nil + } +} + +return body, nil \ No newline at end of file diff --git a/mmv1/templates/terraform/examples/kms_autokey_config_folder.tf.tmpl b/mmv1/templates/terraform/examples/kms_autokey_config_folder.tf.tmpl new file mode 100644 index 000000000000..cfce6f308ae1 --- /dev/null +++ b/mmv1/templates/terraform/examples/kms_autokey_config_folder.tf.tmpl @@ -0,0 +1,31 @@ +resource "random_id" "bucket_prefix" { + byte_length = 4 +} + +resource "google_folder" "autokey_folder" { + display_name = "tf-test-folder-${random_id.bucket_prefix.hex}" + parent = "organizations/${var.org_id}" +} + +resource "google_project" "key_project" { + name = "tf-test-key-project-${random_id.bucket_prefix.hex}" + project_id = "tf-test-key-project-${random_id.bucket_prefix.hex}" + org_id = var.org_id + billing_account = var.billing_account +} + +resource "google_kms_autokey_config" "folder_config" { + provider = google-beta + folder = google_folder.autokey_folder.name + key_project = "projects/${google_project.key_project.project_id}" + # For folder scope, valid values: DEDICATED_KEY_PROJECT, RESOURCE_PROJECT, DISABLED + key_project_resolution_mode = "DEDICATED_KEY_PROJECT" +} + +variable "org_id" { + type = string +} + +variable "billing_account" { + type = string +} diff --git a/mmv1/templates/terraform/examples/kms_autokey_config_project.tf.tmpl b/mmv1/templates/terraform/examples/kms_autokey_config_project.tf.tmpl new file mode 100644 index 000000000000..638689ab2483 --- /dev/null +++ b/mmv1/templates/terraform/examples/kms_autokey_config_project.tf.tmpl @@ -0,0 +1,25 @@ +resource "random_id" "bucket_prefix" { + byte_length = 4 +} + +resource "google_project" "test_project" { + name = "tf-test-project-${random_id.bucket_prefix.hex}" + project_id = "tf-test-project-${random_id.bucket_prefix.hex}" + org_id = var.org_id + billing_account = var.billing_account +} + +resource "google_kms_autokey_config" "project_config" { + provider = google-beta + project = google_project.test_project.name + # For project scope, valid values: RESOURCE_PROJECT, DISABLED + key_project_resolution_mode = "RESOURCE_PROJECT" +} + +variable "org_id" { + type = string +} + +variable "billing_account" { + type = string +} \ No newline at end of file diff --git a/mmv1/templates/terraform/pre_delete/kms_autokey_config.go.erb b/mmv1/templates/terraform/pre_delete/kms_autokey_config.go.erb new file mode 100644 index 000000000000..067212babe69 --- /dev/null +++ b/mmv1/templates/terraform/pre_delete/kms_autokey_config.go.erb @@ -0,0 +1,36 @@ +var parent string +var updateMask string + +if v, ok := d.GetOk("folder"); ok { + parent = normalizeParent(v.(string), "folder") + updateMask = "keyProject,keyProjectResolutionMode" +} else if v, ok := d.GetOk("project"); ok { + parent = normalizeParent(v.(string), "project") + updateMask = "keyProjectResolutionMode" +} else { + return fmt.Errorf("either folder or project must be set") +} +url = config.KMSBasePath + parent + "/autokeyConfig?update_mask=" + updateMask +obj = make(map[string]interface{}) +if autokeyHasPrefix(parent, autokeyFolderPrefix) { + obj["keyProject"] = nil + obj["keyProjectResolutionMode"] = nil +} else { + obj["keyProjectResolutionMode"] = nil +} + +_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutDelete), + Headers: headers, +}) +if err != nil { + return fmt.Errorf("Error clearing AutokeyConfig for %s: %s", parent, err) +} + +return nil \ No newline at end of file