microsoft / terraform-provider-azuredevops

Terraform Azure DevOps provider
https://www.terraform.io/docs/providers/azuredevops/
MIT License
386 stars 276 forks source link

changing the project description forces a replacement of `azuredevops_group_membership` #523

Open RodrigoGroener opened 2 years ago

RodrigoGroener commented 2 years ago

Community Note

Terraform (and Azure DevOps Provider) Version

Terraform v1.1.2 on linux_amd64

Affected Resource(s)

Terraform Configuration Files

terraform {
  required_providers {
    azuredevops = {
      source  = "microsoft/azuredevops"
      version = ">=0.1.8"
    }
  }
}

resource "azuredevops_project" "project" {
  name               = "bug-20012022"
  description        = "This is a nice project description"
  visibility         = "private"
  version_control    = "Git"
  work_item_template = "Agile"
}

data "azuredevops_group" "build_administrators" {
  project_id = azuredevops_project.project.id
  name       = "Build Administrators"
}

resource "azuredevops_group" "project_leads" {
  scope        = azuredevops_project.project.id
  display_name = "Project Leads"
  description  = "Manage the Azure DevOps Project."
}

resource "azuredevops_group_membership" "project_leads_in_build_administrators" {
  group   = data.azuredevops_group.build_administrators.descriptor
  members = [azuredevops_group.project_leads.descriptor]
  mode    = "add"
}

Debug Output

Terraform will perform the following actions:

  # data.azuredevops_group.build_administrators will be read during apply
  # (config refers to values not yet known)
 <= data "azuredevops_group" "build_administrators"  {
      ~ descriptor = "vssgp.Uy0xLTktMTU1MTM3NDI0NS03MDY3NDU4NDEtMjA4MDM1MDc5Ny0yNjExMzk1NTM2LTYwMzE1ODU4Mi0wLTAtMC0xLTI" -> (known after apply)
      ~ id         = "vssgp.Uy0xLTktMTU1MTM3NDI0NS03MDY3NDU4NDEtMjA4MDM1MDc5Ny0yNjExMzk1NTM2LTYwMzE1ODU4Mi0wLTAtMC0xLTI" -> (known after apply)
        name       = "Build Administrators"
      ~ origin     = "vsts" -> (known after apply)
      ~ origin_id  = "3775c6d9-ab13-4bfa-b336-6271c275e70d" -> (known after apply)
        # (1 unchanged attribute hidden)
    }

  # azuredevops_group_membership.project_leads_in_build_administrators must be replaced
-/+ resource "azuredevops_group_membership" "project_leads_in_build_administrators" {
      ~ group   = "vssgp.Uy0xLTktMTU1MTM3NDI0NS03MDY3NDU4NDEtMjA4MDM1MDc5Ny0yNjExMzk1NTM2LTYwMzE1ODU4Mi0wLTAtMC0xLTI" -> (known after apply) # forces replacement
      ~ id      = "5577006791947779410" -> (known after apply)
        # (2 unchanged attributes hidden)
    }

  # azuredevops_project.project will be updated in-place
  ~ resource "azuredevops_project" "project" {
      ~ description         = "This is a nice project description" -> "This is a very nice project description"
        id                  = "f7bd21fe-aeb8-45f9-abd2-efb7f8bcafc5"
        name                = "bug-20012022"
        # (5 unchanged attributes hidden)
    }

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

Panic Output

Expected Behavior

terraform should only updated azuredevops_project.project in-place.

Actual Behavior

Steps to Reproduce

  1. terraform apply
  2. change the project description
  3. terraform plan

Important Factoids

References

xuzhang3 commented 2 years ago

I did some investigation, there's similar issue in Terraform core: https://github.com/hashicorp/terraform/issues/27776 If I set the azuredevops_group.build_administrators.project_id to a fixed string value, there will be no diff during plan, since the value of project_id not changed but Terraform core treat this property has been changed, this seems a bug in Terraform core.

smcolligan commented 2 years ago

I've been encountering a similar issue with the azuredevops_project data source that seems to be related to this and/or suffer from the same root issue.

I've been using the data source in child modules following this pattern:

It seems that any change to the parent project (changing the description, etc.) forces replacement on any child resource that consumes the id property from the azuredevops_project data source. In some cases it says the id will be (known after apply), in other cases it gives a similar message if the id is used in a scope attribute.

In the following sample, I'm creating two groups. One references the id from the project resource, the other references the id from the project data source (which references the name from the project resource):

resource "azuredevops_project" "project" {
  name               = "Test Project"
  description        = "Test Project Description"
  visibility         = "private"
  version_control    = "Git"
  work_item_template = "Agile"

  features = {
    "testplans" = "disabled"
    "artifacts" = "disabled"
  }
}

resource "azuredevops_group" "group_resource_reference" {
  scope        = azuredevops_project.project.id
  display_name = "Test group resource"
  description  = "Test description resource"
}

data "azuredevops_project" "project" {
  name = azuredevops_project.project.name
}

resource "azuredevops_group" "group_data_reference" {
  scope        = data.azuredevops_project.project.id
  display_name = "Test group data"
  description  = "Test description data"
}

If I create this, and then change the project's description value and re-run, I get this plan:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place
-/+ destroy and then create replacement
 <= read (data resources)

Terraform will perform the following actions:

  # data.azuredevops_project.project will be read during apply
  # (config refers to values not yet known)
 <= data "azuredevops_project" "project"  {
      ~ description         = "Test Project Description" -> (known after apply)
      ~ features            = {} -> (known after apply)
      ~ id                  = "7214ca6b-906c-4674-8593-b98b19125c58" -> (known after apply)
        name                = "Test Project"
      ~ process_template_id = "adcc42ab-9882-485e-a3ed-7678f01f66bc" -> (known after apply)
      - project_id          = "7214ca6b-906c-4674-8593-b98b19125c58" -> null
      ~ version_control     = "Git" -> (known after apply)
      ~ visibility          = "private" -> (known after apply)
      ~ work_item_template  = "Agile" -> (known after apply)
    }

  # azuredevops_group.group_data_reference must be replaced
-/+ resource "azuredevops_group" "group_data_reference" {
      ~ descriptor     = "vssgp.Uy0xLTktMTU1MTM3NDI0NS03NzExNjE4Ni05MDI4NDEwNy0yMjkzNDcxODY0LTE2OTk4MDc5MDItMS00MTA5ODEwNjU3LTIwNjM5MjMyNzUtMzE1Njg3MDg4OS00MDc3NjM1MjEx" -> (known after apply)
      ~ domain         = "vstfs:///Classification/TeamProject/7214ca6b-906c-4674-8593-b98b19125c58" -> (known after apply)
      ~ id             = "vssgp.Uy0xLTktMTU1MTM3NDI0NS03NzExNjE4Ni05MDI4NDEwNy0yMjkzNDcxODY0LTE2OTk4MDc5MDItMS00MTA5ODEwNjU3LTIwNjM5MjMyNzUtMzE1Njg3MDg4OS00MDc3NjM1MjEx" -> (known after apply)
      + mail           = (known after apply)
      ~ members        = [] -> (known after apply)
      ~ origin         = "vsts" -> (known after apply)
      ~ origin_id      = "e308567b-388b-4323-95e4-29f91c26248d" -> (known after apply)
      ~ principal_name = "[Test Project]\\Test group data" -> (known after apply)
      ~ scope          = "7214ca6b-906c-4674-8593-b98b19125c58" -> (known after apply) # forces replacement
      ~ subject_kind   = "group" -> (known after apply)
      ~ url            = "https://vssps.dev.azure.com/kc-digitalmarketing/_apis/Graph/Groups/vssgp.Uy0xLTktMTU1MTM3NDI0NS03NzExNjE4Ni05MDI4NDEwNy0yMjkzNDcxODY0LTE2OTk4MDc5MDItMS00MTA5ODEwNjU3LTIwNjM5MjMyNzUtMzE1Njg3MDg4OS00MDc3NjM1MjEx" -> (known after apply)
        # (2 unchanged attributes hidden)
    }

  # azuredevops_project.project will be updated in-place
  ~ resource "azuredevops_project" "project" {
      ~ description         = "Test Project Description" -> "Test Project Description - NEW VALUE"
        id                  = "7214ca6b-906c-4674-8593-b98b19125c58"
        name                = "Test Project"
        # (5 unchanged attributes hidden)
    }

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

Changes to a resource shouldn't (and don't) generate a new id property, but in this case, that seems to be how the change is being interpreted by dependent resources.

smcolligan commented 2 years ago

I did some investigation, there's similar issue in Terraform core: hashicorp/terraform#27776 If I set the azuredevops_group.build_administrators.project_id to a fixed string value, there will be no diff during plan, since the value of project_id not changed but Terraform core treat this property has been changed, this seems a bug in Terraform core.

Also, I don't see how the issue you reference above is related. Both my issue and OP's issue seem to be caused by child resources responding to a change in their parent's id property. That property value isn't changing.

smcolligan commented 2 years ago

One final example...

If I change my test code to this and reference an existing project, this issue does not exist.

data "azuredevops_project" "project" {
  name = "Test Project"
}

resource "azuredevops_group" "group_data_reference" {
  scope        = data.azuredevops_project.project.id
  display_name = "SMC Test"
  description  = "SMC Test"
}

So it seems like the project resource is causing updates in the data sources that reference the project name or id attribute when it shouldn't.

xuzhang3 commented 2 years ago

@smcolligan I did not dig into the Terraform to find out for how Terraform-SDK generate the resource update/diff. In the usage scenario here, Terraform should ignore the changes, and as you posted here, the workaround for this issue is use a data source instead of a resource.

kjgen commented 1 year ago

Has there been any updates related to this issue? I'm currently facing the same issue on my end. I'm creating azuredevops_project with terraform and referencing the "Contributors" project group in each. I'm adding members to the Contributor groups all in the same terraform plan. If I remove a project from my terraform files, terraform wants to replace the azuredevops_group_membership resources because it requires to read the data sources of the group which doesn't seem right.

  # data.azuredevops_group.project_contributors["<custom id>"] will be read during apply
  # (depends on a resource or a module with changes pending)
 <= data "azuredevops_group" "project_contributors" {
      + descriptor = (known after apply)
      + id         = (known after apply)
      + name       = "Contributors"
      + origin     = (known after apply)
      + origin_id  = (known after apply)
      + project_id = "<redacted>"
    }

  # azuredevops_group_membership.project_contributors["<custom id>"] must be replaced
-/+ resource "azuredevops_group_membership" "project_contributors" {
      ~ group   = "vssgp.<redacted>" -> (known after apply) # forces replacement
      ~ id      = "<redacted>" -> (known after apply)
        # (2 unchanged attributes hidden)