Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add TTL fields to Secret Manager #3360

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/4821.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
secretmanager: added `ttl`, `expire_time`, `topics` and `rotation` fields to `google_secret_manager_secret`
```
235 changes: 235 additions & 0 deletions google-beta/resource_secret_manager_secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ after the Secret has been created.`,
ForceNew: true,
Description: `This must be unique within the project.`,
},
"expire_time": {
Type: schema.TypeString,
Computed: true,
Optional: true,
Description: `Timestamp in UTC when the Secret is scheduled to expire. This is always provided on output, regardless of what was sent on input.
A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`,
},
"labels": {
Type: schema.TypeMap,
Optional: true,
Expand All @@ -124,6 +131,54 @@ An object containing a list of "key": value pairs. Example:
{ "name": "wrench", "mass": "1.3kg", "count": "3" }.`,
Elem: &schema.Schema{Type: schema.TypeString},
},
"rotation": {
Type: schema.TypeList,
Optional: true,
Description: `The rotation time and period for a Secret. At 'next_rotation_time', Secret Manager will send a Pub/Sub notification to the topics configured on the Secret. 'topics' must be set to configure rotation.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"next_rotation_time": {
Type: schema.TypeString,
Optional: true,
Description: `Timestamp in UTC at which the Secret is scheduled to rotate.
A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`,
RequiredWith: []string{"rotation.0.rotation_period"},
},
"rotation_period": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The Duration between rotation notifications. Must be in seconds and at least 3600s (1h) and at most 3153600000s (100 years).
If rotationPeriod is set, 'next_rotation_time' must be set. 'next_rotation_time' will be advanced by this period when the service automatically sends rotation notifications.`,
},
},
},
RequiredWith: []string{"topics"},
},
"topics": {
Type: schema.TypeList,
Optional: true,
Description: `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.`,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Required: true,
Description: `The resource name of the Pub/Sub topic that will be published to, in the following format: projects/*/topics/*.
For publication to succeed, the Secret Manager Service Agent service account must have pubsub.publisher permissions on the topic.`,
},
},
},
RequiredWith: []string{"rotation"},
},
"ttl": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
Description: `The TTL for the Secret.
A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s".`,
},
"create_time": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -166,6 +221,30 @@ func resourceSecretManagerSecretCreate(d *schema.ResourceData, meta interface{})
} else if v, ok := d.GetOkExists("replication"); !isEmptyValue(reflect.ValueOf(replicationProp)) && (ok || !reflect.DeepEqual(v, replicationProp)) {
obj["replication"] = replicationProp
}
topicsProp, err := expandSecretManagerSecretTopics(d.Get("topics"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("topics"); !isEmptyValue(reflect.ValueOf(topicsProp)) && (ok || !reflect.DeepEqual(v, topicsProp)) {
obj["topics"] = topicsProp
}
expireTimeProp, err := expandSecretManagerSecretExpireTime(d.Get("expire_time"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("expire_time"); !isEmptyValue(reflect.ValueOf(expireTimeProp)) && (ok || !reflect.DeepEqual(v, expireTimeProp)) {
obj["expireTime"] = expireTimeProp
}
ttlProp, err := expandSecretManagerSecretTtl(d.Get("ttl"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("ttl"); !isEmptyValue(reflect.ValueOf(ttlProp)) && (ok || !reflect.DeepEqual(v, ttlProp)) {
obj["ttl"] = ttlProp
}
rotationProp, err := expandSecretManagerSecretRotation(d.Get("rotation"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("rotation"); !isEmptyValue(reflect.ValueOf(rotationProp)) && (ok || !reflect.DeepEqual(v, rotationProp)) {
obj["rotation"] = rotationProp
}

url, err := replaceVars(d, config, "{{SecretManagerBasePath}}projects/{{project}}/secrets?secretId={{secret_id}}")
if err != nil {
Expand Down Expand Up @@ -252,6 +331,15 @@ func resourceSecretManagerSecretRead(d *schema.ResourceData, meta interface{}) e
if err := d.Set("replication", flattenSecretManagerSecretReplication(res["replication"], d, config)); err != nil {
return fmt.Errorf("Error reading Secret: %s", err)
}
if err := d.Set("topics", flattenSecretManagerSecretTopics(res["topics"], d, config)); err != nil {
return fmt.Errorf("Error reading Secret: %s", err)
}
if err := d.Set("expire_time", flattenSecretManagerSecretExpireTime(res["expireTime"], d, config)); err != nil {
return fmt.Errorf("Error reading Secret: %s", err)
}
if err := d.Set("rotation", flattenSecretManagerSecretRotation(res["rotation"], d, config)); err != nil {
return fmt.Errorf("Error reading Secret: %s", err)
}

return nil
}
Expand All @@ -278,6 +366,24 @@ func resourceSecretManagerSecretUpdate(d *schema.ResourceData, meta interface{})
} else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) {
obj["labels"] = labelsProp
}
topicsProp, err := expandSecretManagerSecretTopics(d.Get("topics"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("topics"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, topicsProp)) {
obj["topics"] = topicsProp
}
expireTimeProp, err := expandSecretManagerSecretExpireTime(d.Get("expire_time"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("expire_time"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, expireTimeProp)) {
obj["expireTime"] = expireTimeProp
}
rotationProp, err := expandSecretManagerSecretRotation(d.Get("rotation"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("rotation"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, rotationProp)) {
obj["rotation"] = rotationProp
}

url, err := replaceVars(d, config, "{{SecretManagerBasePath}}projects/{{project}}/secrets/{{secret_id}}")
if err != nil {
Expand All @@ -290,6 +396,18 @@ func resourceSecretManagerSecretUpdate(d *schema.ResourceData, meta interface{})
if d.HasChange("labels") {
updateMask = append(updateMask, "labels")
}

if d.HasChange("topics") {
updateMask = append(updateMask, "topics")
}

if d.HasChange("expire_time") {
updateMask = append(updateMask, "expireTime")
}

if d.HasChange("rotation") {
updateMask = append(updateMask, "rotation")
}
// 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, ",")})
Expand Down Expand Up @@ -454,6 +572,55 @@ func flattenSecretManagerSecretReplicationUserManagedReplicasCustomerManagedEncr
return v
}

func flattenSecretManagerSecretTopics(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return v
}
l := v.([]interface{})
transformed := make([]interface{}, 0, len(l))
for _, raw := range l {
original := raw.(map[string]interface{})
if len(original) < 1 {
// Do not include empty json objects coming back from the api
continue
}
transformed = append(transformed, map[string]interface{}{
"name": flattenSecretManagerSecretTopicsName(original["name"], d, config),
})
}
return transformed
}
func flattenSecretManagerSecretTopicsName(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenSecretManagerSecretExpireTime(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenSecretManagerSecretRotation(v interface{}, d *schema.ResourceData, config *Config) interface{} {
if v == nil {
return nil
}
original := v.(map[string]interface{})
if len(original) == 0 {
return nil
}
transformed := make(map[string]interface{})
transformed["next_rotation_time"] =
flattenSecretManagerSecretRotationNextRotationTime(original["nextRotationTime"], d, config)
transformed["rotation_period"] =
flattenSecretManagerSecretRotationRotationPeriod(original["rotationPeriod"], d, config)
return []interface{}{transformed}
}
func flattenSecretManagerSecretRotationNextRotationTime(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenSecretManagerSecretRotationRotationPeriod(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func expandSecretManagerSecretLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) {
if v == nil {
return map[string]string{}, nil
Expand Down Expand Up @@ -573,3 +740,71 @@ func expandSecretManagerSecretReplicationUserManagedReplicasCustomerManagedEncry
func expandSecretManagerSecretReplicationUserManagedReplicasCustomerManagedEncryptionKmsKeyName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandSecretManagerSecretTopics(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
l := v.([]interface{})
req := make([]interface{}, 0, len(l))
for _, raw := range l {
if raw == nil {
continue
}
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedName, err := expandSecretManagerSecretTopicsName(original["name"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedName); val.IsValid() && !isEmptyValue(val) {
transformed["name"] = transformedName
}

req = append(req, transformed)
}
return req, nil
}

func expandSecretManagerSecretTopicsName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandSecretManagerSecretExpireTime(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandSecretManagerSecretTtl(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandSecretManagerSecretRotation(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
l := v.([]interface{})
if len(l) == 0 || l[0] == nil {
return nil, nil
}
raw := l[0]
original := raw.(map[string]interface{})
transformed := make(map[string]interface{})

transformedNextRotationTime, err := expandSecretManagerSecretRotationNextRotationTime(original["next_rotation_time"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedNextRotationTime); val.IsValid() && !isEmptyValue(val) {
transformed["nextRotationTime"] = transformedNextRotationTime
}

transformedRotationPeriod, err := expandSecretManagerSecretRotationRotationPeriod(original["rotation_period"], d, config)
if err != nil {
return nil, err
} else if val := reflect.ValueOf(transformedRotationPeriod); val.IsValid() && !isEmptyValue(val) {
transformed["rotationPeriod"] = transformedRotationPeriod
}

return transformed, nil
}

func expandSecretManagerSecretRotationNextRotationTime(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}

func expandSecretManagerSecretRotationRotationPeriod(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return v, nil
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestAccSecretManagerSecret_secretConfigBasicExample(t *testing.T) {
ResourceName: "google_secret_manager_secret.secret-basic",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"secret_id"},
ImportStateVerifyIgnore: []string{"ttl", "secret_id"},
},
},
})
Expand Down
17 changes: 11 additions & 6 deletions google-beta/resource_secret_manager_secret_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ func TestAccSecretManagerSecret_import(t *testing.T) {
Config: testAccSecretManagerSecret_basic(context),
},
{
ResourceName: "google_secret_manager_secret.secret-basic",
ImportState: true,
ImportStateVerify: true,
ResourceName: "google_secret_manager_secret.secret-basic",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"ttl"},
},
},
})
Expand All @@ -50,9 +51,10 @@ func TestAccSecretManagerSecret_cmek(t *testing.T) {
Config: testAccSecretMangerSecret_cmek(context1),
},
{
ResourceName: "google_secret_manager_secret.secret-basic",
ImportState: true,
ImportStateVerify: true,
ResourceName: "google_secret_manager_secret.secret-basic",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"ttl"},
},
},
})
Expand All @@ -77,6 +79,9 @@ resource "google_secret_manager_secret" "secret-basic" {
}
}
}

ttl = "3600s"

}
`, context)
}
Expand Down
39 changes: 39 additions & 0 deletions website/docs/r/secret_manager_secret.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,49 @@ The `customer_managed_encryption` block supports:
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.
Structure is documented below.

* `expire_time` -
(Optional)
Timestamp in UTC when the Secret is scheduled to expire. This is always provided on output, regardless of what was sent on input.
A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".

* `ttl` -
(Optional)
The TTL for the Secret.
A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s".

* `rotation` -
(Optional)
The rotation time and period for a Secret. At `next_rotation_time`, Secret Manager will send a Pub/Sub notification to the topics configured on the Secret. `topics` must be set to configure rotation.
Structure is documented below.

* `project` - (Optional) The ID of the project in which the resource belongs.
If it is not provided, the provider project is used.


The `topics` block supports:

* `name` -
(Required)
The resource name of the Pub/Sub topic that will be published to, in the following format: projects/*/topics/*.
For publication to succeed, the Secret Manager Service Agent service account must have pubsub.publisher permissions on the topic.

The `rotation` block supports:

* `next_rotation_time` -
(Optional)
Timestamp in UTC at which the Secret is scheduled to rotate.
A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".

* `rotation_period` -
(Optional)
The Duration between rotation notifications. Must be in seconds and at least 3600s (1h) and at most 3153600000s (100 years).
If rotationPeriod is set, `next_rotation_time` must be set. `next_rotation_time` will be advanced by this period when the service automatically sends rotation notifications.

## Attributes Reference

In addition to the arguments listed above, the following computed attributes are exported:
Expand Down