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 deadLetterPolicy to Pub/Sub Subscription resource #1913

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/3305.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
pubsub: Added `dead_letter_policy` support to `google_pubsub_subscription`
```
137 changes: 137 additions & 0 deletions google-beta/resource_pubsub_subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,54 @@ for the call to the push endpoint.
If the subscriber never acknowledges the message, the Pub/Sub system
will eventually redeliver the message.`,
},
"dead_letter_policy": {
Type: schema.TypeList,
Optional: true,
Description: `A policy that specifies the conditions for dead lettering messages in
this subscription. If dead_letter_policy is not set, dead lettering
is disabled.

The Cloud Pub/Sub service account associated with this subscriptions's
parent project (i.e.,
service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com) must have
permission to Acknowledge() messages on this subscription.`,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"dead_letter_topic": {
Type: schema.TypeString,
Optional: true,
Description: `The name of the topic to which dead letter messages should be published.
Format is 'projects/{project}/topics/{topic}'.

The Cloud Pub/Sub service\naccount associated with the enclosing subscription's
parent project (i.e.,
service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com) must have
permission to Publish() to this topic.

The operation will fail if the topic does not exist.
Users should ensure that there is a subscription attached to this topic
since messages published to a topic with no subscriptions are lost.`,
},
"max_delivery_attempts": {
Type: schema.TypeInt,
Optional: true,
Description: `The maximum number of delivery attempts for any message. The value must be
between 5 and 100.

The number of delivery attempts is defined as 1 + (the sum of number of
NACKs and number of times the acknowledgement deadline has been exceeded for the message).

A NACK is any call to ModifyAckDeadline with a 0 deadline. Note that
client libraries may automatically extend ack_deadlines.

This field will be honored on a best effort basis.

If this parameter is 0, a default value of 5 is used.`,
},
},
},
},
"expiration_policy": {
Type: schema.TypeList,
Computed: true,
Expand Down Expand Up @@ -290,6 +338,12 @@ func resourcePubsubSubscriptionCreate(d *schema.ResourceData, meta interface{})
} else if v, ok := d.GetOkExists("expiration_policy"); ok || !reflect.DeepEqual(v, expirationPolicyProp) {
obj["expirationPolicy"] = expirationPolicyProp
}
deadLetterPolicyProp, err := expandPubsubSubscriptionDeadLetterPolicy(d.Get("dead_letter_policy"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("dead_letter_policy"); ok || !reflect.DeepEqual(v, deadLetterPolicyProp) {
obj["deadLetterPolicy"] = deadLetterPolicyProp
}

obj, err = resourcePubsubSubscriptionEncoder(d, meta, obj)
if err != nil {
Expand Down Expand Up @@ -418,6 +472,9 @@ func resourcePubsubSubscriptionRead(d *schema.ResourceData, meta interface{}) er
if err := d.Set("expiration_policy", flattenPubsubSubscriptionExpirationPolicy(res["expirationPolicy"], d, config)); err != nil {
return fmt.Errorf("Error reading Subscription: %s", err)
}
if err := d.Set("dead_letter_policy", flattenPubsubSubscriptionDeadLetterPolicy(res["deadLetterPolicy"], d, config)); err != nil {
return fmt.Errorf("Error reading Subscription: %s", err)
}

return nil
}
Expand Down Expand Up @@ -467,6 +524,12 @@ func resourcePubsubSubscriptionUpdate(d *schema.ResourceData, meta interface{})
} else if v, ok := d.GetOkExists("expiration_policy"); ok || !reflect.DeepEqual(v, expirationPolicyProp) {
obj["expirationPolicy"] = expirationPolicyProp
}
deadLetterPolicyProp, err := expandPubsubSubscriptionDeadLetterPolicy(d.Get("dead_letter_policy"), d, config)
if err != nil {
return err
} else if v, ok := d.GetOkExists("dead_letter_policy"); ok || !reflect.DeepEqual(v, deadLetterPolicyProp) {
obj["deadLetterPolicy"] = deadLetterPolicyProp
}

obj, err = resourcePubsubSubscriptionUpdateEncoder(d, meta, obj)
if err != nil {
Expand Down Expand Up @@ -504,6 +567,10 @@ func resourcePubsubSubscriptionUpdate(d *schema.ResourceData, meta interface{})
if d.HasChange("expiration_policy") {
updateMask = append(updateMask, "expirationPolicy")
}

if d.HasChange("dead_letter_policy") {
updateMask = append(updateMask, "deadLetterPolicy")
}
// 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 @@ -669,6 +736,42 @@ func flattenPubsubSubscriptionExpirationPolicyTtl(v interface{}, d *schema.Resou
return v
}

func flattenPubsubSubscriptionDeadLetterPolicy(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["dead_letter_topic"] =
flattenPubsubSubscriptionDeadLetterPolicyDeadLetterTopic(original["deadLetterTopic"], d, config)
transformed["max_delivery_attempts"] =
flattenPubsubSubscriptionDeadLetterPolicyMaxDeliveryAttempts(original["maxDeliveryAttempts"], d, config)
return []interface{}{transformed}
}
func flattenPubsubSubscriptionDeadLetterPolicyDeadLetterTopic(v interface{}, d *schema.ResourceData, config *Config) interface{} {
return v
}

func flattenPubsubSubscriptionDeadLetterPolicyMaxDeliveryAttempts(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 expandPubsubSubscriptionName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) {
return replaceVars(d, config, "projects/{{project}}/subscriptions/{{name}}")
}
Expand Down Expand Up @@ -826,6 +929,40 @@ func expandPubsubSubscriptionExpirationPolicyTtl(v interface{}, d TerraformResou
return v, nil
}

func expandPubsubSubscriptionDeadLetterPolicy(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{})

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

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

return transformed, nil
}

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

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

func resourcePubsubSubscriptionEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
delete(obj, "name")
return obj, nil
Expand Down
46 changes: 46 additions & 0 deletions google-beta/resource_pubsub_subscription_generated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,52 @@ resource "google_pubsub_subscription" "example" {
`, context)
}

func TestAccPubsubSubscription_pubsubSubscriptionDeadLetterExample(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"random_suffix": acctest.RandString(10),
}

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckPubsubSubscriptionDestroy,
Steps: []resource.TestStep{
{
Config: testAccPubsubSubscription_pubsubSubscriptionDeadLetterExample(context),
},
{
ResourceName: "google_pubsub_subscription.example",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func testAccPubsubSubscription_pubsubSubscriptionDeadLetterExample(context map[string]interface{}) string {
return Nprintf(`
resource "google_pubsub_topic" "example" {
name = "tf-test-example-topic%{random_suffix}"
}

resource "google_pubsub_topic" "example_dead_letter" {
name = "tf-test-example-topic%{random_suffix}-dead-letter"
}

resource "google_pubsub_subscription" "example" {
name = "tf-test-example-subscription%{random_suffix}"
topic = google_pubsub_topic.example.name

dead_letter_policy {
dead_letter_topic = google_pubsub_topic.example_dead_letter.id
max_delivery_attempts = 10
}
}
`, context)
}

func testAccCheckPubsubSubscriptionDestroy(s *terraform.State) error {
for name, rs := range s.RootModule().Resources {
if rs.Type != "google_pubsub_subscription" {
Expand Down
62 changes: 62 additions & 0 deletions website/docs/r/pubsub_subscription.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,33 @@ resource "google_pubsub_subscription" "example" {
topic = google_pubsub_topic.example.name
}
```
<div class = "oics-button" style="float: right; margin: 0 0 -15px">
<a href="https://console.cloud.google.com/cloudshell/open?cloudshell_git_repo=https%3A%2F%2Fgithub.jparrowsec.cn%2Fterraform-google-modules%2Fdocs-examples.git&cloudshell_working_dir=pubsub_subscription_dead_letter&cloudshell_image=gcr.io%2Fgraphite-cloud-shell-images%2Fterraform%3Alatest&open_in_editor=main.tf&cloudshell_print=.%2Fmotd&cloudshell_tutorial=.%2Ftutorial.md" target="_blank">
<img alt="Open in Cloud Shell" src="//gstatic.com/cloudssh/images/open-btn.svg" style="max-height: 44px; margin: 32px auto; max-width: 100%;">
</a>
</div>
## Example Usage - Pubsub Subscription Dead Letter


```hcl
resource "google_pubsub_topic" "example" {
name = "example-topic"
}

resource "google_pubsub_topic" "example_dead_letter" {
name = "example-topic-dead-letter"
}

resource "google_pubsub_subscription" "example" {
name = "example-subscription"
topic = google_pubsub_topic.example.name

dead_letter_policy {
dead_letter_topic = google_pubsub_topic.example_dead_letter.id
max_delivery_attempts = 10
}
}
```

## Argument Reference

Expand Down Expand Up @@ -181,6 +208,16 @@ The following arguments are supported:
resource never expires. The minimum allowed value for expirationPolicy.ttl
is 1 day. Structure is documented below.

* `dead_letter_policy` -
(Optional)
A policy that specifies the conditions for dead lettering messages in
this subscription. If dead_letter_policy is not set, dead lettering
is disabled.
The Cloud Pub/Sub service account associated with this subscriptions's
parent project (i.e.,
service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com) must have
permission to Acknowledge() messages on this subscription. 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.

Expand Down Expand Up @@ -248,6 +285,31 @@ The `expiration_policy` block supports:
A duration in seconds with up to nine fractional digits, terminated by 's'.
Example - "3.5s".

The `dead_letter_policy` block supports:

* `dead_letter_topic` -
(Optional)
The name of the topic to which dead letter messages should be published.
Format is `projects/{project}/topics/{topic}`.
The Cloud Pub/Sub service\naccount associated with the enclosing subscription's
parent project (i.e.,
service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com) must have
permission to Publish() to this topic.
The operation will fail if the topic does not exist.
Users should ensure that there is a subscription attached to this topic
since messages published to a topic with no subscriptions are lost.

* `max_delivery_attempts` -
(Optional)
The maximum number of delivery attempts for any message. The value must be
between 5 and 100.
The number of delivery attempts is defined as 1 + (the sum of number of
NACKs and number of times the acknowledgement deadline has been exceeded for the message).
A NACK is any call to ModifyAckDeadline with a 0 deadline. Note that
client libraries may automatically extend ack_deadlines.
This field will be honored on a best effort basis.
If this parameter is 0, a default value of 5 is used.

## Attributes Reference

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