terraform-provider-gitlab icon indicating copy to clipboard operation
terraform-provider-gitlab copied to clipboard

Perpetual diff when using multiple deploy_access_levels in gitlab_project_protected_environment resource

Open dschaaff opened this issue 3 years ago • 7 comments
trafficstars

GitLab Provider version

3.15.1

GitLab version

gitlab.com

Terraform version

1.2.1

Relevant Terraform Configuration

resource "gitlab_project_protected_environment" "staging" {
  project     = gitlab_project.this.id
  environment = "staging"
  deploy_access_levels {
    access_level = "maintainer"
  }
  deploy_access_levels {
    group_id = var.sre_group_id
  }
}

Description

When using a single deploy_access_group things work as expected. When using multiple deploy_access_levels however, there is a perpetual diff leading to the resource being recreate on every terraform apply.

-/+ resource "gitlab_project_protected_environment" "staging" {
      ~ id          = "xxxxxxxx:staging" -> (known after apply)
        # (2 unchanged attributes hidden)

      ~ deploy_access_levels {
          ~ access_level_description = "SRE" -> (known after apply)
          - group_id                 = xxxxxxx -> null # forces replacement
          - user_id                  = 0 -> null
            # (1 unchanged attribute hidden)
        }
      ~ deploy_access_levels {
          ~ access_level             = "maintainer" -> (known after apply)
          ~ access_level_description = "Maintainers" -> (known after apply)
          ~ group_id                 = 0 -> xxxxxxx # forces replacement
          - user_id                  = 0 -> null
        }
    }

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

dschaaff avatar Jun 14 '22 22:06 dschaaff

@dschaaff Is the group (with id var.sre_group_id) shared with the project gitlab_project.this.id?

It seems to be a current limitation of the GitLab API that in case it's not shared with the group it's just ignored without any errors and thus, the provider runs into those perpetual state diffs.

It's mentioned in the upstream GitLab API docs and I've just added a little note block to the gitlab_project_protected_environment resource in the provider with #1210

@dschaaff can you please confirm if this was or wasn't the issue for you? (Also if you feel like it, it would be great to create an upstream GitLab issue to properly report errors for this situation.)

timofurrer avatar Aug 10 '22 14:08 timofurrer

I do have that project shared with the group directly

resource "gitlab_project_share_group" "sre" {
  project_id   = gitlab_project.this.id
  group_id     = var.sre_group_id
  group_access = "developer"
}

dschaaff avatar Aug 10 '22 19:08 dschaaff

@dschaaff and I assume the resources are applied in the correct order? (well at least it should apply the second time I suppose)

Can you post some debug logs using TF_LOG=debug (make sure to redact sensitive data) ?

timofurrer avatar Aug 10 '22 19:08 timofurrer

I'm attaching a redacted trace log with the relevant portion of data gitlab_tf_log_trace.zip .

Here is the full source of the terraform code

resource "gitlab_project" "this" {
  name                                             = var.name
  namespace_id                                     = redacted
  approvals_before_merge                           = 1
  archive_on_destroy                               = true # archive project instead of delete on terraform destroy
  ci_forward_deployment_enabled                    = true
  description                                      = var.description
  forking_access_level                             = "disabled"
  issues_enabled                                   = false
  merge_pipelines_enabled                          = true
  merge_trains_enabled                             = true
  only_allow_merge_if_all_discussions_are_resolved = true
  only_allow_merge_if_pipeline_succeeds            = true
  packages_enabled                                 = true
  printing_merge_request_link_enabled              = true
  public_builds                                    = false
  push_rules {
    prevent_secrets = true
  }
  remove_source_branch_after_merge = true
  shared_runners_enabled           = false
  squash_option                    = "default_off"
  # TODO: what changes to ignore
  lifecycle {
    ignore_changes  = []
    prevent_destroy = true
  }
}

resource "gitlab_branch_protection" "this" {
  for_each                     = toset(["master", "main"])
  project                      = gitlab_project.this.id
  branch                       = each.key
  push_access_level            = "maintainer"
  merge_access_level           = "maintainer"
  unprotect_access_level       = "maintainer"
  allow_force_push             = false
  code_owner_approval_required = true
}

resource "gitlab_project_share_group" "sre" {
  project_id   = gitlab_project.this.id
  group_id     = var.sre_group_id
  group_access = "developer"
}

# TODO: this resource doesn't fully work right now. It produces a perpetual diff
resource "gitlab_project_protected_environment" "staging" {
  project     = gitlab_project.this.id
  environment = "test"
  deploy_access_levels {
    access_level = "maintainer"
  }
  deploy_access_levels {
    group_id = var.sre_group_id
  }
  depends_on = [
    gitlab_project_share_group.sre
  ]
}

module "services_repos" {
  source = "./modules/microservice-project"
  for_each = {
    "my-app" = {
      name        = "my-app"
      description = "test"
    }
  }
  name         = each.value["name"]
  description  = each.value["description"]
  sre_group_id = gitlab_group.sre.id
}

dschaaff avatar Aug 10 '22 20:08 dschaaff

Okay, according to the logs I think the problem is that the deploy_access_levels attribute is a list (which is ordered) and the GitLab API returns the deploy access levels in a different order than it previously did when they were first persistent in state.

This is the order it's returned by the API:

"deploy_access_levels": [
  {
   "access_level": 40,
   "access_level_description": "SRE",
   "user_id": null,
   "group_id": 51509863,
   "group_inheritance_type": 0
  },
  {
   "access_level": 40,
   "access_level_description": "Maintainers",
   "user_id": null,
   "group_id": null,
   "group_inheritance_type": 0
  }
]

The logs don't contain the API response for when it was queried the first time. Do you mind posting that parts of the logs, too to verify? And maybe also the part of the state file which contains the state for that particular resource.

The Terraform provider SDK supports a set type, but this wouldn't probably work either because of the computed attributes within the elements of the deploy_access_levels (I need to verify this). A possible solution might be to just deterministically order the deploy access levels before writing them to state and hack our way around with customize diff / suppress diff functions so that the user doesn't have to order it the same way in the HCL.

timofurrer avatar Aug 11 '22 07:08 timofurrer

I always get the response in this order

{
		"name": "test",
		"deploy_access_levels": [
			{
				"access_level": 40,
				"access_level_description": "SRE",
				"user_id": null,
				"group_id": 51509863,
				"group_inheritance_type": 0
			},
			{
				"access_level": 40,
				"access_level_description": "Maintainers",
				"user_id": null,
				"group_id": null,
				"group_inheritance_type": 0
			}
		],
		"required_approval_count": 0,
		"approval_rules": []
	},

This always has the group before the maintainers access level. If I switch my terraform resource to match that order, like below, then I no longer receive a diff on every apply.

resource "gitlab_project_protected_environment" "staging" {
  project     = gitlab_project.this.id
  environment = "test"
  deploy_access_levels {
    group_id = var.sre_group_id
  }
  deploy_access_levels {
    access_level = "maintainer"
  }
  depends_on = [
    gitlab_project_share_group.sre
  ]
}

The storage in the state file matches the order I put the deploy access levels in my resource. Here is what the state looked like originally

"instances": [
        {
          "schema_version": 0,
          "attributes": {
            "deploy_access_levels": [
              {
                "access_level": "maintainer",
                "access_level_description": "SRE",
                "group_id": 51509863,
                "user_id": 0
              },
              {
                "access_level": "maintainer",
                "access_level_description": "Maintainers",
                "group_id": 0,
                "user_id": 0
              }
            ],

The key for now seems to be to ensure that the ordering in the terraform resources exactly matches the API response from GitLab. I suppose I'd call that a bug as I wouldn't expect the order that deploy_access_levels are listed in terraform to matter.

dschaaff avatar Aug 11 '22 23:08 dschaaff

@dschaaff thanks for the confirmation! Yes, indeed that's a bug - the order you define your deploy_access_levels in shouldn't matter.

timofurrer avatar Aug 12 '22 06:08 timofurrer

This functionality has been released in v3.17.0 of the Terraform GitLab Provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading.

For further feature requests or bug reports with this functionality, please create a new GitHub issue. Thank you!

github-actions[bot] avatar Aug 24 '22 18:08 github-actions[bot]