Thrasi
Thrasi

Reputation: 498

How to update google_bigquery_table_iam_member to point to new table on recreation due to an updated schema

I have a BigQuery table and an add a service account as an iam member to this table:

resource "google_bigquery_table" "table" {
  dataset_id          = dataset
  table_id            = table
  project             = project
  schema              = "jsonSchema.json"
}

resource "google_bigquery_table_iam_member" "access_right" {
  project    = google_bigquery_table.table.project
  dataset_id = google_bigquery_table.table.dataset_id
  table_id   = google_bigquery_table.table.table_id
  role       = "roles/bigquery.dataEditor"
  member     = "serviceAccount:[email protected]"
}

Removing a column from jsonSchema.json and applying the changes enforces the destruction of the table and the creation of a new one:

Terraform will perform the following actions:

  # module.module.google_bigquery_table.table must be replaced
-/+ resource "google_bigquery_table" "table" {
      ...
      ~ schema              = jsonencode(
          ~ [ # forces replacement
                # (8 unchanged elements hidden)
                {
                    mode = "REQUIRED"
                    name = "column1"
                    type = "TIMESTAMP"
                },
              - {
                  - mode = "REQUIRED"
                  - name = "column2"
                  - type = "STRING"
                },
            ]
        )
      ...

Plan: 1 to add, 0 to change, 1 to destroy.

At this point the google_bigquery_table_iam_member resource created is still pointing to the old table in the state. However, in GCP the service account no longer has access to the non existing table and no new access has been given to the newly created table.

Running terraform apply a second time it notices the missing access

Terraform will perform the following actions:

  # module.module.google_bigquery_table_iam_member.access_rights will be created
  + resource "google_bigquery_table_iam_member" "access_rights" {
      + dataset_id = "dataset"
      + etag       = (known after apply)
      + id         = (known after apply)
      + member     = "serviceAccount:[email protected]"
      + project    = "project"
      + role       = "roles/bigquery.dataEditor"
      + table_id   = "table"
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Is it possible to achieve this in a single step (a single terraform apply)? i.e.

Upvotes: 2

Views: 1131

Answers (3)

Thrasi
Thrasi

Reputation: 498

Running this setup with:

terraform version 1.1.4
google provider version 4.9.0

Rather than:

terraform version 1.1.3
google provider version 4.5.0

eliminated the issue.

However, if you for some reason cannot do that, @PjoterS has an alternative solution

Upvotes: 1

PjoterS
PjoterS

Reputation: 14092

As mentioned in the comment section by OP, the solution was to upgrade Terraform and the provider versions. However as I performed a few tests I wanted to share output of them.

Another option to solve this issue is to use recreate. Below Example how it works.

main.tf

### Creating Service Account
resource "google_service_account" "bigquerytest" {
  project      = "<MyProjectID>"
  account_id   = "bigquery-table"
  display_name = "bigquery-table-test"
  provider     = google
}
### Re-Create table
resource "google_bigquery_table" "table" {
  dataset_id = "test"
  table_id = "bqtable"
  project = "<MyProjectID>"
  schema  = file("/home/<myuser>/terrabq/jsonSchema.json")
  deletion_protection=false
}

### DataEditor binding
resource "google_bigquery_table_iam_member" "access-right" {
  project = "<MyProjectID>"
  dataset_id = "<YourDataset_id>"
  table_id = "bqtable"
  role = "roles/bigquery.dataEditor"
  member = "serviceAccount:${google_service_account.bigquerytest.email}"
}

jsonSchema.json

[
  {
    "mode": "NULLABLE",
    "name": "source",
    "type": "STRING"
  },
  {
    "mode": "NULLABLE",
    "name": "status",
    "type": "STRING"
  },
  {
  "mode": "NULLABLE",
  "name": "test",
  "type": "STRING"
  },
  {
  "mode": "NULLABLE",
  "name": "test4",
  "type": "STRING"
  }
]

Scenario: Create a new Table - bqtable with specific schema, create ServiceAccount and proper IAM member permission for this Table.

Output:

...
Plan: 3 to add, 0 to change, 0 to destroy.
...
google_service_account.bigquerytest: Creating...
google_bigquery_table.table: Creating...
google_bigquery_table.table: Creation complete after 1s [id=projects/<myproject>/datasets/test/tables/bqtable]
google_service_account.bigquerytest: Creation complete after 1s [id=projects/<myproject>/serviceAccounts/bigquery-table@<myproject>.iam.gserviceaccount.com]
google_bigquery_table_iam_member.access-right: Creating...
google_bigquery_table_iam_member.access-right: Creation complete after 4s [id=projects/<myproject>/datasets/test/tables/bqtable/roles/bigquery.dataEditor/serviceAccount:bigquery-table@<myproject>.iam.gserviceaccount.com]

Next step is to change the schema in jsonSchema.json.

NOTE

  • When you are adding column in the schema, the table won't be recreated. It will just update the table and in all new column will be NULL value.
 # google_bigquery_table.table will be updated in-place
  ~ resource "google_bigquery_table" "table" {

In BQ it would looks like that:

  • When you are removing column from schema
  # google_bigquery_table.table must be replaced
-/+ resource "google_bigquery_table" "table" {

Please keep in mind that if the Table will be recreated, all data from it will be purged.

Issued scenario:

If you will just change the schema (remove column), the table will be recreated but IAM rules weren't updated.

Plan output was probably like that: Plan: 1 to add, 0 to change, 1 to destroy.

OP was able to solve this issue with an updated version of Terraform and provider.

However, if you still have issues, you can use -replace flag to re-create resource.

$ terraform apply -replace=google_bigquery_table_iam_member.access-right

Actions taken by terraform was:

  # google_bigquery_table.table must be replaced
-/+ resource "google_bigquery_table" "table" {
...
  # google_bigquery_table_iam_member.access-right will be replaced, as requested
-/+ resource "google_bigquery_table_iam_member" "access-right" {
      ~ etag       = "<randomString>" -> (known after apply)
      ~ id         = "projects/<myproject>/datasets/<mydataset>/tables/bqtable/roles/bigquery.dataEditor/serviceAccount:bigquery-table@<myproject>.iam.gserviceaccount.com" -> (known after apply)
      ~ table_id   = "projects/<myproject>/datasets/<mydataset>/tables/bqtable" -> "bqtable"
        # (4 unchanged attributes hidden)
    }

Plan: 2 to add, 0 to change, 2 to destroy.

In Addition to depends_on in terraform, it's used mainly for ordering or to postpone creation of resources.

To sum up:

  • Solution which worked for OP was updating Terraform and Provider versions
  • Another solution is to use terraform apply -replace=[resource.resourcename]

Upvotes: 1

mariux
mariux

Reputation: 3127

This is weird behavior maybe caused by the provider knowing the result of the resource fields beforehand and confusing terraforms implicit dependency detection.

You can try to force the dependency by adding an explicit depends_on to the iam resource to ensure recreation:

resource "google_bigquery_table_iam_member" "access_right" {
  project    = google_bigquery_table.table.project
  dataset_id = google_bigquery_table.table.dataset_id
  table_id   = google_bigquery_table.table.table_id
  role       = "roles/bigquery.dataEditor"
  member     = "serviceAccount:[email protected]"

  depends_on = [google_bigquery_table.table]
}

in this case terraform should be able to detect changes as you now also depend on changing fields like etag that are only known after apply.

To better understand the issue a plan output of an initial apply would help.

Upvotes: 0

Related Questions