diff --git a/.changelog/8500.txt b/.changelog/8500.txt new file mode 100644 index 00000000000..5afe4d1249d --- /dev/null +++ b/.changelog/8500.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +secretmanager: added `annotations` field to `google_secret_manager_secret` resource +``` diff --git a/google/resource_secret_manager_secret_generated_test.go b/google/resource_secret_manager_secret_generated_test.go index 1a15e5a65e0..f53daf2926c 100644 --- a/google/resource_secret_manager_secret_generated_test.go +++ b/google/resource_secret_manager_secret_generated_test.go @@ -78,6 +78,55 @@ resource "google_secret_manager_secret" "secret-basic" { `, context) } +func TestAccSecretManagerSecret_secretWithAnnotationsExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckSecretManagerSecretDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccSecretManagerSecret_secretWithAnnotationsExample(context), + }, + { + ResourceName: "google_secret_manager_secret.secret-with-annotations", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ttl", "secret_id"}, + }, + }, + }) +} + +func testAccSecretManagerSecret_secretWithAnnotationsExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_secret_manager_secret" "secret-with-annotations" { + secret_id = "secret%{random_suffix}" + + labels = { + label = "my-label" + } + + annotations = { + key1 = "someval" + key2 = "someval2" + key3 = "someval3" + key4 = "someval4" + key5 = "someval5" + } + + replication { + automatic = true + } +} +`, context) +} + func testAccCheckSecretManagerSecretDestroyProducer(t *testing.T) func(s *terraform.State) error { return func(s *terraform.State) error { for name, rs := range s.RootModule().Resources { diff --git a/google/resource_secret_manager_secret_test.go b/google/resource_secret_manager_secret_test.go index a28ef0db2b3..b088fe86e38 100644 --- a/google/resource_secret_manager_secret_test.go +++ b/google/resource_secret_manager_secret_test.go @@ -65,6 +65,49 @@ func TestAccSecretManagerSecret_cmek(t *testing.T) { }) } +func TestAccSecretManagerSecret_annotationsUpdate(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckSecretManagerSecretDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccSecretManagerSecret_annotationsBasic(context), + }, + { + ResourceName: "google_secret_manager_secret.secret-with-annotations", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ttl"}, + }, + { + Config: testAccSecretManagerSecret_annotationsUpdate(context), + }, + { + ResourceName: "google_secret_manager_secret.secret-with-annotations", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ttl"}, + }, + { + Config: testAccSecretManagerSecret_annotationsBasic(context), + }, + { + ResourceName: "google_secret_manager_secret.secret-with-annotations", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"ttl"}, + }, + }, + }) +} + func testAccSecretManagerSecret_basic(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_secret_manager_secret" "secret-basic" { @@ -128,3 +171,50 @@ resource "google_secret_manager_secret" "secret-basic" { } `, context) } + +func testAccSecretManagerSecret_annotationsBasic(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_secret_manager_secret" "secret-with-annotations" { + secret_id = "tf-test-secret-%{random_suffix}" + + labels = { + label = "my-label" + } + + annotations = { + key1 = "someval" + key2 = "someval2" + key3 = "someval3" + key4 = "someval4" + key5 = "someval5" + } + + replication { + automatic = true + } +} +`, context) +} + +func testAccSecretManagerSecret_annotationsUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_secret_manager_secret" "secret-with-annotations" { + secret_id = "tf-test-secret-%{random_suffix}" + + labels = { + label = "my-label" + } + + annotations = { + key1 = "someval" + key2update = "someval2" + key3 = "someval3update" + key4update = "someval4update" + } + + replication { + automatic = true + } +} +`, context) +} diff --git a/google/services/secretmanager/resource_secret_manager_secret.go b/google/services/secretmanager/resource_secret_manager_secret.go index 39c02cac88c..08d18eb9aaa 100644 --- a/google/services/secretmanager/resource_secret_manager_secret.go +++ b/google/services/secretmanager/resource_secret_manager_secret.go @@ -119,6 +119,25 @@ after the Secret has been created.`, ForceNew: true, Description: `This must be unique within the project.`, }, + "annotations": { + Type: schema.TypeMap, + Optional: true, + Description: `Custom metadata about the secret. + +Annotations are distinct from various forms of labels. Annotations exist to allow +client tools to store their own state information without requiring a database. + +Annotation keys must be between 1 and 63 characters long, have a UTF-8 encoding of +maximum 128 bytes, begin and end with an alphanumeric character ([a-z0-9A-Z]), and +may have dashes (-), underscores (_), dots (.), and alphanumerics in between these +symbols. + +The total size of annotation keys and values must be less than 16KiB. + +An object containing a list of "key": value pairs. Example: +{ "name": "wrench", "mass": "1.3kg", "count": "3" }.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, "expire_time": { Type: schema.TypeString, Computed: true, @@ -226,6 +245,12 @@ func resourceSecretManagerSecretCreate(d *schema.ResourceData, meta interface{}) } else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { obj["labels"] = labelsProp } + annotationsProp, err := expandSecretManagerSecretAnnotations(d.Get("annotations"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("annotations"); !tpgresource.IsEmptyValue(reflect.ValueOf(annotationsProp)) && (ok || !reflect.DeepEqual(v, annotationsProp)) { + obj["annotations"] = annotationsProp + } replicationProp, err := expandSecretManagerSecretReplication(d.Get("replication"), d, config) if err != nil { return err @@ -353,6 +378,9 @@ func resourceSecretManagerSecretRead(d *schema.ResourceData, meta interface{}) e if err := d.Set("labels", flattenSecretManagerSecretLabels(res["labels"], d, config)); err != nil { return fmt.Errorf("Error reading Secret: %s", err) } + if err := d.Set("annotations", flattenSecretManagerSecretAnnotations(res["annotations"], d, config)); err != nil { + return fmt.Errorf("Error reading Secret: %s", err) + } if err := d.Set("replication", flattenSecretManagerSecretReplication(res["replication"], d, config)); err != nil { return fmt.Errorf("Error reading Secret: %s", err) } @@ -391,6 +419,12 @@ func resourceSecretManagerSecretUpdate(d *schema.ResourceData, meta interface{}) } else if v, ok := d.GetOkExists("labels"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { obj["labels"] = labelsProp } + annotationsProp, err := expandSecretManagerSecretAnnotations(d.Get("annotations"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("annotations"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, annotationsProp)) { + obj["annotations"] = annotationsProp + } topicsProp, err := expandSecretManagerSecretTopics(d.Get("topics"), d, config) if err != nil { return err @@ -422,6 +456,10 @@ func resourceSecretManagerSecretUpdate(d *schema.ResourceData, meta interface{}) updateMask = append(updateMask, "labels") } + if d.HasChange("annotations") { + updateMask = append(updateMask, "annotations") + } + if d.HasChange("topics") { updateMask = append(updateMask, "topics") } @@ -541,6 +579,10 @@ func flattenSecretManagerSecretLabels(v interface{}, d *schema.ResourceData, con return v } +func flattenSecretManagerSecretAnnotations(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + func flattenSecretManagerSecretReplication(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { if v == nil { return nil @@ -673,6 +715,17 @@ func expandSecretManagerSecretLabels(v interface{}, d tpgresource.TerraformResou return m, nil } +func expandSecretManagerSecretAnnotations(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + func expandSecretManagerSecretReplication(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { l := v.([]interface{}) if len(l) == 0 || l[0] == nil { diff --git a/website/docs/r/secret_manager_secret.html.markdown b/website/docs/r/secret_manager_secret.html.markdown index f74f92dd3d6..ad88d6103c8 100644 --- a/website/docs/r/secret_manager_secret.html.markdown +++ b/website/docs/r/secret_manager_secret.html.markdown @@ -54,6 +54,35 @@ resource "google_secret_manager_secret" "secret-basic" { } } ``` +
+## Example Usage - Secret With Annotations + + +```hcl +resource "google_secret_manager_secret" "secret-with-annotations" { + secret_id = "secret" + + labels = { + label = "my-label" + } + + annotations = { + key1 = "someval" + key2 = "someval2" + key3 = "someval3" + key4 = "someval4" + key5 = "someval5" + } + + replication { + automatic = true + } +} +``` ## Argument Reference @@ -123,6 +152,19 @@ The following arguments are supported: An object containing a list of "key": value pairs. Example: { "name": "wrench", "mass": "1.3kg", "count": "3" }. +* `annotations` - + (Optional) + Custom metadata about the secret. + Annotations are distinct from various forms of labels. Annotations exist to allow + client tools to store their own state information without requiring a database. + Annotation keys must be between 1 and 63 characters long, have a UTF-8 encoding of + maximum 128 bytes, begin and end with an alphanumeric character ([a-z0-9A-Z]), and + may have dashes (-), underscores (_), dots (.), and alphanumerics in between these + symbols. + The total size of annotation keys and values must be less than 16KiB. + An object containing a list of "key": value pairs. Example: + { "name": "wrench", "mass": "1.3kg", "count": "3" }. + * `topics` - (Optional) A list of up to 10 Pub/Sub topics to which messages are published when control plane operations are called on the secret or its versions.