From 1e8a142db162758417346eb0720095e3c9f01c78 Mon Sep 17 00:00:00 2001 From: The Magician Date: Mon, 8 Jun 2020 12:18:55 -0700 Subject: [PATCH] add data catalog tag (#3569) (#2144) * add data catalog tag * add data catalog tag * fix missing err check * update after review comments * fixing a bad diff * update per code review comments * update fields values to camel case in ruby Signed-off-by: Modular Magician --- .changelog/3569.txt | 3 + google-beta/provider.go | 5 +- ...esource_app_engine_flexible_app_version.go | 7 +- ...esource_app_engine_standard_app_version.go | 7 +- .../resource_binary_authorization_policy.go | 11 +- google-beta/resource_data_catalog_tag.go | 558 ++++++++++++++++++ ...esource_data_catalog_tag_generated_test.go | 412 +++++++++++++ .../resource_data_catalog_tag_sweeper_test.go | 124 ++++ .../resource_data_catalog_tag_template.go | 21 +- google-beta/resource_data_catalog_tag_test.go | 122 ++++ .../resource_source_repo_repository.go | 7 +- website/docs/r/data_catalog_tag.html.markdown | 434 ++++++++++++++ website/google.erb | 4 + 13 files changed, 1698 insertions(+), 17 deletions(-) create mode 100644 .changelog/3569.txt create mode 100644 google-beta/resource_data_catalog_tag.go create mode 100644 google-beta/resource_data_catalog_tag_generated_test.go create mode 100644 google-beta/resource_data_catalog_tag_sweeper_test.go create mode 100644 google-beta/resource_data_catalog_tag_test.go create mode 100644 website/docs/r/data_catalog_tag.html.markdown diff --git a/.changelog/3569.txt b/.changelog/3569.txt new file mode 100644 index 0000000000..d2b089e5df --- /dev/null +++ b/.changelog/3569.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`data_catalog_tag` +``` diff --git a/google-beta/provider.go b/google-beta/provider.go index e1c0a2aef9..e0abc236c3 100644 --- a/google-beta/provider.go +++ b/google-beta/provider.go @@ -654,9 +654,9 @@ func Provider() terraform.ResourceProvider { return provider } -// Generated resources: 161 +// Generated resources: 162 // Generated IAM resources: 66 -// Total generated resources: 227 +// Total generated resources: 228 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -778,6 +778,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_data_catalog_entry_group_iam_policy": ResourceIamPolicy(DataCatalogEntryGroupIamSchema, DataCatalogEntryGroupIamUpdaterProducer, DataCatalogEntryGroupIdParseFunc), "google_data_catalog_entry": resourceDataCatalogEntry(), "google_data_catalog_tag_template": resourceDataCatalogTagTemplate(), + "google_data_catalog_tag": resourceDataCatalogTag(), "google_data_fusion_instance": resourceDataFusionInstance(), "google_dataproc_autoscaling_policy": resourceDataprocAutoscalingPolicy(), "google_datastore_index": resourceDatastoreIndex(), diff --git a/google-beta/resource_app_engine_flexible_app_version.go b/google-beta/resource_app_engine_flexible_app_version.go index c052491fe9..90d43a065d 100644 --- a/google-beta/resource_app_engine_flexible_app_version.go +++ b/google-beta/resource_app_engine_flexible_app_version.go @@ -3073,13 +3073,16 @@ func expandAppEngineFlexibleAppVersionDeploymentFiles(v interface{}, d Terraform transformedSha1Sum, err := expandAppEngineFlexibleAppVersionDeploymentFilesSha1Sum(original["sha1_sum"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedSha1Sum); val.IsValid() && !isEmptyValue(val) { + transformed["sha1Sum"] = transformedSha1Sum } - transformed["sha1Sum"] = transformedSha1Sum + transformedSourceUrl, err := expandAppEngineFlexibleAppVersionDeploymentFilesSourceUrl(original["source_url"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedSourceUrl); val.IsValid() && !isEmptyValue(val) { + transformed["sourceUrl"] = transformedSourceUrl } - transformed["sourceUrl"] = transformedSourceUrl transformedName, err := expandString(original["name"], d, config) if err != nil { diff --git a/google-beta/resource_app_engine_standard_app_version.go b/google-beta/resource_app_engine_standard_app_version.go index cf810ec1d0..73fddddb3c 100644 --- a/google-beta/resource_app_engine_standard_app_version.go +++ b/google-beta/resource_app_engine_standard_app_version.go @@ -1556,13 +1556,16 @@ func expandAppEngineStandardAppVersionDeploymentFiles(v interface{}, d Terraform transformedSha1Sum, err := expandAppEngineStandardAppVersionDeploymentFilesSha1Sum(original["sha1_sum"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedSha1Sum); val.IsValid() && !isEmptyValue(val) { + transformed["sha1Sum"] = transformedSha1Sum } - transformed["sha1Sum"] = transformedSha1Sum + transformedSourceUrl, err := expandAppEngineStandardAppVersionDeploymentFilesSourceUrl(original["source_url"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedSourceUrl); val.IsValid() && !isEmptyValue(val) { + transformed["sourceUrl"] = transformedSourceUrl } - transformed["sourceUrl"] = transformedSourceUrl transformedName, err := expandString(original["name"], d, config) if err != nil { diff --git a/google-beta/resource_binary_authorization_policy.go b/google-beta/resource_binary_authorization_policy.go index 695a133243..4cc3ff2a48 100644 --- a/google-beta/resource_binary_authorization_policy.go +++ b/google-beta/resource_binary_authorization_policy.go @@ -561,18 +561,23 @@ func expandBinaryAuthorizationPolicyClusterAdmissionRules(v interface{}, d Terra transformedEvaluationMode, err := expandBinaryAuthorizationPolicyClusterAdmissionRulesEvaluationMode(original["evaluation_mode"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedEvaluationMode); val.IsValid() && !isEmptyValue(val) { + transformed["evaluationMode"] = transformedEvaluationMode } - transformed["evaluationMode"] = transformedEvaluationMode + transformedRequireAttestationsBy, err := expandBinaryAuthorizationPolicyClusterAdmissionRulesRequireAttestationsBy(original["require_attestations_by"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedRequireAttestationsBy); val.IsValid() && !isEmptyValue(val) { + transformed["requireAttestationsBy"] = transformedRequireAttestationsBy } - transformed["requireAttestationsBy"] = transformedRequireAttestationsBy + transformedEnforcementMode, err := expandBinaryAuthorizationPolicyClusterAdmissionRulesEnforcementMode(original["enforcement_mode"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedEnforcementMode); val.IsValid() && !isEmptyValue(val) { + transformed["enforcementMode"] = transformedEnforcementMode } - transformed["enforcementMode"] = transformedEnforcementMode transformedCluster, err := expandString(original["cluster"], d, config) if err != nil { diff --git a/google-beta/resource_data_catalog_tag.go b/google-beta/resource_data_catalog_tag.go new file mode 100644 index 0000000000..81dcc2cc47 --- /dev/null +++ b/google-beta/resource_data_catalog_tag.go @@ -0,0 +1,558 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "regexp" + "strconv" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func resourceDataCatalogTag() *schema.Resource { + return &schema.Resource{ + Create: resourceDataCatalogTagCreate, + Read: resourceDataCatalogTagRead, + Update: resourceDataCatalogTagUpdate, + Delete: resourceDataCatalogTagDelete, + + Importer: &schema.ResourceImporter{ + State: resourceDataCatalogTagImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(4 * time.Minute), + Update: schema.DefaultTimeout(4 * time.Minute), + Delete: schema.DefaultTimeout(4 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "fields": { + Type: schema.TypeSet, + Required: true, + Description: `This maps the ID of a tag field to the value of and additional information about that field. +Valid field IDs are defined by the tag's template. A tag must have at least 1 field and at most 500 fields.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "field_name": { + Type: schema.TypeString, + Required: true, + }, + "bool_value": { + Type: schema.TypeBool, + Optional: true, + Description: `Holds the value for a tag field with boolean type.`, + }, + "double_value": { + Type: schema.TypeFloat, + Optional: true, + Description: `Holds the value for a tag field with double type.`, + }, + "enum_value": { + Type: schema.TypeString, + Optional: true, + Description: `The display name of the enum value.`, + }, + + "string_value": { + Type: schema.TypeString, + Optional: true, + Description: `Holds the value for a tag field with string type.`, + }, + "timestamp_value": { + Type: schema.TypeString, + Optional: true, + Description: `Holds the value for a tag field with timestamp type.`, + }, + "display_name": { + Type: schema.TypeString, + Computed: true, + Description: `The display name of this field`, + }, + "order": { + Type: schema.TypeInt, + Computed: true, + Description: `The order of this field with respect to other fields in this tag. For example, a higher value can indicate +a more important field. The value can be negative. Multiple fields can have the same order, and field orders +within a tag do not have to be sequential.`, + }, + }, + }, + }, + "template": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The resource name of the tag template that this tag uses. Example: +projects/{project_id}/locations/{location}/tagTemplates/{tagTemplateId} +This field cannot be modified after creation.`, + }, + "column": { + Type: schema.TypeString, + Optional: true, + Description: `Resources like Entry can have schemas associated with them. This scope allows users to attach tags to an +individual column based on that schema. + +For attaching a tag to a nested column, use '.' to separate the column names. Example: +'outer_column.inner_column'`, + }, + "parent": { + Type: schema.TypeString, + Optional: true, + Description: `The name of the parent this tag is attached to. This can be the name of an entry or an entry group. If an entry group, the tag will be attached to +all entries in that group.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource name of the tag in URL format. Example: +projects/{project_id}/locations/{location}/entrygroups/{entryGroupId}/entries/{entryId}/tags/{tag_id} or +projects/{project_id}/locations/{location}/entrygroups/{entryGroupId}/tags/{tag_id} +where tag_id is a system-generated identifier. Note that this Tag may not actually be stored in the location in this name.`, + }, + "template_displayname": { + Type: schema.TypeString, + Computed: true, + Description: `The display name of the tag template.`, + }, + }, + } +} + +func resourceDataCatalogTagCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + templateProp, err := expandNestedDataCatalogTagTemplate(d.Get("template"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("template"); !isEmptyValue(reflect.ValueOf(templateProp)) && (ok || !reflect.DeepEqual(v, templateProp)) { + obj["template"] = templateProp + } + fieldsProp, err := expandNestedDataCatalogTagFields(d.Get("fields"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("fields"); !isEmptyValue(reflect.ValueOf(fieldsProp)) && (ok || !reflect.DeepEqual(v, fieldsProp)) { + obj["fields"] = fieldsProp + } + columnProp, err := expandNestedDataCatalogTagColumn(d.Get("column"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("column"); !isEmptyValue(reflect.ValueOf(columnProp)) && (ok || !reflect.DeepEqual(v, columnProp)) { + obj["column"] = columnProp + } + + url, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{parent}}/tags") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Tag: %#v", obj) + res, err := sendRequestWithTimeout(config, "POST", "", url, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Tag: %s", err) + } + if err := d.Set("name", flattenNestedDataCatalogTagName(res["name"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "name": %s`, err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Tag %q: %#v", d.Id(), res) + + return resourceDataCatalogTagRead(d, meta) +} + +func resourceDataCatalogTagRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{parent}}/tags") + if err != nil { + return err + } + + res, err := sendRequest(config, "GET", "", url, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("DataCatalogTag %q", d.Id())) + } + + res, err = flattenNestedDataCatalogTag(d, meta, res) + if err != nil { + return err + } + + if res == nil { + // Object isn't there any more - remove it from the state. + log.Printf("[DEBUG] Removing DataCatalogTag because it couldn't be matched.") + d.SetId("") + return nil + } + + if err := d.Set("name", flattenNestedDataCatalogTagName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading Tag: %s", err) + } + if err := d.Set("template", flattenNestedDataCatalogTagTemplate(res["template"], d, config)); err != nil { + return fmt.Errorf("Error reading Tag: %s", err) + } + if err := d.Set("template_displayname", flattenNestedDataCatalogTagTemplateDisplayname(res["templateDisplayName"], d, config)); err != nil { + return fmt.Errorf("Error reading Tag: %s", err) + } + if err := d.Set("fields", flattenNestedDataCatalogTagFields(res["fields"], d, config)); err != nil { + return fmt.Errorf("Error reading Tag: %s", err) + } + if err := d.Set("column", flattenNestedDataCatalogTagColumn(res["column"], d, config)); err != nil { + return fmt.Errorf("Error reading Tag: %s", err) + } + + return nil +} + +func resourceDataCatalogTagUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + obj := make(map[string]interface{}) + fieldsProp, err := expandNestedDataCatalogTagFields(d.Get("fields"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("fields"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, fieldsProp)) { + obj["fields"] = fieldsProp + } + columnProp, err := expandNestedDataCatalogTagColumn(d.Get("column"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("column"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, columnProp)) { + obj["column"] = columnProp + } + + url, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Tag %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("fields") { + updateMask = append(updateMask, "fields") + } + + if d.HasChange("column") { + updateMask = append(updateMask, "column") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + _, err = sendRequestWithTimeout(config, "PATCH", "", url, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating Tag %q: %s", d.Id(), err) + } + + return resourceDataCatalogTagRead(d, meta) +} + +func resourceDataCatalogTagDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + + url, err := replaceVars(d, config, "{{DataCatalogBasePath}}{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting Tag %q", d.Id()) + + res, err := sendRequestWithTimeout(config, "DELETE", "", url, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Tag") + } + + log.Printf("[DEBUG] Finished deleting Tag %q: %#v", d.Id(), res) + return nil +} + +func resourceDataCatalogTagImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + + // current import_formats can't import fields with forward slashes in their value + if err := parseImportId([]string{"(?P.+)"}, d, config); err != nil { + return nil, err + } + + name := d.Get("name").(string) + egRegex := regexp.MustCompile("(.+)/tags") + + parts := egRegex.FindStringSubmatch(name) + if len(parts) != 2 { + return nil, fmt.Errorf("entry name does not fit the format %s", egRegex) + } + + d.Set("parent", parts[1]) + return []*schema.ResourceData{d}, nil +} + +func flattenNestedDataCatalogTagName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagTemplate(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagTemplateDisplayname(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagFields(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return v + } + l := v.(map[string]interface{}) + transformed := make([]interface{}, 0, len(l)) + for k, raw := range l { + original := raw.(map[string]interface{}) + transformed = append(transformed, map[string]interface{}{ + "field_name": k, + "display_name": flattenNestedDataCatalogTagFieldsDisplayName(original["display_name"], d, config), + "order": flattenNestedDataCatalogTagFieldsOrder(original["order"], d, config), + "double_value": flattenNestedDataCatalogTagFieldsDoubleValue(original["doubleValue"], d, config), + "string_value": flattenNestedDataCatalogTagFieldsStringValue(original["stringValue"], d, config), + "bool_value": flattenNestedDataCatalogTagFieldsBoolValue(original["boolValue"], d, config), + "timestamp_value": flattenNestedDataCatalogTagFieldsTimestampValue(original["timestampValue"], d, config), + "enum_value": flattenNestedDataCatalogTagFieldsEnumValue(original["enumValue"], d, config), + }) + } + return transformed +} +func flattenNestedDataCatalogTagFieldsDisplayName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagFieldsOrder(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := strconv.ParseInt(strVal, 10, 64); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func flattenNestedDataCatalogTagFieldsDoubleValue(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagFieldsStringValue(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagFieldsBoolValue(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagFieldsTimestampValue(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenNestedDataCatalogTagFieldsEnumValue(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + + return v.(map[string]interface{})["displayName"] +} + +func flattenNestedDataCatalogTagColumn(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandNestedDataCatalogTagTemplate(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandNestedDataCatalogTagFields(v interface{}, d TerraformResourceData, config *Config) (map[string]interface{}, error) { + if v == nil { + return map[string]interface{}{}, nil + } + m := make(map[string]interface{}) + for _, raw := range v.(*schema.Set).List() { + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedDisplayName, err := expandNestedDataCatalogTagFieldsDisplayName(original["display_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDisplayName); val.IsValid() && !isEmptyValue(val) { + transformed["display_name"] = transformedDisplayName + } + + transformedOrder, err := expandNestedDataCatalogTagFieldsOrder(original["order"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedOrder); val.IsValid() && !isEmptyValue(val) { + transformed["order"] = transformedOrder + } + + transformedDoubleValue, err := expandNestedDataCatalogTagFieldsDoubleValue(original["double_value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDoubleValue); val.IsValid() && !isEmptyValue(val) { + transformed["doubleValue"] = transformedDoubleValue + } + + transformedStringValue, err := expandNestedDataCatalogTagFieldsStringValue(original["string_value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedStringValue); val.IsValid() && !isEmptyValue(val) { + transformed["stringValue"] = transformedStringValue + } + + transformedBoolValue, err := expandNestedDataCatalogTagFieldsBoolValue(original["bool_value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedBoolValue); val.IsValid() && !isEmptyValue(val) { + transformed["boolValue"] = transformedBoolValue + } + + transformedTimestampValue, err := expandNestedDataCatalogTagFieldsTimestampValue(original["timestamp_value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTimestampValue); val.IsValid() && !isEmptyValue(val) { + transformed["timestampValue"] = transformedTimestampValue + } + + transformedEnumValue, err := expandNestedDataCatalogTagFieldsEnumValue(original["enum_value"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedEnumValue); val.IsValid() && !isEmptyValue(val) { + transformed["enumValue"] = transformedEnumValue + } + + transformedFieldName, err := expandString(original["field_name"], d, config) + if err != nil { + return nil, err + } + m[transformedFieldName] = transformed + } + return m, nil +} + +func expandNestedDataCatalogTagFieldsDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandNestedDataCatalogTagFieldsOrder(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandNestedDataCatalogTagFieldsDoubleValue(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandNestedDataCatalogTagFieldsStringValue(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandNestedDataCatalogTagFieldsBoolValue(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandNestedDataCatalogTagFieldsTimestampValue(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandNestedDataCatalogTagFieldsEnumValue(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + // we flattened the original["enum_value"]["display_name"] object to be just original["enum_value"] so here, + // v is the value we want from the config + transformed := make(map[string]interface{}) + if val := reflect.ValueOf(v); val.IsValid() && !isEmptyValue(val) { + transformed["displayName"] = v + } + + return transformed, nil +} + +func expandNestedDataCatalogTagColumn(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func flattenNestedDataCatalogTag(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + var v interface{} + var ok bool + + v, ok = res["tags"] + if !ok || v == nil { + return nil, nil + } + + switch v.(type) { + case []interface{}: + break + case map[string]interface{}: + // Construct list out of single nested resource + v = []interface{}{v} + default: + return nil, fmt.Errorf("expected list or map for value tags. Actual value: %v", v) + } + + _, item, err := resourceDataCatalogTagFindNestedObjectInList(d, meta, v.([]interface{})) + if err != nil { + return nil, err + } + return item, nil +} + +func resourceDataCatalogTagFindNestedObjectInList(d *schema.ResourceData, meta interface{}, items []interface{}) (index int, item map[string]interface{}, err error) { + expectedName := d.Get("name") + expectedFlattenedName := flattenNestedDataCatalogTagName(expectedName, d, meta.(*Config)) + + // Search list for this resource. + for idx, itemRaw := range items { + if itemRaw == nil { + continue + } + item := itemRaw.(map[string]interface{}) + + itemName := flattenNestedDataCatalogTagName(item["name"], d, meta.(*Config)) + // isEmptyValue check so that if one is nil and the other is "", that's considered a match + if !(isEmptyValue(reflect.ValueOf(itemName)) && isEmptyValue(reflect.ValueOf(expectedFlattenedName))) && !reflect.DeepEqual(itemName, expectedFlattenedName) { + log.Printf("[DEBUG] Skipping item with name= %#v, looking for %#v)", itemName, expectedFlattenedName) + continue + } + log.Printf("[DEBUG] Found item for resource %q: %#v)", d.Id(), item) + return idx, item, nil + } + return -1, nil, nil +} diff --git a/google-beta/resource_data_catalog_tag_generated_test.go b/google-beta/resource_data_catalog_tag_generated_test.go new file mode 100644 index 0000000000..b9aa7cdfae --- /dev/null +++ b/google-beta/resource_data_catalog_tag_generated_test.go @@ -0,0 +1,412 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestAccDataCatalogTag_dataCatalogEntryTagBasicExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "force_delete": true, + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDataCatalogTagDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataCatalogTag_dataCatalogEntryTagBasicExample(context), + }, + { + ResourceName: "google_data_catalog_tag.basic_tag", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccDataCatalogTag_dataCatalogEntryTagBasicExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_data_catalog_entry" "entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "tf_test_my_entry%{random_suffix}" + + user_specified_type = "my_custom_type" + user_specified_system = "SomethingExternal" +} + +resource "google_data_catalog_entry_group" "entry_group" { + entry_group_id = "tf_test_my_entry_group%{random_suffix}" +} + +resource "google_data_catalog_tag_template" "tag_template" { + tag_template_id = "tf_test_my_template%{random_suffix}" + region = "us-central1" + display_name = "Demo Tag Template" + + fields { + field_id = "source" + display_name = "Source of data asset" + type { + primitive_type = "STRING" + } + is_required = true + } + + fields { + field_id = "num_rows" + display_name = "Number of rows in the data asset" + type { + primitive_type = "DOUBLE" + } + } + + fields { + field_id = "pii_type" + display_name = "PII type" + type { + enum_type { + allowed_values { + display_name = "EMAIL" + } + allowed_values { + display_name = "SOCIAL SECURITY NUMBER" + } + allowed_values { + display_name = "NONE" + } + } + } + } + + force_delete = "%{force_delete}" +} + +resource "google_data_catalog_tag" "basic_tag" { + parent = google_data_catalog_entry.entry.id + template = google_data_catalog_tag_template.tag_template.id + + fields { + field_name = "source" + string_value = "my-string" + } +} +`, context) +} + +func TestAccDataCatalogTag_dataCatalogEntryGroupTagExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "force_delete": true, + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDataCatalogTagDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataCatalogTag_dataCatalogEntryGroupTagExample(context), + }, + { + ResourceName: "google_data_catalog_tag.entry_group_tag", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccDataCatalogTag_dataCatalogEntryGroupTagExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_data_catalog_entry" "first_entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "tf_test_first_entry%{random_suffix}" + + user_specified_type = "my_custom_type" + user_specified_system = "SomethingExternal" +} + +resource "google_data_catalog_entry" "second_entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "tf_test_second_entry%{random_suffix}" + + user_specified_type = "another_custom_type" + user_specified_system = "SomethingElseExternal" +} + +resource "google_data_catalog_entry_group" "entry_group" { + entry_group_id = "tf_test_my_entry_group%{random_suffix}" +} + +resource "google_data_catalog_tag_template" "tag_template" { + tag_template_id = "tf_test_my_template%{random_suffix}" + region = "us-central1" + display_name = "Demo Tag Template" + + fields { + field_id = "source" + display_name = "Source of data asset" + type { + primitive_type = "STRING" + } + is_required = true + } + + fields { + field_id = "num_rows" + display_name = "Number of rows in the data asset" + type { + primitive_type = "DOUBLE" + } + } + + fields { + field_id = "pii_type" + display_name = "PII type" + type { + enum_type { + allowed_values { + display_name = "EMAIL" + } + allowed_values { + display_name = "SOCIAL SECURITY NUMBER" + } + allowed_values { + display_name = "NONE" + } + } + } + } + + force_delete = "%{force_delete}" +} + +resource "google_data_catalog_tag" "entry_group_tag" { + parent = google_data_catalog_entry_group.entry_group.id + template = google_data_catalog_tag_template.tag_template.id + + fields { + field_name = "source" + string_value = "my-string" + } +} +`, context) +} + +func TestAccDataCatalogTag_dataCatalogEntryTagFullExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "force_delete": true, + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDataCatalogTagDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataCatalogTag_dataCatalogEntryTagFullExample(context), + }, + { + ResourceName: "google_data_catalog_tag.basic_tag", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"parent"}, + }, + }, + }) +} + +func testAccDataCatalogTag_dataCatalogEntryTagFullExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_data_catalog_entry" "entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "tf_test_my_entry%{random_suffix}" + + user_specified_type = "my_custom_type" + user_specified_system = "SomethingExternal" + + schema = < 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/resource_data_catalog_tag_template.go b/google-beta/resource_data_catalog_tag_template.go index 2a2224aff3..d13946840d 100644 --- a/google-beta/resource_data_catalog_tag_template.go +++ b/google-beta/resource_data_catalog_tag_template.go @@ -341,7 +341,7 @@ func resourceDataCatalogTagTemplateImport(d *schema.ResourceData, meta interface parts := egRegex.FindStringSubmatch(name) if len(parts) != 4 { - return nil, fmt.Errorf("entry group name does not fit the format %s", egRegex) + return nil, fmt.Errorf("tag template name does not fit the format %s", egRegex) } d.Set("project", parts[1]) d.Set("region", parts[2]) @@ -475,28 +475,37 @@ func expandDataCatalogTagTemplateFields(v interface{}, d TerraformResourceData, transformedName, err := expandDataCatalogTagTemplateFieldsName(original["name"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) { + transformed["name"] = transformedName } - transformed["name"] = transformedName + transformedDisplayName, err := expandDataCatalogTagTemplateFieldsDisplayName(original["display_name"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedDisplayName); val.IsValid() && !isEmptyValue(val) { + transformed["displayName"] = transformedDisplayName } - transformed["displayName"] = transformedDisplayName + transformedType, err := expandDataCatalogTagTemplateFieldsType(original["type"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedType); val.IsValid() && !isEmptyValue(val) { + transformed["type"] = transformedType } - transformed["type"] = transformedType + transformedIsRequired, err := expandDataCatalogTagTemplateFieldsIsRequired(original["is_required"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedIsRequired); val.IsValid() && !isEmptyValue(val) { + transformed["isRequired"] = transformedIsRequired } - transformed["isRequired"] = transformedIsRequired + transformedOrder, err := expandDataCatalogTagTemplateFieldsOrder(original["order"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedOrder); val.IsValid() && !isEmptyValue(val) { + transformed["order"] = transformedOrder } - transformed["order"] = transformedOrder transformedFieldId, err := expandString(original["field_id"], d, config) if err != nil { diff --git a/google-beta/resource_data_catalog_tag_test.go b/google-beta/resource_data_catalog_tag_test.go new file mode 100644 index 0000000000..f09d92005c --- /dev/null +++ b/google-beta/resource_data_catalog_tag_test.go @@ -0,0 +1,122 @@ +package google + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccDataCatalogTag_update(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "force_delete": true, + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckDataCatalogEntryDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataCatalogTag_dataCatalogEntryTagBasicExample(context), + }, + { + ResourceName: "google_data_catalog_tag.basic_tag", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataCatalogTag_dataCatalogEntryTag_update(context), + }, + { + ResourceName: "google_data_catalog_tag.basic_tag", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataCatalogTag_dataCatalogEntryTagBasicExample(context), + }, + { + ResourceName: "google_data_catalog_tag.basic_tag", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccDataCatalogTag_dataCatalogEntryTag_update(context map[string]interface{}) string { + return Nprintf(` +resource "google_data_catalog_entry" "entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "tf_test_my_entry%{random_suffix}" + + user_specified_type = "my_custom_type" + user_specified_system = "SomethingExternal" +} + +resource "google_data_catalog_entry_group" "entry_group" { + entry_group_id = "tf_test_my_entry_group%{random_suffix}" +} + +resource "google_data_catalog_tag_template" "tag_template" { + tag_template_id = "tf_test_my_template%{random_suffix}" + region = "us-central1" + display_name = "Demo Tag Template" + + fields { + field_id = "source" + display_name = "Source of data asset" + type { + primitive_type = "STRING" + } + is_required = true + } + + fields { + field_id = "num_rows" + display_name = "Number of rows in the data asset" + type { + primitive_type = "DOUBLE" + } + } + + fields { + field_id = "pii_type" + display_name = "PII type" + type { + enum_type { + allowed_values { + display_name = "EMAIL" + } + allowed_values { + display_name = "SOCIAL SECURITY NUMBER" + } + allowed_values { + display_name = "NONE" + } + } + } + } + + force_delete = "%{force_delete}" +} + +resource "google_data_catalog_tag" "basic_tag" { + parent = google_data_catalog_entry.entry.id + template = google_data_catalog_tag_template.tag_template.id + + fields { + field_name = "source" + string_value = "my-new-string" + } + + fields { + field_name = "num_rows" + double_value = 5 + } +} +`, context) +} diff --git a/google-beta/resource_source_repo_repository.go b/google-beta/resource_source_repo_repository.go index 801c03260d..9f963d3682 100644 --- a/google-beta/resource_source_repo_repository.go +++ b/google-beta/resource_source_repo_repository.go @@ -351,13 +351,16 @@ func expandSourceRepoRepositoryPubsubConfigs(v interface{}, d TerraformResourceD transformedMessageFormat, err := expandSourceRepoRepositoryPubsubConfigsMessageFormat(original["message_format"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedMessageFormat); val.IsValid() && !isEmptyValue(val) { + transformed["messageFormat"] = transformedMessageFormat } - transformed["messageFormat"] = transformedMessageFormat + transformedServiceAccountEmail, err := expandSourceRepoRepositoryPubsubConfigsServiceAccountEmail(original["service_account_email"], d, config) if err != nil { return nil, err + } else if val := reflect.ValueOf(transformedServiceAccountEmail); val.IsValid() && !isEmptyValue(val) { + transformed["serviceAccountEmail"] = transformedServiceAccountEmail } - transformed["serviceAccountEmail"] = transformedServiceAccountEmail transformedTopic, err := expandSourceRepoRepositoryPubsubConfigsTopic(original["topic"], d, config) if err != nil { diff --git a/website/docs/r/data_catalog_tag.html.markdown b/website/docs/r/data_catalog_tag.html.markdown new file mode 100644 index 0000000000..488588ef3e --- /dev/null +++ b/website/docs/r/data_catalog_tag.html.markdown @@ -0,0 +1,434 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** AUTO GENERATED CODE *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Data catalog" +layout: "google" +page_title: "Google: google_data_catalog_tag" +sidebar_current: "docs-google-data-catalog-tag" +description: |- + Tags are used to attach custom metadata to Data Catalog resources. +--- + +# google\_data\_catalog\_tag + +Tags are used to attach custom metadata to Data Catalog resources. Tags conform to the specifications within their tag template. + +See [Data Catalog IAM](https://cloud.google.com/data-catalog/docs/concepts/iam) for information on the permissions needed to create or view tags. + + +To get more information about Tag, see: + +* [API documentation](https://cloud.google.com/data-catalog/docs/reference/rest/v1/projects.locations.entryGroups.tags) +* How-to Guides + * [Official Documentation](https://cloud.google.com/data-catalog/docs) + + +## Example Usage - Data Catalog Entry Tag Basic + + +```hcl +resource "google_data_catalog_entry" "entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "my_entry" + + user_specified_type = "my_custom_type" + user_specified_system = "SomethingExternal" +} + +resource "google_data_catalog_entry_group" "entry_group" { + entry_group_id = "my_entry_group" +} + +resource "google_data_catalog_tag_template" "tag_template" { + tag_template_id = "my_template" + region = "us-central1" + display_name = "Demo Tag Template" + + fields { + field_id = "source" + display_name = "Source of data asset" + type { + primitive_type = "STRING" + } + is_required = true + } + + fields { + field_id = "num_rows" + display_name = "Number of rows in the data asset" + type { + primitive_type = "DOUBLE" + } + } + + fields { + field_id = "pii_type" + display_name = "PII type" + type { + enum_type { + allowed_values { + display_name = "EMAIL" + } + allowed_values { + display_name = "SOCIAL SECURITY NUMBER" + } + allowed_values { + display_name = "NONE" + } + } + } + } + + force_delete = "false" +} + +resource "google_data_catalog_tag" "basic_tag" { + parent = google_data_catalog_entry.entry.id + template = google_data_catalog_tag_template.tag_template.id + + fields { + field_name = "source" + string_value = "my-string" + } +} +``` + +## Example Usage - Data Catalog Entry Group Tag + + +```hcl +resource "google_data_catalog_entry" "first_entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "first_entry" + + user_specified_type = "my_custom_type" + user_specified_system = "SomethingExternal" +} + +resource "google_data_catalog_entry" "second_entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "second_entry" + + user_specified_type = "another_custom_type" + user_specified_system = "SomethingElseExternal" +} + +resource "google_data_catalog_entry_group" "entry_group" { + entry_group_id = "my_entry_group" +} + +resource "google_data_catalog_tag_template" "tag_template" { + tag_template_id = "my_template" + region = "us-central1" + display_name = "Demo Tag Template" + + fields { + field_id = "source" + display_name = "Source of data asset" + type { + primitive_type = "STRING" + } + is_required = true + } + + fields { + field_id = "num_rows" + display_name = "Number of rows in the data asset" + type { + primitive_type = "DOUBLE" + } + } + + fields { + field_id = "pii_type" + display_name = "PII type" + type { + enum_type { + allowed_values { + display_name = "EMAIL" + } + allowed_values { + display_name = "SOCIAL SECURITY NUMBER" + } + allowed_values { + display_name = "NONE" + } + } + } + } + + force_delete = "false" +} + +resource "google_data_catalog_tag" "entry_group_tag" { + parent = google_data_catalog_entry_group.entry_group.id + template = google_data_catalog_tag_template.tag_template.id + + fields { + field_name = "source" + string_value = "my-string" + } +} +``` + +## Example Usage - Data Catalog Entry Tag Full + + +```hcl +resource "google_data_catalog_entry" "entry" { + entry_group = google_data_catalog_entry_group.entry_group.id + entry_id = "my_entry" + + user_specified_type = "my_custom_type" + user_specified_system = "SomethingExternal" + + schema = < If you're importing a resource with beta features, make sure to include `-provider=google-beta` +as an argument so that Terraform uses the correct provider to import your resource. diff --git a/website/google.erb b/website/google.erb index 36b88f94e2..a5a039aea2 100644 --- a/website/google.erb +++ b/website/google.erb @@ -1635,6 +1635,10 @@ google_data_catalog_entry_group_iam +
  • + google_data_catalog_tag +
  • +
  • google_data_catalog_tag_template