diff --git a/CHANGELOG.md b/CHANGELOG.md index c628c94..30b9fa2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,42 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [1.0.0] - 2025-10-02 + +[Compare with previous version](https://github.com/sparkfabrik/terraform-gitlab-kubernetes-gitlab-agent/compare/0.13.0...1.0.0) + +### Added + +- New variable `operate_at_root_group_level` to simplify configuration and replace the combination of `gitlab_agent_grant_access_to_entire_root_namespace` and `gitlab_agent_create_variables_in_root_namespace`. +- New variable `groups_enabled` to specify groups where the GitLab Agent should be enabled (when not operating at root group level). +- New variable `projects_enabled` to specify projects where the GitLab Agent should be enabled (when not operating at root group level). +- Auto-detection of parent group when `operate_at_root_group_level = false` and no groups/projects are specified. +- Support for creating CI/CD variables in multiple groups and projects simultaneously. +- Dynamic generation of agent configuration file based on enabled groups/projects using `yamlencode()`. +- New outputs: `gitlab_enabled_groups`, `gitlab_enabled_projects`, `gitlab_parent_group_auto_detected`. + +### Changed + +- Agent configuration file is now dynamically generated based on `operate_at_root_group_level` and enabled groups/projects. +- CI/CD variables can now be created in multiple targets (root group, specific groups, or specific projects) depending on configuration. +- Output `gitlab_root_namespace_id` now returns `null` when not operating at root group level. + +### Removed + +- **BREAKING CHANGE**: variable `gitlab_agent_grant_access_to_entire_root_namespace` - replaced by `operate_at_root_group_level`. +- **BREAKING CHANGE**: variable `gitlab_agent_create_variables_in_root_namespace` - behavior is now determined by `operate_at_root_group_level`. +- Backward compatibility logic for deprecated variables. + +### Migration Guide + +If you were using the removed variables, migrate as follows: + +- `gitlab_agent_grant_user_access_to_root_namespace = true` -> `operate_at_root_group_level = true` + `gitlab_agent_grant_user_access_to_root_namespace = true` +- `gitlab_agent_grant_access_to_entire_root_namespace = true` + `gitlab_agent_create_variables_in_root_namespace = true` → `operate_at_root_group_level = true` + `gitlab_agent_grant_user_access_to_root_namespace = true` +- `gitlab_agent_grant_access_to_entire_root_namespace = false` -> `operate_at_root_group_level = false` + configure `groups_enabled` and/or `projects_enabled` + +**Note**: user access is now only available when `operate_at_root_group_level = true`. If you need user access to specific groups/projects, this is not currently supported by Gitlab. + ## [0.12.0] - 2025-05-19 [Compare with previous version](https://github.com/sparkfabrik/terraform-gitlab-kubernetes-gitlab-agent/compare/0.11.0...0.12.0) diff --git a/README.md b/README.md index cf70ce4..3fa9d9a 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,60 @@ This module creates all the necessary resources to deploy a Gitlab Agent on a Ku It uses the Gitlab provider to register the agent on the Gitlab server. The generated registration token is use to create an Helm release of the Gitlab Agent in the cluster. -If required (`gitlab_agent_grant_access_to_entire_root_namespace` configured to `true`), it also creates the necessary configuration files in the given Gitlab project, granting access to all the projects in the root namespace and subgroups. +The module supports multiple configuration modes: + +- **Root Group Level** (default): The agent has access to the entire root namespace and CI/CD variables are created in the root group. +- **Auto-detect Parent**: when not operating at root level and no specific groups/projects are provided, the module automatically detects the parent group of the agent project. +- **Specific Groups/Projects**: enable the agent only for specific groups or projects, with variables created in those locations. **ATTENTION**: you have to manually create the project that will host the Gitlab Agent configuration in Gitlab before running this module. From version `0.7.0`, if you set `gitlab_project_name` the module will create Gitlab project automatically. This new behavior requires the provider to have the proper permissions to create the project in the namespace. +## Configuration Examples + +### Example 1: Root Group (Default) +```hcl +module "gitlab_agent" { + source = "github.com/sparkfabrik/terraform-gitlab-kubernetes-gitlab-agent" + + gitlab_project_path_with_namespace = "my-org/agents-project" + gitlab_agent_name = "production-agent" + namespace = "gitlab-agent" +} +``` + +### Example 2: Auto-detect Parent Group +```hcl +module "gitlab_agent" { + source = "github.com/sparkfabrik/terraform-gitlab-kubernetes-gitlab-agent" + + gitlab_project_path_with_namespace = "my-org/team-a/subgroup/agents" + gitlab_agent_name = "team-agent" + namespace = "gitlab-agent" + + operate_at_root_group_level = false + # Parent group "my-org/team-a/subgroup" will be automatically detected +} +``` + +### Example 3: Specific Groups +```hcl +module "gitlab_agent" { + source = "github.com/sparkfabrik/terraform-gitlab-kubernetes-gitlab-agent" + + gitlab_project_path_with_namespace = "my-org/infrastructure/agents" + gitlab_agent_name = "shared-agent" + namespace = "gitlab-agent" + + operate_at_root_group_level = false + groups_enabled = [ + "my-org/team-a", + "my-org/team-b" + ] +} +``` + ## RBAC configuration for the Gitlab Agent service account This module uses the default configuration of the Gitlab Agent Helm chart. The default configuration grants to the Gitlab Agent service account the `cluster-admin` ClusterRole. If you want to change this configuration, you can use the `helm_additional_values` variable to pass additional values to the Helm chart. @@ -32,9 +80,9 @@ provider "gitlab" { | Name | Version | |------|---------| -| [gitlab](#provider\_gitlab) | >= 15.7 | -| [helm](#provider\_helm) | >= 2.0 | -| [kubernetes](#provider\_kubernetes) | >= 2.23 | +| [gitlab](#provider\_gitlab) | 18.4.1 | +| [helm](#provider\_helm) | 3.0.2 | +| [kubernetes](#provider\_kubernetes) | 2.38.0 | ## Requirements @@ -52,13 +100,11 @@ provider "gitlab" { | [agent\_replicas](#input\_agent\_replicas) | The number of replicas of the Gitlab Agent. | `number` | `1` | no | | [create\_default\_pod\_anti\_affinity](#input\_create\_default\_pod\_anti\_affinity) | Create default podAntiAffinity rules for the Gitlab Agent pods. | `bool` | `true` | no | | [create\_namespace](#input\_create\_namespace) | Create namespace for the helm release. If false, the namespace must be created before using this module. | `bool` | `true` | no | -| [gitlab\_agent\_append\_to\_config\_file](#input\_gitlab\_agent\_append\_to\_config\_file) | Append the Gitlab Agent configuration to the configuration file created for the entire root namespace. This variable is only used when `gitlab_agent_grant_access_to_entire_root_namespace` is true. | `string` | `""` | no | +| [gitlab\_agent\_append\_to\_config\_file](#input\_gitlab\_agent\_append\_to\_config\_file) | Append custom configuration to the Gitlab Agent configuration file. This content will be added at the end of the generated configuration. | `string` | `""` | no | | [gitlab\_agent\_branch\_name](#input\_gitlab\_agent\_branch\_name) | The branch name where the Gitlab Agent configuration will be stored. | `string` | `"main"` | no | | [gitlab\_agent\_commmit\_message](#input\_gitlab\_agent\_commmit\_message) | The commit message to use when committing the Gitlab Agent configuration file. You can use the placeholder `{{gitlab_agent_name}}` to reference the Gitlab Agent name. | `string` | `"[CI] Add agent config file for {{gitlab_agent_name}}"` | no | -| [gitlab\_agent\_create\_variables\_in\_root\_namespace](#input\_gitlab\_agent\_create\_variables\_in\_root\_namespace) | Create two Gitlab CI/CD variables in the root namespace useful to configure the Kubernetes context and use the Gitlab Agent. These variables are created in the root namespace of the project defined in `gitlab_project_path_with_namespace`, which is the project that hosts the Gitlab Agent configuration. | `bool` | `true` | no | -| [gitlab\_agent\_custom\_config\_file\_content](#input\_gitlab\_agent\_custom\_config\_file\_content) | The content of the Gitlab Agent configuration file. If not provided and `gitlab_agent_grant_access_to_entire_root_namespace` is true, the default configuration file will be used and the root namespace will be granted access to the Gitlab Agent. If you set this variable, it takes precedence over `gitlab_agent_grant_access_to_entire_root_namespace`. | `string` | `""` | no | +| [gitlab\_agent\_custom\_config\_file\_content](#input\_gitlab\_agent\_custom\_config\_file\_content) | The content of the Gitlab Agent configuration file. If not provided, the default configuration file will be generated based on `operate_at_root_group_level`, `groups_enabled`, and `projects_enabled`. If you set this variable, it takes precedence over the automatic configuration generation. | `string` | `""` | no | | [gitlab\_agent\_deploy\_enabled](#input\_gitlab\_agent\_deploy\_enabled) | Whether to deploy the GitLab Agent components. If false, only creates the GitLab Agent token, Kubernetes namespace and secret without deploying the agent itself. | `bool` | `true` | no | -| [gitlab\_agent\_grant\_access\_to\_entire\_root\_namespace](#input\_gitlab\_agent\_grant\_access\_to\_entire\_root\_namespace) | Grant access to the entire root namespace. If false, you can provide a custom configuration file content using the variable `gitlab_agent_custom_config_file_content`. Otherwise, you will have to manually manage the access to the Gitlab Agent committing the proper configuration to the Gitlab project. | `bool` | `true` | no | | [gitlab\_agent\_grant\_user\_access\_to\_root\_namespace](#input\_gitlab\_agent\_grant\_user\_access\_to\_root\_namespace) | Grant `user_access` to the root namespace. | `bool` | `false` | no | | [gitlab\_agent\_name](#input\_gitlab\_agent\_name) | The name of the Gitlab Agent. | `string` | n/a | yes | | [gitlab\_agent\_token\_description](#input\_gitlab\_agent\_token\_description) | The description of the Gitlab Agent token. You can use the placeholder `{{gitlab_agent_name}}` to reference the Gitlab Agent name. | `string` | `"Token for the Gitlab Agent {{gitlab_agent_name}}."` | no | @@ -67,13 +113,16 @@ provider "gitlab" { | [gitlab\_agent\_variable\_name\_agent\_project](#input\_gitlab\_agent\_variable\_name\_agent\_project) | The name of the Gitlab CI/CD variable that stores the Gitlab Agent project path. | `string` | `"GITLAB_AGENT_PROJECT"` | no | | [gitlab\_project\_name](#input\_gitlab\_project\_name) | The name of the Gitlab project that hosts the Gitlab Agent configuration. If not provided, the module will use the project defined in `gitlab_project_path_with_namespace`. | `string` | `""` | no | | [gitlab\_project\_path\_with\_namespace](#input\_gitlab\_project\_path\_with\_namespace) | The path with namespace of the Gitlab project that hosts the Gitlab Agent configuration. The project must be created in Gitlab before running this module. The configured Gitlab provider must have write access to the project. | `string` | n/a | yes | +| [groups\_enabled](#input\_groups\_enabled) | List of group paths where the GitLab Agent should be enabled. Only used when operate\_at\_root\_group\_level is false. If empty and projects\_enabled is also empty, the parent group of the agent project will be used automatically. | `list(string)` | `[]` | no | | [helm\_additional\_values](#input\_helm\_additional\_values) | Additional values to be passed to the Helm chart. | `list(string)` | `[]` | no | -| [helm\_chart\_version](#input\_helm\_chart\_version) | The version of the gitlab-agent Helm chart. You can see the available versions at https://gitlab.com/gitlab-org/charts/gitlab-agent/-/tags, or using the command `helm search repo gitlab/gitlab-agent -l` after adding the Gitlab Helm repository. | `string` | `"2.13.0"` | no | +| [helm\_chart\_version](#input\_helm\_chart\_version) | The version of the gitlab-agent Helm chart. You can see the available versions at https://gitlab.com/gitlab-org/charts/gitlab-agent/-/tags, or using the command `helm search repo gitlab/gitlab-agent -l` after adding the Gitlab Helm repository. | `string` | `"2.14.1"` | no | | [helm\_release\_name](#input\_helm\_release\_name) | The name of the Helm release. | `string` | `"gitlab-agent"` | no | | [k8s\_additional\_labels](#input\_k8s\_additional\_labels) | Additional labels to apply to the kubernetes resources. | `map(string)` | `{}` | no | | [k8s\_default\_labels](#input\_k8s\_default\_labels) | Labels to apply to the kubernetes resources. These are opinionated labels, you can add more labels using the variable `additional_k8s_labels`. If you want to remove a label, you can override it with an empty map(string). | `map(string)` |
{
"managed-by": "terraform",
"scope": "gitlab-agent"
}
| no | | [k8s\_gitlab\_agent\_token\_secret\_name](#input\_k8s\_gitlab\_agent\_token\_secret\_name) | The name of the Kubernetes secret that will store the Gitlab Agent token. You can use the placeholder `{{gitlab_agent_name}}` to reference the Gitlab Agent name. | `string` | `"{{gitlab_agent_name}}-token"` | no | | [namespace](#input\_namespace) | The namespace in which the Gitlab Agent resources will be created. | `string` | `"gitlab-agent"` | no | +| [operate\_at\_root\_group\_level](#input\_operate\_at\_root\_group\_level) | Operate at root group level. If true, grants access to entire root namespace and creates variables in root group. If false, behavior depends on groups\_enabled and projects\_enabled. This replaces gitlab\_agent\_grant\_access\_to\_entire\_root\_namespace and gitlab\_agent\_create\_variables\_in\_root\_namespace. | `bool` | `true` | no | +| [projects\_enabled](#input\_projects\_enabled) | List of project paths (with namespace) where the GitLab Agent should be enabled. Only used when operate\_at\_root\_group\_level is false. If empty and groups\_enabled is also empty, the parent group of the agent project will be used automatically. | `list(string)` | `[]` | no | ## Outputs @@ -82,7 +131,10 @@ provider "gitlab" { | [gitlab\_agent\_kubernetes\_context\_variables](#output\_gitlab\_agent\_kubernetes\_context\_variables) | The Gitlab Agent information to be used to configure the Kubernetes context. | | [gitlab\_agent\_token](#output\_gitlab\_agent\_token) | The token of the Gitlab Agent. | | [gitlab\_agents\_project\_id](#output\_gitlab\_agents\_project\_id) | The ID of the Gitlab project where the Gitlab Agents are installed. | -| [gitlab\_root\_namespace\_id](#output\_gitlab\_root\_namespace\_id) | The ID of the root namespace of the Gitlab Agents project. | +| [gitlab\_enabled\_groups](#output\_gitlab\_enabled\_groups) | List of groups where the GitLab Agent has been enabled with variables. | +| [gitlab\_enabled\_projects](#output\_gitlab\_enabled\_projects) | List of projects where the GitLab Agent has been enabled with variables. | +| [gitlab\_parent\_group\_auto\_detected](#output\_gitlab\_parent\_group\_auto\_detected) | Whether the parent group was automatically detected. | +| [gitlab\_root\_namespace\_id](#output\_gitlab\_root\_namespace\_id) | The ID of the root namespace of the Gitlab Agents project. Only available when operate\_at\_root\_group\_level is true. | | [k8s\_common\_labels](#output\_k8s\_common\_labels) | Common labels to apply to the kubernetes resources. | | [k8s\_gitlab\_agent\_token\_secret\_name](#output\_k8s\_gitlab\_agent\_token\_secret\_name) | The name of the Kubernetes secret that will store the Gitlab Agent token. | @@ -92,14 +144,19 @@ provider "gitlab" { |------|------| | [gitlab_cluster_agent.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/cluster_agent) | resource | | [gitlab_cluster_agent_token.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/cluster_agent_token) | resource | -| [gitlab_group_variable.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/group_variable) | resource | +| [gitlab_group_variable.enabled_groups](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/group_variable) | resource | +| [gitlab_group_variable.root_namespace](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/group_variable) | resource | | [gitlab_project.project](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/project) | resource | +| [gitlab_project_variable.enabled_projects](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/project_variable) | resource | | [gitlab_repository_file.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/resources/repository_file) | resource | | [helm_release.this](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | | [kubernetes_namespace_v1.this](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/namespace_v1) | resource | | [kubernetes_secret_v1.gitlab_agent_token_secret](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource | +| [gitlab_group.enabled_groups](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/data-sources/group) | data source | +| [gitlab_group.parent_group](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/data-sources/group) | data source | | [gitlab_group.root_namespace](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/data-sources/group) | data source | | [gitlab_metadata.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/data-sources/metadata) | data source | +| [gitlab_project.enabled_projects](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/data-sources/project) | data source | | [gitlab_project.this](https://registry.terraform.io/providers/gitlabhq/gitlab/latest/docs/data-sources/project) | data source | | [kubernetes_namespace_v1.this](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/data-sources/namespace_v1) | data source | diff --git a/files/config.yaml.tftpl b/files/config.yaml.tftpl index 3f60a79..fb75474 100644 --- a/files/config.yaml.tftpl +++ b/files/config.yaml.tftpl @@ -1,7 +1,7 @@ +%{~ if operate_at_root_group_level ~} ci_access: groups: - id: ${root_namespace} - %{~ if gitlab_agent_grant_user_access_to_root_namespace } user_access: access_as: @@ -9,6 +9,23 @@ user_access: groups: - id: ${root_namespace} %{~ endif ~} +%{~ else ~} +%{~ if length(groups_to_enable) > 0 || length(projects_to_enable) > 0 ~} +ci_access: + %{~ if length(groups_to_enable) > 0 ~} + groups: + %{~ for group in groups_to_enable ~} + - id: ${group} + %{~ endfor ~} + %{~ endif ~} + %{~ if length(projects_to_enable) > 0 ~} + projects: + %{~ for project in projects_to_enable ~} + - id: ${project} + %{~ endfor ~} + %{~ endif ~} +%{~ endif ~} +%{~ endif ~} %{~ if trimspace(gitlab_agent_append_to_config_file) != "" } ${gitlab_agent_append_to_config_file} diff --git a/main.tf b/main.tf index b0566a2..175f0c9 100644 --- a/main.tf +++ b/main.tf @@ -19,8 +19,32 @@ locals { gitlab_agent_commmit_message_computed = replace(var.gitlab_agent_commmit_message, "{{gitlab_agent_name}}", var.gitlab_agent_name) k8s_gitlab_agent_token_secret_name_computed = replace(var.k8s_gitlab_agent_token_secret_name, "{{gitlab_agent_name}}", var.gitlab_agent_name) + # Determine the parent group of the project + project_path_parts = split("/", var.gitlab_project_path_with_namespace) + parent_group_path = length(local.project_path_parts) > 1 ? join("/", slice(local.project_path_parts, 0, length(local.project_path_parts) - 1)) : "" + + # Determine if we are in auto-parent mode + auto_detect_parent = !var.operate_at_root_group_level && length(concat(var.groups_enabled, var.projects_enabled)) == 0 + + # Final list of groups to enable + groups_to_enable = var.operate_at_root_group_level ? [] : ( + local.auto_detect_parent ? [local.parent_group_path] : var.groups_enabled + ) + + # Final list of projects to enable + projects_to_enable = var.operate_at_root_group_level ? [] : ( + local.auto_detect_parent ? [] : var.projects_enabled + ) + # Gitlab Agent configuration file - final_configuration_file_content = var.gitlab_agent_custom_config_file_content != "" ? var.gitlab_agent_custom_config_file_content : (var.gitlab_agent_grant_access_to_entire_root_namespace ? templatefile("${path.module}/files/config.yaml.tftpl", { root_namespace = data.gitlab_group.root_namespace.path, gitlab_agent_append_to_config_file = var.gitlab_agent_append_to_config_file, gitlab_agent_grant_user_access_to_root_namespace = var.gitlab_agent_grant_user_access_to_root_namespace }) : "") + final_configuration_file_content = var.gitlab_agent_custom_config_file_content != "" ? var.gitlab_agent_custom_config_file_content : templatefile("${path.module}/files/config.yaml.tftpl", { + operate_at_root_group_level = var.operate_at_root_group_level + gitlab_agent_grant_user_access_to_root_namespace = var.gitlab_agent_grant_user_access_to_root_namespace + root_namespace = data.gitlab_group.root_namespace.path + groups_to_enable = local.groups_to_enable + projects_to_enable = local.projects_to_enable + gitlab_agent_append_to_config_file = var.gitlab_agent_append_to_config_file + }) # Gitlab Agent CI/CD variables gitlab_agent_kubernetes_context_variables = { @@ -41,10 +65,28 @@ data "gitlab_group" "root_namespace" { full_path = local.project_root_namespace } +# Data source for parent group when auto-detecting +data "gitlab_group" "parent_group" { + count = local.auto_detect_parent ? 1 : 0 + full_path = local.parent_group_path +} + +# Data source for the specified groups +data "gitlab_group" "enabled_groups" { + for_each = !var.operate_at_root_group_level && !local.auto_detect_parent ? toset(var.groups_enabled) : toset([]) + full_path = each.value +} + +# Data source for the specified projects +data "gitlab_project" "enabled_projects" { + for_each = !var.operate_at_root_group_level && !local.auto_detect_parent ? toset(var.projects_enabled) : toset([]) + path_with_namespace = each.value +} + resource "gitlab_project" "project" { count = local.use_existing_project == 0 ? 1 : 0 name = var.gitlab_project_name - namespace_id = data.gitlab_group.root_namespace.group_id + namespace_id = var.operate_at_root_group_level ? data.gitlab_group.root_namespace.group_id : data.gitlab_group.parent_group[0].group_id } resource "gitlab_cluster_agent" "this" { @@ -78,8 +120,9 @@ resource "gitlab_repository_file" "this" { ] } -resource "gitlab_group_variable" "this" { - for_each = var.gitlab_agent_create_variables_in_root_namespace ? local.gitlab_agent_kubernetes_context_variables : {} +# Variables for root group (when operate_at_root_group_level is true) +resource "gitlab_group_variable" "root_namespace" { + for_each = var.operate_at_root_group_level ? local.gitlab_agent_kubernetes_context_variables : {} group = data.gitlab_group.root_namespace.group_id key = each.key @@ -94,6 +137,42 @@ resource "gitlab_group_variable" "this" { ] } +# Variables for specific groups (when operate_at_root_group_level is false) +resource "gitlab_group_variable" "enabled_groups" { + for_each = !var.operate_at_root_group_level && length(local.groups_to_enable) > 0 ? { + for pair in setproduct(keys(local.gitlab_agent_kubernetes_context_variables), local.groups_to_enable) : + "${pair[1]}__${pair[0]}" => { + group_path = pair[1] + key = pair[0] + value = local.gitlab_agent_kubernetes_context_variables[pair[0]] + } + } : {} + + group = local.auto_detect_parent && each.value.group_path == local.parent_group_path ? data.gitlab_group.parent_group[0].group_id : data.gitlab_group.enabled_groups[each.value.group_path].group_id + key = each.value.key + value = each.value.value + protected = false + masked = false +} + +# Variables for specific projects (when operate_at_root_group_level is false) +resource "gitlab_project_variable" "enabled_projects" { + for_each = !var.operate_at_root_group_level && length(local.projects_to_enable) > 0 ? { + for pair in setproduct(keys(local.gitlab_agent_kubernetes_context_variables), local.projects_to_enable) : + "${pair[1]}__${pair[0]}" => { + project_path = pair[1] + key = pair[0] + value = local.gitlab_agent_kubernetes_context_variables[pair[0]] + } + } : {} + + project = data.gitlab_project.enabled_projects[each.value.project_path].id + key = each.value.key + value = each.value.value + protected = false + masked = false +} + # Kubernetes resources resource "kubernetes_namespace_v1" "this" { count = var.create_namespace ? 1 : 0 diff --git a/outputs.tf b/outputs.tf index e6166f2..6549bce 100644 --- a/outputs.tf +++ b/outputs.tf @@ -25,6 +25,22 @@ output "gitlab_agents_project_id" { } output "gitlab_root_namespace_id" { - description = "The ID of the root namespace of the Gitlab Agents project." - value = data.gitlab_group.root_namespace.group_id + description = "The ID of the root namespace of the Gitlab Agents project. Only available when operate_at_root_group_level is true." + value = var.operate_at_root_group_level ? data.gitlab_group.root_namespace.group_id : null } + +output "gitlab_enabled_groups" { + description = "List of groups where the GitLab Agent has been enabled with variables." + value = local.groups_to_enable +} + +output "gitlab_enabled_projects" { + description = "List of projects where the GitLab Agent has been enabled with variables." + value = local.projects_to_enable +} + +output "gitlab_parent_group_auto_detected" { + description = "Whether the parent group was automatically detected." + value = local.auto_detect_parent +} + diff --git a/spec.md b/spec.md new file mode 100644 index 0000000..54fa41c --- /dev/null +++ b/spec.md @@ -0,0 +1,343 @@ +# Specifica Modifiche Modulo Terraform GitLab Kubernetes Agent + +## Obiettivo +Modificare il modulo per gestire il comportamento del GitLab agent in modo diverso a seconda che si operi a livello di root group o meno, semplificando la configurazione con un toggle unico e introducendo comportamenti automatici intelligenti. + +## Requisiti Funzionali + +### 1. Toggle Unico per Root Group +- **Rimuovere** le variabili separate: + - `gitlab_agent_grant_access_to_entire_root_namespace` + - `gitlab_agent_create_variables_in_root_namespace` +- **Introdurre** una nuova variabile booleana unica (es. `operate_at_root_group_level` o `is_root_group_scope`) + +### 2. Comportamento se in Root Group +Quando `operate_at_root_group_level == true`: +- File di configurazione automaticamente gestito +- Variabili CI/CD create sul root group +- Comportamento identico all'attuale implementazione + +### 3. Comportamento se NON in Root Group +Quando `operate_at_root_group_level == false`: + +#### 3.1 Nessun gruppo o progetto specificato +Se `length(concat(groups_enabled, projects_enabled)) == 0`: +- Recuperare automaticamente il parent DIRETTO del progetto specificato in `gitlab_project_path_with_namespace` +- Trattare questo parent come l'UNICO elemento di `groups_enabled` +- **Nota**: Il parent potrebbe essere un root group - verificare e gestire questo caso + +#### 3.2 Gruppi o progetti specificati +Se sono specificati gruppi o progetti: +- Creare file di configurazione solo per quei gruppi/progetti specifici +- Creare variabili CI/CD in quei gruppi/progetti specifici (non nel root) + +--- + +## Piano di Implementazione + +### 1. Modifiche alle Variabili (variables.tf) + +#### Variabili da Rimuovere +- `gitlab_agent_grant_access_to_entire_root_namespace` +- `gitlab_agent_create_variables_in_root_namespace` + +#### Variabili da Aggiungere +```terraform +variable "operate_at_root_group_level" { + description = "Operate at root group level. If true, grants access to entire root namespace and creates variables in root group. If false, behavior depends on groups_enabled and projects_enabled." + type = bool + default = true +} + +variable "groups_enabled" { + description = "List of group IDs or paths where the GitLab Agent should be enabled. Only used when operate_at_root_group_level is false. If empty and projects_enabled is also empty, the parent group of the agent project will be used automatically." + type = list(string) + default = [] +} + +variable "projects_enabled" { + description = "List of project IDs or paths where the GitLab Agent should be enabled. Only used when operate_at_root_group_level is false. If empty and groups_enabled is also empty, the parent group of the agent project will be used automatically." + type = list(string) + default = [] +} +``` + +#### Validazioni da Aggiungere +- Verificare coerenza tra `operate_at_root_group_level` e uso di `gitlab_agent_custom_config_file_content` +- Validare che gruppi/progetti specificati esistano (dove possibile) + +### 2. Modifiche ai Locals (main.tf) + +#### Nuovi Locals da Aggiungere + +```terraform +# Determina il parent group del progetto +parent_group_path = join("/", slice(split("/", var.gitlab_project_path_with_namespace), 0, length(split("/", var.gitlab_project_path_with_namespace)) - 1)) + +# Determina se siamo in modalità auto-parent +auto_detect_parent = !var.operate_at_root_group_level && length(concat(var.groups_enabled, var.projects_enabled)) == 0 + +# Lista finale di gruppi da abilitare +groups_to_enable = var.operate_at_root_group_level ? [local.project_root_namespace] : ( + local.auto_detect_parent ? [local.parent_group_path] : var.groups_enabled +) + +# Lista finale di progetti da abilitare +projects_to_enable = var.operate_at_root_group_level ? [] : ( + local.auto_detect_parent ? [] : var.projects_enabled +) +``` + +#### Local da Modificare + +```terraform +# final_configuration_file_content +# Deve essere modificato per gestire: +# - Root group: comportamento attuale (template con root_namespace) +# - Non-root con gruppi/progetti: generare config dinamico con yamlencode() o nuovo template +# - Considerare se mantenerlo vuoto quando custom_config è specificato +``` + +### 3. Data Source Aggiuntivi + +#### Per il Parent Group (quando auto-detect) +```terraform +data "gitlab_group" "parent_group" { + count = local.auto_detect_parent ? 1 : 0 + full_path = local.parent_group_path +} +``` + +#### Per i Gruppi Specificati +```terraform +data "gitlab_group" "enabled_groups" { + for_each = !var.operate_at_root_group_level ? toset(var.groups_enabled) : toset([]) + full_path = each.value +} +``` + +#### Per i Progetti Specificati +```terraform +data "gitlab_project" "enabled_projects" { + for_each = !var.operate_at_root_group_level ? toset(var.projects_enabled) : toset([]) + path_with_namespace = each.value +} +``` + +### 4. Modifiche alle Risorse (main.tf) + +#### Risorsa gitlab_repository_file.this +- Mantenere la logica attuale per root group +- Per non-root-group: generare il file solo se ci sono gruppi/progetti da abilitare +- Modificare il contenuto in base ai gruppi/progetti target + +#### Risorsa gitlab_group_variable.this +**Sostituire con logica condizionale:** + +```terraform +# Variabili per root group (comportamento attuale) +resource "gitlab_group_variable" "root_namespace" { + for_each = var.operate_at_root_group_level ? local.gitlab_agent_kubernetes_context_variables : {} + # ... resto della configurazione +} + +# Variabili per gruppi specifici (non-root-group) +resource "gitlab_group_variable" "enabled_groups" { + for_each = !var.operate_at_root_group_level && length(local.groups_to_enable) > 0 ? { + for pair in setproduct(keys(local.gitlab_agent_kubernetes_context_variables), local.groups_to_enable) : + "${pair[1]}_${pair[0]}" => { + group = pair[1] + key = pair[0] + value = local.gitlab_agent_kubernetes_context_variables[pair[0]] + } + } : {} + # ... resto della configurazione +} + +# Variabili per progetti specifici (non-root-group) +resource "gitlab_project_variable" "enabled_projects" { + for_each = !var.operate_at_root_group_level && length(local.projects_to_enable) > 0 ? { + for pair in setproduct(keys(local.gitlab_agent_kubernetes_context_variables), local.projects_to_enable) : + "${pair[1]}_${pair[0]}" => { + project = pair[1] + key = pair[0] + value = local.gitlab_agent_kubernetes_context_variables[pair[0]] + } + } : {} + # ... resto della configurazione +} +``` + +### 5. Template o Generazione Dinamica Config + +#### Opzione A: Nuovo Template +Creare `files/config-custom.yaml.tftpl` per gestire gruppi/progetti specifici: +```yaml +ci_access: +%{~ if length(groups) > 0 } + groups: +%{~ for group in groups } + - id: ${group} +%{~ endfor } +%{~ endif } +%{~ if length(projects) > 0 } + projects: +%{~ for project in projects } + - id: ${project} +%{~ endfor } +%{~ endif } +``` + +#### Opzione B: Generazione Dinamica +Usare `yamlencode()` per generare dinamicamente il contenuto: +```terraform +local.final_configuration_file_content = var.operate_at_root_group_level ? + templatefile("${path.module}/files/config.yaml.tftpl", {...}) : + yamlencode({ + ci_access = { + groups = [for g in local.groups_to_enable : { id = g }] + projects = [for p in local.projects_to_enable : { id = p }] + } + }) +``` + +### 6. Modifiche agli Outputs (outputs.tf) + +#### Output da Modificare +```terraform +output "gitlab_root_namespace_id" { + description = "The ID of the root namespace of the Gitlab Agents project. Only available when operate_at_root_group_level is true." + value = var.operate_at_root_group_level ? data.gitlab_group.root_namespace.group_id : null +} +``` + +#### Output da Aggiungere +```terraform +output "gitlab_enabled_groups" { + description = "List of groups where the GitLab Agent has been enabled with variables." + value = local.groups_to_enable +} + +output "gitlab_enabled_projects" { + description = "List of projects where the GitLab Agent has been enabled with variables." + value = local.projects_to_enable +} + +output "gitlab_parent_group_auto_detected" { + description = "Whether the parent group was automatically detected." + value = local.auto_detect_parent +} +``` + +--- + +## Punti Critici e Considerazioni + +### 1. Parsing del Parent Path +- **Attenzione**: Il parsing del path deve gestire correttamente: + - Progetti nel root group: `root-group/project` → parent = `root-group` + - Progetti in sottogruppi: `root/subgroup1/subgroup2/project` → parent = `root/subgroup1/subgroup2` +- **Validazione**: Verificare che il parent esista prima di procedere + +### 2. Parent che È un Root Group +- Se il parent risulta essere un root group, il comportamento dovrebbe essere coerente +- Valutare se forzare `operate_at_root_group_level = true` in questo caso o gestirlo normalmente + +### 3. Backward Compatibility +**Opzioni:** +- **Breaking change**: Rimuovere completamente le vecchie variabili (richiede major version bump) +- **Deprecazione**: Mantenere le vecchie variabili con warning, mappandole alle nuove +- **Migrazione automatica**: Creare locals che traducono vecchia configurazione → nuova + +**Raccomandazione**: Considerare un major version bump con breaking change per semplificare il codice. + +### 4. Generazione Config.yaml +**Preferenza**: Usare `yamlencode()` per maggiore flessibilità e manutenibilità rispetto ai template, a meno che non si voglia mantenere il supporto per `gitlab_agent_append_to_config_file`. + +### 5. Validazione Gruppi/Progetti +- Usare data source per validare l'esistenza di gruppi/progetti specificati +- Gestire errori in modo chiaro se path non esistono + +### 6. User Access +- Attualmente `gitlab_agent_grant_user_access_to_root_namespace` si applica solo al root namespace +- Valutare se estendere questa funzionalità anche a gruppi/progetti specifici + +--- + +## Ordine di Implementazione Consigliato + +1. **Aggiungere nuove variabili** (`operate_at_root_group_level`, `groups_enabled`, `projects_enabled`) +2. **Aggiungere locals** per logica condizionale e parsing parent +3. **Aggiungere data sources** per gruppi/progetti +4. **Modificare generazione config file** con logica condizionale +5. **Sostituire risorse variabili** con nuova logica multi-target +6. **Aggiornare outputs** +7. **Aggiungere validazioni** +8. **Testing completo** di tutti gli scenari +9. **Deprecare vecchie variabili** (se si sceglie quel percorso) +10. **Aggiornare documentazione** (README.md) + +--- + +## Scenari di Test + +### Scenario 1: Root Group (comportamento attuale) +```terraform +operate_at_root_group_level = true +# groups_enabled e projects_enabled ignorati +``` +**Aspettativa**: File su root namespace, variabili su root group + +### Scenario 2: Auto-detect Parent +```terraform +operate_at_root_group_level = false +groups_enabled = [] +projects_enabled = [] +gitlab_project_path_with_namespace = "root-group/subgroup/project" +``` +**Aspettativa**: Parent = "root-group/subgroup", file e variabili su quel gruppo + +### Scenario 3: Gruppi Specifici +```terraform +operate_at_root_group_level = false +groups_enabled = ["group1", "group2/subgroup"] +projects_enabled = [] +``` +**Aspettativa**: File con access a group1 e group2/subgroup, variabili in entrambi + +### Scenario 4: Progetti Specifici +```terraform +operate_at_root_group_level = false +groups_enabled = [] +projects_enabled = ["org/project1", "org/project2"] +``` +**Aspettativa**: File con access a project1 e project2, variabili in entrambi + +### Scenario 5: Mix Gruppi e Progetti +```terraform +operate_at_root_group_level = false +groups_enabled = ["group1"] +projects_enabled = ["org/project1"] +``` +**Aspettativa**: File con access a group1 e project1, variabili in entrambi + +### Scenario 6: Parent È Root Group +```terraform +operate_at_root_group_level = false +groups_enabled = [] +projects_enabled = [] +gitlab_project_path_with_namespace = "root-group/project" +``` +**Aspettativa**: Parent = "root-group" (che è root), comportamento da definire + +--- + +## Note Finali + +Questa specifica fornisce una roadmap completa per l'implementazione. Prima di iniziare il coding: + +1. Decidere la strategia di backward compatibility +2. Scegliere tra template vs `yamlencode()` per config generation +3. Definire comportamento esatto quando parent è root group +4. Preparare esempi e test cases +5. Pianificare versioning (major bump vs minor/patch) diff --git a/variables.tf b/variables.tf index 52dd01e..5d57284 100644 --- a/variables.tf +++ b/variables.tf @@ -3,6 +3,7 @@ variable "gitlab_project_name" { type = string default = "" } + variable "gitlab_agent_deploy_enabled" { description = "Whether to deploy the GitLab Agent components. If false, only creates the GitLab Agent token, Kubernetes namespace and secret without deploying the agent itself." type = bool @@ -31,8 +32,8 @@ variable "gitlab_agent_token_description" { default = "Token for the Gitlab Agent {{gitlab_agent_name}}." } -variable "gitlab_agent_grant_access_to_entire_root_namespace" { - description = "Grant access to the entire root namespace. If false, you can provide a custom configuration file content using the variable `gitlab_agent_custom_config_file_content`. Otherwise, you will have to manually manage the access to the Gitlab Agent committing the proper configuration to the Gitlab project." +variable "operate_at_root_group_level" { + description = "Operate at root group level. If true, grants access to entire root namespace and creates variables in root group. If false, behavior depends on groups_enabled and projects_enabled. This replaces gitlab_agent_grant_access_to_entire_root_namespace and gitlab_agent_create_variables_in_root_namespace." type = bool default = true } @@ -43,15 +44,27 @@ variable "gitlab_agent_grant_user_access_to_root_namespace" { default = false } +variable "groups_enabled" { + description = "List of group paths where the GitLab Agent should be enabled. Only used when operate_at_root_group_level is false. If empty and projects_enabled is also empty, the parent group of the agent project will be used automatically." + type = list(string) + default = [] +} + +variable "projects_enabled" { + description = "List of project paths (with namespace) where the GitLab Agent should be enabled. Only used when operate_at_root_group_level is false. If empty and groups_enabled is also empty, the parent group of the agent project will be used automatically." + type = list(string) + default = [] +} + variable "gitlab_agent_append_to_config_file" { - description = "Append the Gitlab Agent configuration to the configuration file created for the entire root namespace. This variable is only used when `gitlab_agent_grant_access_to_entire_root_namespace` is true." + description = "Append custom configuration to the Gitlab Agent configuration file. This content will be added at the end of the generated configuration." type = string default = "" } variable "gitlab_agent_custom_config_file_content" { - description = "The content of the Gitlab Agent configuration file. If not provided and `gitlab_agent_grant_access_to_entire_root_namespace` is true, the default configuration file will be used and the root namespace will be granted access to the Gitlab Agent. If you set this variable, it takes precedence over `gitlab_agent_grant_access_to_entire_root_namespace`." + description = "The content of the Gitlab Agent configuration file. If not provided, the default configuration file will be generated based on `operate_at_root_group_level`, `groups_enabled`, and `projects_enabled`. If you set this variable, it takes precedence over the automatic configuration generation." type = string default = "" } @@ -68,12 +81,6 @@ variable "gitlab_agent_branch_name" { default = "main" } -variable "gitlab_agent_create_variables_in_root_namespace" { - description = "Create two Gitlab CI/CD variables in the root namespace useful to configure the Kubernetes context and use the Gitlab Agent. These variables are created in the root namespace of the project defined in `gitlab_project_path_with_namespace`, which is the project that hosts the Gitlab Agent configuration." - type = bool - default = true -} - variable "gitlab_agent_variable_name_agent_id" { description = "The name of the Gitlab CI/CD variable that stores the Gitlab Agent ID." type = string