Skip to content

Commit 3ec0939

Browse files
authored
Merge pull request #8 from sparkfabrik/feat/3414_allow_multiple_projects_groups
feat: change 'gitlab_group_id' and 'gitlab_project_id' variables to 'gitlab_group_ids' and 'gitlab_project_ids' allowing for multiple GitLab groups or projects configuration
2 parents c2acb40 + 843865f commit 3ec0939

File tree

10 files changed

+169
-70
lines changed

10 files changed

+169
-70
lines changed

CHANGELOG.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,50 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
88

99
## [Unreleased]
1010

11+
## [0.5.0] - 2025-05-30
12+
13+
[Compare with previous version](https://github.com/sparkfabrik/terraform-google-gcp-gitlab-wif/compare/0.4.0...0.5.0)
14+
15+
### :warning: Breaking change
16+
17+
The variables `gitlab_group_id` and `gitlab_project_id` have been renamed to `gitlab_group_ids` and `gitlab_project_ids` and the type has been changed from `string` to `list(string)`. This allows for multiple group and project IDs to be specified.
18+
19+
You can update your configuration simply by changing the variable names and wrapping the existing values in a list, like this:
20+
21+
**From:**
22+
23+
```hcl
24+
module "example" {
25+
source = "github.com/sparkfabrik/terraform-google-gcp-gitlab-wif?ref=main"
26+
version = ">= 0.1.0"
27+
28+
name = "My Workload Identity Federation"
29+
gcp_project_id = "awesome-gcp-project"
30+
gitlab_project_id = 42
31+
}
32+
```
33+
34+
**To:**
35+
36+
```hcl
37+
module "example" {
38+
source = "github.com/sparkfabrik/terraform-google-gcp-gitlab-wif?ref=main"
39+
version = ">= 0.1.0"
40+
41+
name = "My Workload Identity Federation"
42+
gcp_project_id = "awesome-gcp-project"
43+
gitlab_project_ids = [42]
44+
}
45+
```
46+
47+
### Added
48+
49+
- You can add GitLab groups and projects together with the same module, allowing for more flexibility in managing multiple GitLab groups and projects usint the same OIDC provider configuration.
50+
51+
### Changed
52+
53+
- Change the variables `gitlab_group_id` and `gitlab_project_id` to `gitlab_group_ids` and `gitlab_project_ids`, allowing for multiple group and project IDs to be specified.
54+
1155
## [0.4.0] - 2025-05-29
1256

1357
[Compare with previous version](https://github.com/sparkfabrik/terraform-google-gcp-gitlab-wif/compare/0.3.1...0.4.0)

README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ You can refer to the official [GitLab documentation](https://docs.gitlab.com/ci/
3434
| <a name="input_gitlab_gcp_wif_project_id_variable_name"></a> [gitlab\_gcp\_wif\_project\_id\_variable\_name](#input\_gitlab\_gcp\_wif\_project\_id\_variable\_name) | The name of the GitLab variable to store the GCP project ID for WIF. | `string` | `"GCP_WIF_PROJECT_ID"` | no |
3535
| <a name="input_gitlab_gcp_wif_provider_variable_name"></a> [gitlab\_gcp\_wif\_provider\_variable\_name](#input\_gitlab\_gcp\_wif\_provider\_variable\_name) | The name of the GitLab variable to store the GCP WIF provider name. | `string` | `"GCP_WIF_PROVIDER"` | no |
3636
| <a name="input_gitlab_gcp_wif_service_account_email_variable_name"></a> [gitlab\_gcp\_wif\_service\_account\_email\_variable\_name](#input\_gitlab\_gcp\_wif\_service\_account\_email\_variable\_name) | The name of the GitLab variable to store the GCP WIF service account email. | `string` | `"GCP_WIF_SERVICE_ACCOUNT_EMAIL"` | no |
37-
| <a name="input_gitlab_group_id"></a> [gitlab\_group\_id](#input\_gitlab\_group\_id) | The GitLab group ID to allow access from. Use this for group-level access. | `number` | `0` | no |
37+
| <a name="input_gitlab_group_ids"></a> [gitlab\_group\_ids](#input\_gitlab\_group\_ids) | The GitLab group IDs to allow access from. Use this for group-level access. | `list(number)` | `[]` | no |
3838
| <a name="input_gitlab_instance_url"></a> [gitlab\_instance\_url](#input\_gitlab\_instance\_url) | The URL of your GitLab instance. | `string` | `"https://gitlab.com"` | no |
39-
| <a name="input_gitlab_project_id"></a> [gitlab\_project\_id](#input\_gitlab\_project\_id) | The GitLab project ID to allow access from. Use this for project-level access. | `number` | `0` | no |
39+
| <a name="input_gitlab_project_ids"></a> [gitlab\_project\_ids](#input\_gitlab\_project\_ids) | The GitLab project IDs to allow access from. Use this for project-level access. | `list(number)` | `[]` | no |
4040
| <a name="input_gitlab_variables_additional"></a> [gitlab\_variables\_additional](#input\_gitlab\_variables\_additional) | Additional GitLab variables to create. This should be a map where the key is the variable name and the value is an object containing the variable properties. This allows you to define custom variables for project or group where the module is applied. | <pre>map(object({<br/> value = string<br/> protected = optional(bool, false)<br/> masked = optional(bool, false)<br/> description = optional(string, "Managed by {{MANAGER_NAME}}.")<br/> }))</pre> | `{}` | no |
4141
| <a name="input_gitlab_variables_description"></a> [gitlab\_variables\_description](#input\_gitlab\_variables\_description) | The description for the GitLab variables created by this module. You can use `{{MANAGER_NAME}}` to include the name of the 'manager' defined in `gitlab_variables_description_manager_name`. | `string` | `"Managed by {{MANAGER_NAME}}."` | no |
4242
| <a name="input_gitlab_variables_description_manager_name"></a> [gitlab\_variables\_description\_manager\_name](#input\_gitlab\_variables\_description\_manager\_name) | The name of the manager to include in the GitLab variable description. | `string` | `"terraform-google-gcp-gitlab-wif module"` | no |
@@ -49,7 +49,7 @@ You can refer to the official [GitLab documentation](https://docs.gitlab.com/ci/
4949
| Name | Description |
5050
|------|-------------|
5151
| <a name="output_gitlab_variables"></a> [gitlab\_variables](#output\_gitlab\_variables) | The GitLab variables created by this module. |
52-
| <a name="output_principal_set"></a> [principal\_set](#output\_principal\_set) | The principal set string used for IAM bindings. |
52+
| <a name="output_principal_set"></a> [principal\_set](#output\_principal\_set) | The principal sets string used for IAM bindings. |
5353
| <a name="output_secret_created"></a> [secret\_created](#output\_secret\_created) | The names and IDs of the secrets created by this module. |
5454
| <a name="output_secret_ids"></a> [secret\_ids](#output\_secret\_ids) | Map of original secret names to their Secret Manager secret IDs |
5555
| <a name="output_secret_names"></a> [secret\_names](#output\_secret\_names) | Map of original secret names to their formatted names |
@@ -80,6 +80,7 @@ You can refer to the official [GitLab documentation](https://docs.gitlab.com/ci/
8080
| [google_service_account.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account) | resource |
8181
| [google_service_account_iam_member.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/service_account_iam_member) | resource |
8282
| [random_id.suffix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) | resource |
83+
| [gitlab_group.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/data-sources/group) | data source |
8384
| [google_project.project](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/project) | data source |
8485
| [google_service_account.this](https://registry.terraform.io/providers/hashicorp/google/latest/docs/data-sources/service_account) | data source |
8586

examples/main.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module "example" {
77

88
name = var.name
99
gcp_project_id = var.gcp_project_id
10-
gitlab_project_id = var.gitlab_project_id
10+
gitlab_project_ids = var.gitlab_project_ids
1111
gitlab_instance_url = var.gitlab_instance_url
1212
secret_gcp_project_id = var.secret_gcp_project_id
1313
secret_names = var.secret_names

examples/test.tfvars

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "MyAwesomeWif"
22
gcp_project_id = "my-gcp-project-id"
3-
gitlab_project_id = 42
3+
gitlab_project_ids = [42]
44
gitlab_instance_url = "https://my.gitlab.com"
55
secret_gcp_project_id = "my-gcp-secret-project-id"
66
secret_names = [

examples/variables.tf

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@ variable "gcp_project_id" {
1010
}
1111

1212
# GitLab variables
13-
variable "gitlab_project_id" {
14-
description = "The GitLab project ID to allow access from. Use this for project-level access."
15-
type = string
13+
variable "gitlab_project_ids" {
14+
description = "The GitLab project IDs to allow access from. Use this for project-level access."
15+
type = list(number)
16+
default = []
1617
}
1718

1819
variable "gitlab_instance_url" {

gitlab.tf

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,16 @@
1+
# Fetch GitLab groups path, if needed
2+
data "gitlab_group" "this" {
3+
for_each = length(var.gitlab_group_ids) > 0 ? toset([for id in var.gitlab_group_ids : tostring(id)]) : []
4+
5+
group_id = tonumber(each.value)
6+
}
7+
18
# GitLab group and project variables for Workload Identity Federation
29
# Group variables if `var.gitlab_group_id` is provided
310
resource "gitlab_group_variable" "gcp_wif_project_id" {
4-
count = local.is_gitlab_group_level ? 1 : 0
11+
for_each = length(var.gitlab_group_ids) > 0 ? toset([for id in var.gitlab_group_ids : tostring(id)]) : []
512

6-
group = var.gitlab_group_id
13+
group = tonumber(each.value)
714
key = var.gitlab_gcp_wif_project_id_variable_name
815
value = data.google_project.project.number
916
description = local.gitlab_variables_description
@@ -12,9 +19,9 @@ resource "gitlab_group_variable" "gcp_wif_project_id" {
1219
}
1320

1421
resource "gitlab_group_variable" "gcp_wif_pool" {
15-
count = local.is_gitlab_group_level ? 1 : 0
22+
for_each = length(var.gitlab_group_ids) > 0 ? toset([for id in var.gitlab_group_ids : tostring(id)]) : []
1623

17-
group = var.gitlab_group_id
24+
group = tonumber(each.value)
1825
key = var.gitlab_gcp_wif_pool_variable_name
1926
value = google_iam_workload_identity_pool.this.workload_identity_pool_id
2027
description = local.gitlab_variables_description
@@ -23,9 +30,9 @@ resource "gitlab_group_variable" "gcp_wif_pool" {
2330
}
2431

2532
resource "gitlab_group_variable" "gcp_wif_provider" {
26-
count = local.is_gitlab_group_level ? 1 : 0
33+
for_each = length(var.gitlab_group_ids) > 0 ? toset([for id in var.gitlab_group_ids : tostring(id)]) : []
2734

28-
group = var.gitlab_group_id
35+
group = tonumber(each.value)
2936
key = var.gitlab_gcp_wif_provider_variable_name
3037
value = google_iam_workload_identity_pool_provider.this.workload_identity_pool_provider_id
3138
description = local.gitlab_variables_description
@@ -34,9 +41,9 @@ resource "gitlab_group_variable" "gcp_wif_provider" {
3441
}
3542

3643
resource "gitlab_group_variable" "gcp_wif_service_account_email" {
37-
count = local.is_gitlab_group_level ? 1 : 0
44+
for_each = length(var.gitlab_group_ids) > 0 ? toset([for id in var.gitlab_group_ids : tostring(id)]) : []
3845

39-
group = var.gitlab_group_id
46+
group = tonumber(each.value)
4047
key = var.gitlab_gcp_wif_service_account_email_variable_name
4148
value = local.sa_email
4249
description = local.gitlab_variables_description
@@ -45,10 +52,12 @@ resource "gitlab_group_variable" "gcp_wif_service_account_email" {
4552
}
4653

4754
resource "gitlab_group_variable" "gitlab_variables_additional" {
48-
for_each = local.is_gitlab_group_level ? local.gitlab_variables_additional_final : {}
55+
for_each = length(var.gitlab_group_ids) > 0 ? {
56+
for key, val in local.gitlab_variables_additional_final : key => val if val.gitlab_resource_type == local.group_resource_suffix
57+
} : {}
4958

50-
group = var.gitlab_group_id
51-
key = each.key
59+
group = each.value.gitlab_resource_id
60+
key = each.value.key
5261
value = each.value.value
5362
description = each.value.description
5463
protected = each.value.protected
@@ -57,9 +66,9 @@ resource "gitlab_group_variable" "gitlab_variables_additional" {
5766

5867
# Project variables if `var.gitlab_project_id` is provided
5968
resource "gitlab_project_variable" "gcp_wif_project_id" {
60-
count = local.is_gitlab_project_level ? 1 : 0
69+
for_each = length(var.gitlab_project_ids) > 0 ? toset([for id in var.gitlab_project_ids : tostring(id)]) : []
6170

62-
project = var.gitlab_project_id
71+
project = tonumber(each.value)
6372
key = var.gitlab_gcp_wif_project_id_variable_name
6473
value = data.google_project.project.number
6574
description = local.gitlab_variables_description
@@ -68,9 +77,9 @@ resource "gitlab_project_variable" "gcp_wif_project_id" {
6877
}
6978

7079
resource "gitlab_project_variable" "gcp_wif_pool" {
71-
count = local.is_gitlab_project_level ? 1 : 0
80+
for_each = length(var.gitlab_project_ids) > 0 ? toset([for id in var.gitlab_project_ids : tostring(id)]) : []
7281

73-
project = var.gitlab_project_id
82+
project = tonumber(each.value)
7483
key = var.gitlab_gcp_wif_pool_variable_name
7584
value = google_iam_workload_identity_pool.this.workload_identity_pool_id
7685
description = local.gitlab_variables_description
@@ -79,9 +88,9 @@ resource "gitlab_project_variable" "gcp_wif_pool" {
7988
}
8089

8190
resource "gitlab_project_variable" "gcp_wif_provider" {
82-
count = local.is_gitlab_project_level ? 1 : 0
91+
for_each = length(var.gitlab_project_ids) > 0 ? toset([for id in var.gitlab_project_ids : tostring(id)]) : []
8392

84-
project = var.gitlab_project_id
93+
project = tonumber(each.value)
8594
key = var.gitlab_gcp_wif_provider_variable_name
8695
value = google_iam_workload_identity_pool_provider.this.workload_identity_pool_provider_id
8796
description = local.gitlab_variables_description
@@ -90,9 +99,9 @@ resource "gitlab_project_variable" "gcp_wif_provider" {
9099
}
91100

92101
resource "gitlab_project_variable" "gcp_wif_service_account_email" {
93-
count = local.is_gitlab_project_level ? 1 : 0
102+
for_each = length(var.gitlab_project_ids) > 0 ? toset([for id in var.gitlab_project_ids : tostring(id)]) : []
94103

95-
project = var.gitlab_project_id
104+
project = tonumber(each.value)
96105
key = var.gitlab_gcp_wif_service_account_email_variable_name
97106
value = local.sa_email
98107
description = local.gitlab_variables_description
@@ -101,10 +110,12 @@ resource "gitlab_project_variable" "gcp_wif_service_account_email" {
101110
}
102111

103112
resource "gitlab_project_variable" "gitlab_variables_additional" {
104-
for_each = local.is_gitlab_project_level ? local.gitlab_variables_additional_final : {}
113+
for_each = length(var.gitlab_project_ids) > 0 ? {
114+
for key, val in local.gitlab_variables_additional_final : key => val if val.gitlab_resource_type == local.project_resource_suffix
115+
} : {}
105116

106-
project = var.gitlab_project_id
107-
key = each.key
117+
project = each.value.gitlab_resource_id
118+
key = each.value.key
108119
value = each.value.value
109120
description = each.value.description
110121
protected = each.value.protected

locals.tf

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,24 @@
11
locals {
2-
resource_name_suffix = "${var.name}-${random_id.suffix.hex}"
3-
is_gitlab_group_level = var.gitlab_group_id > 0
4-
is_gitlab_project_level = var.gitlab_project_id > 0
5-
attribute_condition = local.is_gitlab_project_level ? "assertion.project_id==\"${var.gitlab_project_id}\"" : "assertion.namespace_id==\"${var.gitlab_group_id}\""
6-
principal_subject = local.is_gitlab_project_level ? "attribute.project_id/${var.gitlab_project_id}" : "attribute.namespace_id/${var.gitlab_group_id}"
7-
principal_set = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.this.name}/${local.principal_subject}"
2+
resource_name_suffix = "${var.name}-${random_id.suffix.hex}"
3+
4+
project_resource_suffix = "project"
5+
group_resource_suffix = "group"
6+
custom_id_group_valid_attribute_name = "custom_is_group_valid"
7+
8+
projects_attribute_condition = "(${join(" || ", [for id in var.gitlab_project_ids : "attribute.project_id==\"${id}\""])})"
9+
groups_attribute_condition = "(attribute.${local.custom_id_group_valid_attribute_name}==\"1\")"
10+
attribute_condition = join(" || ", concat(
11+
length(var.gitlab_project_ids) > 0 ? [local.projects_attribute_condition] : [],
12+
length(var.gitlab_group_ids) > 0 ? [local.groups_attribute_condition] : []
13+
))
14+
15+
principal_subjects = merge(
16+
{ for id in var.gitlab_project_ids : "${local.project_resource_suffix}-${id}" => "attribute.project_id/${id}" },
17+
{ (local.group_resource_suffix) = "attribute.${local.custom_id_group_valid_attribute_name}/1" },
18+
)
19+
principal_sets = {
20+
for key, subject in local.principal_subjects : key => "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.this.name}/${subject}"
21+
}
822

923
# Ensure the account_id is always 28 characters or less
1024
sa_name_prefix = "gwif-sa-"
@@ -43,13 +57,37 @@ locals {
4357
secret_gcp_project_id = var.secret_gcp_project_id != null ? var.secret_gcp_project_id : var.gcp_project_id
4458

4559
gitlab_variables_description = replace(var.gitlab_variables_description, "{{MANAGER_NAME}}", var.gitlab_variables_description_manager_name)
60+
61+
gitlab_variables_additional_group = flatten([
62+
for gitlab_resource_id in var.gitlab_group_ids : [
63+
for key, value in var.gitlab_variables_additional : [
64+
merge(
65+
value,
66+
{
67+
gitlab_resource_type = local.group_resource_suffix,
68+
gitlab_resource_id = gitlab_resource_id,
69+
key = key,
70+
description = replace(value.description, "{{MANAGER_NAME}}", var.gitlab_variables_description_manager_name)
71+
}
72+
)
73+
]
74+
]])
75+
gitlab_variables_additional_project = flatten([
76+
for gitlab_resource_id in var.gitlab_project_ids : [
77+
for key, value in var.gitlab_variables_additional : [
78+
merge(
79+
value,
80+
{
81+
gitlab_resource_type = local.project_resource_suffix,
82+
gitlab_resource_id = gitlab_resource_id,
83+
key = key,
84+
description = replace(value.description, "{{MANAGER_NAME}}", var.gitlab_variables_description_manager_name)
85+
}
86+
)
87+
]
88+
]])
4689
gitlab_variables_additional_final = {
47-
for key, value in var.gitlab_variables_additional :
48-
key => merge(
49-
value,
50-
{
51-
description = replace(value.description, "{{MANAGER_NAME}}", var.gitlab_variables_description_manager_name)
52-
}
53-
)
90+
for item in concat(local.gitlab_variables_additional_group, local.gitlab_variables_additional_project) :
91+
"${item.key}--${item.gitlab_resource_type}--${item.gitlab_resource_id}" => item
5492
}
5593
}

main.tf

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,8 @@ resource "google_iam_workload_identity_pool" "this" {
1616
lifecycle {
1717
# Prevent creation of resources if the module is not configured correctly
1818
precondition {
19-
condition = var.gitlab_group_id > 0 || var.gitlab_project_id > 0
20-
error_message = "Either gitlab_group_id or gitlab_project_id must be provided."
21-
}
22-
23-
precondition {
24-
condition = (var.gitlab_group_id > 0) != (var.gitlab_project_id > 0)
25-
error_message = "Only one of gitlab_group_id or gitlab_project_id should be provided, not both."
19+
condition = length(var.gitlab_group_ids) > 0 || length(var.gitlab_project_ids) > 0
20+
error_message = "Either gitlab_group_ids or gitlab_project_ids must be provided."
2621
}
2722
}
2823
}
@@ -33,8 +28,15 @@ resource "google_iam_workload_identity_pool_provider" "this" {
3328
workload_identity_pool_provider_id = "provider-${substr(local.resource_name_suffix, 0, 32 - length("provider-"))}"
3429
display_name = local.provider_display_name
3530
description = "OIDC identity pool provider for ${var.name}"
36-
attribute_mapping = var.gcp_workload_identity_pool_provider_attribute_mapping
3731
attribute_condition = local.attribute_condition
32+
attribute_mapping = merge(
33+
var.gcp_workload_identity_pool_provider_attribute_mapping,
34+
length(var.gitlab_group_ids) > 0 ? {
35+
"attribute.${local.custom_id_group_valid_attribute_name}" = "${
36+
join(" || ", formatlist("assertion.namespace_path.startsWith(\"%s\")", [for item in data.gitlab_group.this : item.full_path]))
37+
} ? \"1\" : \"0\"",
38+
} : {}
39+
)
3840

3941
oidc {
4042
issuer_uri = var.gitlab_instance_url
@@ -58,7 +60,9 @@ data "google_service_account" "this" {
5860
}
5961

6062
resource "google_service_account_iam_member" "this" {
63+
for_each = local.principal_sets
64+
6165
service_account_id = local.sa_name
6266
role = "roles/iam.workloadIdentityUser"
63-
member = local.principal_set
67+
member = each.value
6468
}

0 commit comments

Comments
 (0)