terraform-google-modules/terraform-google-pubsub

Recreating of push_topic_binding every time on version 2

gjermundgaraba opened this issue · 14 comments

It seems like the resource "google_pubsub_topic_iam_member" "push_topic_binding" is being recreated needlessly every time because of "unknown" member.

  # module.google_pubsub["REMOVED"].google_pubsub_topic_iam_member.push_topic_binding["REMOVED"] must be replaced
-/+ resource "google_pubsub_topic_iam_member" "push_topic_binding" {
      ~ etag    = "REMOVED" -> (known after apply)
      ~ id      = "REMOVED" -> (known after apply)
      ~ member  = "serviceAccount:service-REMOVED@gcp-sa-pubsub.iam.gserviceaccount.com" -> (known after apply) # forces replacement
        # (3 unchanged attributes hidden)
    }

The pubsub topics and everything was set up in a new project, so there is no lingering old state that should cause this. I am not an expert in terraform, but it seems to be related to the way the pubsub_svc_account_email is created from the data block:
pubsub_svc_account_email = "service-${data.google_project.project.number}@gcp-sa-pubsub.iam.gserviceaccount.com"

Here is my setup as well for reference in case I have done something wrong that might cause this:

module "google_pubsub" {
    depends_on = [
        module.project_setup,
        google_project_iam_member.pubsub_service_account_roles,
        module.empower_platform_cloud_build_setup,
        module.cloud_run]
    for_each = local.pubsub_topics_and_subscriptions

    source = "terraform-google-modules/pubsub/google"
    version = "~> 2.0"

    topic = each.key
    create_topic = each.value.create_topic
    project_id = var.project

    push_subscriptions = [for subscription in each.value.subscriptions: {
        name = "subscription-${each.key}-${subscription.service_name}"
        oidc_service_account_email = google_service_account.pubsub_subscription_service_account.email
        ack_deadline_seconds = 200
        push_endpoint = "${lookup(module.cloud_run.deployed_cloud_run_services, subscription.service_name).url}${subscription.root_path}/pubsub/${each.key}"
        enable_message_ordering = true
    }]
}

Thanks for the report, this does look like a bug. We should add ignore_changes on the field.

I believe the issue is that both pull_topic_binding & push_topic_binding loop through the subscriptions to create the bindings when this should be a one-time thing. Additionally, don't think we need them both since it's the same service account binding to the same role i.e. roles/pubsub.publisher in both cases. Something like topic_binding should do.

@morgante - let me know what you think. I can submit a PR for it.

We need to loop through the subscriptions, but we can ignore changes to the member. A PR would be welcome.

PR is up for review.

I believe this is being triggered due to module depends_on in your setup. Module depends_on is deferring the read to apply time so even with the ignore_changes if any resources in the depends_on change, there will an extra read operation. Adding ignore changes throughout will prevent recreate operation though.

@morgante I think another approach could be to let optionally users pass in pubsub_svc_account_email while preserving existing default behavior. This means we wont have to have ignore_changes throughout the module.

Module depends_on is deferring the read to apply time so even with the ignore_changes if any resources in the depends_on change, there will an extra read operation. Adding ignore changes throughout will prevent recreate operation though.

Good point, I think maybe the dependencies might not all be needed? (Especially not explicitly.)

@morgante I think another approach could be to let optionally users pass in pubsub_svc_account_email while preserving existing default behavior. This means we wont have to have ignore_changes throughout the module.

I'd rather not force users to look that up manually.

Good point, I think maybe the dependencies might not all be needed?

Yeah I agree, if there was no module depends_on and only implicit deps the dag should be evaluated properly. This is also seems to be Terraform's official stance on this issue hashicorp/terraform#26383 (comment)

@bjaanes could you elaborate on why you need these explicit dependencies

depends_on = [
        module.project_setup,
        google_project_iam_member.pubsub_service_account_roles,
        module.empower_platform_cloud_build_setup,
        module.cloud_run]

@bjaanes could you elaborate on why you need these explicit dependencies

I technically might not. It is just a practice I developed on my own so I can easily see dependencies and understand the order of things.

There might be one exception if I hadn't set this up exactly like I do, which is the module.project_setup which sets up all the roles and permissions needed for the terraform service account I am using. However, in this particular case it should be OK since the "module.cloud_run.deployed_cloud_run_services" has an explicit dependency on the project_setup part.

If you have suggestions on doing this better I would of course appreciate any feedback on that ;)

@bjaanes thanks for the context. Could you check if this error still happens without the explicit dependencies?

@bharathkkb I just did an update before and after removing the explicit dependencies and it does indeed solve the problem.

esn89 commented

I am experiencing something similar: I am upgrading from 1.9.0 to 2.0.0 and noticed that a lot of resources are being destroyed/created.
Namely google_pubsub_subscriptions and google_pubsub_subscription_iam_members. Is there a workaround for this to prevent this from happening?

@esn89 Please share a copy of your config and Terraform plan for us to understand why it's being recreated.

esn89 commented

@morgante

A copy of the config is here:

module "pubsub-01" {
  source     = "git::https://github.com/terraform-google-modules/terraform-google-pubsub.git?ref=v2.0.0"
  project_id = var.project_id
  topic      = var.topics[0]

  pull_subscriptions = [
    {
      name                 = "test1"
      expiration_policy    = ""
      ack_deadline_seconds = 20
    },
    {
      name                 = "test1"
      expiration_policy    = ""
      ack_deadline_seconds = 20
    },
  ]
}
# ...so on and so forth for each of the topics in the variables of "topics".  Not looped
module "pubsub-02" {
  source     = "git::https://github.com/terraform-google-modules/terraform-google-pubsub.git?ref=v2.0.0"
  project_id = var.project_id
  topic      = var.topics[0]

  pull_subscriptions = [
    {
      name                 = "test2"
      expiration_policy    = ""
      ack_deadline_seconds = 20
    },
    {
      name                 = "test2"
      expiration_policy    = ""
      ack_deadline_seconds = 20
    },
  ]
}

Inside of variables.tf:

variable "topics" {
  description = "Metadata Topics"
  type        = list(any)
  default     = ["test1", "test2", "test3", "test4", "test5"]
}

The plan:

# module.pubsub-01.google_pubsub_subscription.pull_subscriptions will be destroyed
  - resource "google_pubsub_subscription" "pull_subscriptions" {
      - ack_deadline_seconds       = 20 -> null
      - enable_message_ordering    = false -> null
      - id                         = "projects/myproject/subscriptions/test1" -> null
      - labels                     = {} -> null
      - message_retention_duration = "604800s" -> null
      - name                       = "test1" -> null
      - path                       = "projects/myproject/subscriptions/test1" -> null
      - project                    = "myproject" -> null
      - retain_acked_messages      = false -> null
      - topic                      = "projects/myproject/topics/schema" -> null

      - expiration_policy {}
    }

  # module.pubsub-01.google_pubsub_subscription.pull_subscriptions[1] will be destroyed
  - resource "google_pubsub_subscription" "pull_subscriptions" {
      - ack_deadline_seconds       = 20 -> null
      - enable_message_ordering    = false -> null
      - id                         = "projects/myproject/subscriptions/test2" -> null
      - labels                     = {} -> null
      - message_retention_duration = "604800s" -> null
      - name                       = "test2" -> null
      - path                       = "projects/myproject/subscriptions/test2" -> null
      - project                    = "myproject" -> null
      - retain_acked_messages      = false -> null
      - topic                      = "projects/myproject/topics/schema" -> null

      - expiration_policy {}
    }

  # module.pubsub-01.google_pubsub_subscription.pull_subscriptions["test1"] will be created
  + resource "google_pubsub_subscription" "pull_subscriptions" {
      + ack_deadline_seconds       = 20
      + id                         = (known after apply)
      + message_retention_duration = "604800s"
      + name                       = "test1"
      + path                       = (known after apply)
      + project                    = "myproject"
      + topic                      = "schema"

      + expiration_policy {}
    }

  # module.pubsub-01.google_pubsub_subscription.pull_subscriptions["test2"] will be created
  + resource "google_pubsub_subscription" "pull_subscriptions" {
      + ack_deadline_seconds       = 20
      + id                         = (known after apply)
      + message_retention_duration = "604800s"
      + name                       = "test2"
      + path                       = (known after apply)
      + project                    = "myproject"
      + topic                      = "schema"

      + expiration_policy {}
    }

  # module.pubsub-01.google_pubsub_subscription_iam_member.pull_subscription_binding will be destroyed
  - resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      - etag         = "BwXApy5W2/Q=" -> null
      - id           = "projects/myproject/subscriptions/test1/roles/pubsub.subscriber/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project      = "myproject" -> null
      - role         = "roles/pubsub.subscriber" -> null
      - subscription = "test1" -> null
    }

  # module.pubsub-01.google_pubsub_subscription_iam_member.pull_subscription_binding[1] will be destroyed
  - resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      - etag         = "BwXApy5SJ7o=" -> null
      - id           = "projects/myproject/subscriptions/test2/roles/pubsub.subscriber/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project      = "myproject" -> null
      - role         = "roles/pubsub.subscriber" -> null
      - subscription = "test2" -> null
    }

  # module.pubsub-01.google_pubsub_subscription_iam_member.pull_subscription_binding["test1"] will be created
  + resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      + etag         = (known after apply)
      + id           = (known after apply)
      + member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project      = "myproject"
      + role         = "roles/pubsub.subscriber"
      + subscription = "test1"
    }

  # module.pubsub-01.google_pubsub_subscription_iam_member.pull_subscription_binding["test2"] will be created
  + resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      + etag         = (known after apply)
      + id           = (known after apply)
      + member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project      = "myproject"
      + role         = "roles/pubsub.subscriber"
      + subscription = "test2"
    }

  # module.pubsub-01.google_pubsub_topic_iam_member.pull_topic_binding will be destroyed
  - resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      - etag    = "BwXApy4NRek=" -> null
      - id      = "projects/myproject/topics/schema/roles/pubsub.publisher/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project = "myproject" -> null
      - role    = "roles/pubsub.publisher" -> null
      - topic   = "projects/myproject/topics/schema" -> null
    }

  # module.pubsub-01.google_pubsub_topic_iam_member.pull_topic_binding[1] will be destroyed
  - resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      - etag    = "BwXApy4NRek=" -> null
      - id      = "projects/myproject/topics/schema/roles/pubsub.publisher/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project = "myproject" -> null
      - role    = "roles/pubsub.publisher" -> null
      - topic   = "projects/myproject/topics/schema" -> null
    }

  # module.pubsub-01.google_pubsub_topic_iam_member.pull_topic_binding["test1"] will be created
  + resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      + etag    = (known after apply)
      + id      = (known after apply)
      + member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project = "myproject"
      + role    = "roles/pubsub.publisher"
      + topic   = "projects/myproject/topics/schema"
    }

  # module.pubsub-01.google_pubsub_topic_iam_member.pull_topic_binding["test2"] will be created
  + resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      + etag    = (known after apply)
      + id      = (known after apply)
      + member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project = "myproject"
      + role    = "roles/pubsub.publisher"
      + topic   = "projects/myproject/topics/schema"
    }

  # module.pubsub-02.google_pubsub_subscription.pull_subscriptions will be destroyed
  - resource "google_pubsub_subscription" "pull_subscriptions" {
      - ack_deadline_seconds       = 20 -> null
      - enable_message_ordering    = false -> null
      - id                         = "projects/myproject/subscriptions/1-application" -> null
      - labels                     = {} -> null
      - message_retention_duration = "604800s" -> null
      - name                       = "1-application" -> null
      - path                       = "projects/myproject/subscriptions/1-application" -> null
      - project                    = "myproject" -> null
      - retain_acked_messages      = false -> null
      - topic                      = "projects/myproject/topics/application" -> null

      - expiration_policy {}
    }

  # module.pubsub-02.google_pubsub_subscription.pull_subscriptions[1] will be destroyed
  - resource "google_pubsub_subscription" "pull_subscriptions" {
      - ack_deadline_seconds       = 20 -> null
      - enable_message_ordering    = false -> null
      - id                         = "projects/myproject/subscriptions/2-application" -> null
      - labels                     = {} -> null
      - message_retention_duration = "604800s" -> null
      - name                       = "2-application" -> null
      - path                       = "projects/myproject/subscriptions/2-application" -> null
      - project                    = "myproject" -> null
      - retain_acked_messages      = false -> null
      - topic                      = "projects/myproject/topics/application" -> null

      - expiration_policy {}
    }

  # module.pubsub-02.google_pubsub_subscription.pull_subscriptions["1-application"] will be created
  + resource "google_pubsub_subscription" "pull_subscriptions" {
      + ack_deadline_seconds       = 20
      + id                         = (known after apply)
      + message_retention_duration = "604800s"
      + name                       = "1-application"
      + path                       = (known after apply)
      + project                    = "myproject"
      + topic                      = "application"

      + expiration_policy {}
    }

  # module.pubsub-02.google_pubsub_subscription.pull_subscriptions["2-application"] will be created
  + resource "google_pubsub_subscription" "pull_subscriptions" {
      + ack_deadline_seconds       = 20
      + id                         = (known after apply)
      + message_retention_duration = "604800s"
      + name                       = "2-application"
      + path                       = (known after apply)
      + project                    = "myproject"
      + topic                      = "application"

      + expiration_policy {}
    }

  # module.pubsub-02.google_pubsub_subscription_iam_member.pull_subscription_binding will be destroyed
  - resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      - etag         = "BwXApy4F6OE=" -> null
      - id           = "projects/myproject/subscriptions/1-application/roles/pubsub.subscriber/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project      = "myproject" -> null
      - role         = "roles/pubsub.subscriber" -> null
      - subscription = "1-application" -> null
    }

  # module.pubsub-02.google_pubsub_subscription_iam_member.pull_subscription_binding[1] will be destroyed
  - resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      - etag         = "BwXApy4HWYQ=" -> null
      - id           = "projects/myproject/subscriptions/2-application/roles/pubsub.subscriber/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project      = "myproject" -> null
      - role         = "roles/pubsub.subscriber" -> null
      - subscription = "2-application" -> null
    }

  # module.pubsub-02.google_pubsub_subscription_iam_member.pull_subscription_binding["1-application"] will be created
  + resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      + etag         = (known after apply)
      + id           = (known after apply)
      + member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project      = "myproject"
      + role         = "roles/pubsub.subscriber"
      + subscription = "1-application"
    }

  # module.pubsub-02.google_pubsub_subscription_iam_member.pull_subscription_binding["2-application"] will be created
  + resource "google_pubsub_subscription_iam_member" "pull_subscription_binding" {
      + etag         = (known after apply)
      + id           = (known after apply)
      + member       = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project      = "myproject"
      + role         = "roles/pubsub.subscriber"
      + subscription = "2-application"
    }

  # module.pubsub-02.google_pubsub_topic_iam_member.pull_topic_binding will be destroyed
  - resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      - etag    = "BwXApy22N5A=" -> null
      - id      = "projects/myproject/topics/application/roles/pubsub.publisher/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project = "myproject" -> null
      - role    = "roles/pubsub.publisher" -> null
      - topic   = "projects/myproject/topics/application" -> null
    }

  # module.pubsub-02.google_pubsub_topic_iam_member.pull_topic_binding[1] will be destroyed
  - resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      - etag    = "BwXApy22N5A=" -> null
      - id      = "projects/myproject/topics/application/roles/pubsub.publisher/serviceaccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com" -> null
      - project = "myproject" -> null
      - role    = "roles/pubsub.publisher" -> null
      - topic   = "projects/myproject/topics/application" -> null
    }

  # module.pubsub-02.google_pubsub_topic_iam_member.pull_topic_binding["1-application"] will be created
  + resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      + etag    = (known after apply)
      + id      = (known after apply)
      + member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project = "myproject"
      + role    = "roles/pubsub.publisher"
      + topic   = "projects/myproject/topics/application"
    }

  # module.pubsub-02.google_pubsub_topic_iam_member.pull_topic_binding["2-application"] will be created
  + resource "google_pubsub_topic_iam_member" "pull_topic_binding" {
      + etag    = (known after apply)
      + id      = (known after apply)
      + member  = "serviceAccount:service-123412341234@gcp-sa-pubsub.iam.gserviceaccount.com"
      + project = "myproject"
      + role    = "roles/pubsub.publisher"
      + topic   = "projects/myproject/topics/application"
    }

This is from changing the module version from 1.9.0 to 2.0.0.

As noted in the upgrade guide, the state location for subscriptions has moved. You will need to do a one-time migration, but it should not attempt to recreate them once you have.