From a281039ae711fc2a9f4d981dfe9049dd16f8657e Mon Sep 17 00:00:00 2001 From: Aleksander Cynarski Date: Sat, 1 Nov 2025 23:32:29 +0100 Subject: [PATCH] chore: defaults --- .gitignore | 6 + README.md | 74 +++++++++- data.tf | 13 +- data/allowed_project_types.json | 52 +++++++ locals.tf | 76 ++++++++++- main.tf | 99 +++++++++++++- output.tf | 3 - provider.tf | 5 +- variable.tf | 232 +++++++++++++++++++++++++++++++- 9 files changed, 550 insertions(+), 10 deletions(-) create mode 100644 .gitignore create mode 100644 data/allowed_project_types.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..308b01f --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.terraform/ +*.tfstate +*.tfstate.backup +.terraform.lock.hcl +.vscode/ +.DS_Store diff --git a/README.md b/README.md index 93378dc..c4aeb70 100644 --- a/README.md +++ b/README.md @@ -1 +1,73 @@ -# terraform module \ No newline at end of file +## Requirements + +| Name | Version | +|------|---------| +| [gitlab](#requirement\_gitlab) | 18.0.0 | + +## Providers + +| Name | Version | +|------|---------| +| [gitlab](#provider\_gitlab) | 18.0.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [gitlab_branch_protection.protected_branches](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/branch_protection) | resource | +| [gitlab_project.project](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/project) | resource | +| [gitlab_project_custom_attribute.custom_attributes](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/project_custom_attribute) | resource | +| [gitlab_project_environment.environments](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/project_environment) | resource | +| [gitlab_project_job_token_scopes.ci_token_scope](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/project_job_token_scopes) | resource | +| [gitlab_project_push_rules.push_rule](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/project_push_rules) | resource | +| [gitlab_project_variable.scoped_variables](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/project_variable) | resource | +| [gitlab_project_variable.variable](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/project_variable) | resource | +| [gitlab_tag_protection.protected_tags](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/resources/tag_protection) | resource | +| [gitlab_group.parent](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/data-sources/group) | data source | +| [gitlab_group.token_scope_groups](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/data-sources/group) | data source | +| [gitlab_project.token_scope_projects](https://registry.terraform.io/providers/gitlabhq/gitlab/18.0.0/docs/data-sources/project) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [allow\_merge\_on\_skipped\_pipeline](#input\_allow\_merge\_on\_skipped\_pipeline) | Set to true if you want to treat skipped pipelines as if they finished with success. | `bool` | `true` | no | +| [allowed\_avatar\_types\_json](#input\_allowed\_avatar\_types\_json) | Path to allowed avatar types json | `string` | `""` | no | +| [allowed\_project\_types\_json](#input\_allowed\_project\_types\_json) | Path to allowed project types json | `string` | `""` | no | +| [archived](#input\_archived) | Archived project | `bool` | `false` | no | +| [attributes](#input\_attributes) | custom attributes to be set for the project | `map(string)` | `{}` | no | +| [auto\_cancel\_pending\_pipelines](#input\_auto\_cancel\_pending\_pipelines) | Auto-cancel pending pipelines. This isn’t a boolean, but enabled/disabled. | `string` | `"enabled"` | no | +| [avatar](#input\_avatar) | Type of the avatar for the group (default: from type) | `string` | `""` | no | +| [avatars\_dir](#input\_avatars\_dir) | Avatars directory png files | `string` | `""` | no | +| [build\_git\_strategy](#input\_build\_git\_strategy) | The Git strategy. Defaults to fetch. | `string` | `"clone"` | no | +| [build\_timeout](#input\_build\_timeout) | The maximum amount of time, in seconds, that a job can run. | `number` | `3600` | no | +| [ci\_config\_path](#input\_ci\_config\_path) | CI PATH | `string` | `null` | no | +| [ci\_variables](#input\_ci\_variables) | CI variables to be set for the group |
map(object({
value = string
description = optional(string)
protected = optional(bool)
masked = optional(bool)
environment_scope = optional(string)
}))
| `null` | no | +| [default\_branch](#input\_default\_branch) | Default branch | `string` | `"master"` | no | +| [description](#input\_description) | Repository Description | `string` | n/a | yes | +| [environments](#input\_environments) | Project environments | `map(string)` | `{}` | no | +| [forked\_from\_project\_id](#input\_forked\_from\_project\_id) | Forked from | `number` | `0` | no | +| [group\_runners\_enabled](#input\_group\_runners\_enabled) | Enable group runners for this project. | `bool` | `true` | no | +| [is\_enable\_conventional\_commits\_push\_rule](#input\_is\_enable\_conventional\_commits\_push\_rule) | Enable conventional commits push rule | `bool` | `false` | no | +| [is\_enabled\_checkmarx](#input\_is\_enabled\_checkmarx) | Enable checkmarx | `bool` | `true` | no | +| [is\_enabled\_sonarqube](#input\_is\_enabled\_sonarqube) | Enable sonarqube | `bool` | `true` | no | +| [name](#input\_name) | Repository Name | `string` | n/a | yes | +| [only\_allow\_merge\_if\_pipeline\_succeeds](#input\_only\_allow\_merge\_if\_pipeline\_succeeds) | Set to true if you want allow merges only if a pipeline succeeds. | `bool` | `false` | no | +| [parent\_group](#input\_parent\_group) | Parent Group | `string` | n/a | yes | +| [project\_type](#input\_project\_type) | Project type | `string` | `""` | no | +| [protected\_branches](#input\_protected\_branches) | Protected branches |
map(object({
push_access_level = string
merge_access_level = string
}))
|
{
"master": {
"merge_access_level": "developer",
"push_access_level": "maintainer"
}
}
| no | +| [protected\_tags](#input\_protected\_tags) | Protected tags |
map(object({
create_access_level = string
}))
|
{
"v*": {
"create_access_level": "maintainer"
}
}
| no | +| [scoped\_variables](#input\_scoped\_variables) | {
"VARIABLE\_NAME" = {
description = "opis"
protected = optional(bool, false)
masked = optional(bool, false)
values = {
"environment\_name" = "value"
"environment\_2" = "value2"
}
}
} |
map(object({
description = optional(string, "")
protected = optional(bool, false)
masked = optional(bool, false)
values = map(string)
}))
| `{}` | no | +| [shared\_runners\_enabled](#input\_shared\_runners\_enabled) | Enable shared runners for this project. | `bool` | `true` | no | +| [sib\_aplikacja](#input\_sib\_aplikacja) | [UPW] ID Aplikacja w zasobach SIB | `string` | `""` | no | +| [tags](#input\_tags) | Tags | `list(string)` | `[]` | no | +| [token\_scope\_groups](#input\_token\_scope\_groups) | CI\_JOB\_TOKEN group allowlist | `list(string)` | `[]` | no | +| [token\_scope\_repositories](#input\_token\_scope\_repositories) | CI\_JOB\_TOKEN repositories allowlist | `list(string)` | `[]` | no | + +## Outputs + +No outputs. diff --git a/data.tf b/data.tf index 0ad8665..8374367 100644 --- a/data.tf +++ b/data.tf @@ -1 +1,12 @@ -//Data +data "gitlab_group" "parent" { + full_path = var.parent_group +} + +data "gitlab_group" "token_scope_groups" { + for_each = toset(var.token_scope_groups) + full_path = each.key +} +data "gitlab_project" "token_scope_projects" { + for_each = toset(var.token_scope_repositories) + path_with_namespace = each.key +} \ No newline at end of file diff --git a/data/allowed_project_types.json b/data/allowed_project_types.json new file mode 100644 index 0000000..c7feff3 --- /dev/null +++ b/data/allowed_project_types.json @@ -0,0 +1,52 @@ +{ + "": { + "icon_type" : "", + "ci_config_path": ".gitlab-ci.yml", + "tags" : [], + "variables": {}, + "scoped_variables": {}, + "environments": {} + + }, + "python-poetry": { + "icon_type" : "python", + "ci_config_path": "application/python-poetry.yml@fabryka_ofert/fo.devops/gitlab/gitlab-ci", + "tags" : ["vagrant", "devops"], + "variables": {}, + "scoped_variables": {}, + "environments": {} + + }, + "container": { + "icon_type" : "container", + "ci_config_path": "containers/docker.yml", + "tags" : ["docker"], + "variables": {}, + "scoped_variables": {}, + "environments": {} + }, + "packer": { + "icon_type" : "packer", + "ci_config_path": "infrastructure/packer.yml", + "tags" : ["packer"], + "variables": {}, + "scoped_variables": {}, + "environments": {} + }, + "terraform": { + "icon_type" : "terraform", + "ci_config_path": "infrastructure/terraform.yml@fabryka_ofert/fo.devops/gitlab/gitlab-ci", + "tags" : ["terraform"], + "variables": {}, + "scoped_variables": {}, + "environments": {} + }, + "terraform-module": { + "icon_type" : "terraform", + "ci_config_path": "infrastructure/terraform-module.yml@fabryka_ofert/fo.devops/gitlab/gitlab-ci", + "tags" : ["terraform", "terraform-module"], + "variables": {}, + "scoped_variables": {}, + "environments": {} + } +} \ No newline at end of file diff --git a/locals.tf b/locals.tf index 69083a6..4b8363f 100644 --- a/locals.tf +++ b/locals.tf @@ -1,3 +1,77 @@ -//Locals locals { + avatars_dir = var.avatars_dir == "" ? "${path.root}/images" : var.avatars_dir + + allowed_avatar_types_json = var.allowed_avatar_types_json == "" ? "${path.root}/data/allowed_avatar_project_types.json" : var.allowed_avatar_types_json + allowed_avatar_types = jsondecode(file("${local.allowed_avatar_types_json}")) + + allowed_project_types_json = var.allowed_avatar_types_json == "" ? "${path.root}/data/allowed_project_types.json" : var.allowed_project_types_json + allowed_project_types = jsondecode(try(file("${local.allowed_project_types_json}"), null) == null ? file("${path.module}/data/allowed_project_types.json") : file(local.allowed_project_types_json)) + + # Define the allowed project types as a map + avatar_project = local.allowed_project_types[var.project_type].avatar == "" ? null : "${local.avatars_dir}/${local.allowed_project_types[var.project_type].avatar}.png" + avatar_path = var.avatar == "" ? local.avatar_project : "${local.avatars_dir}/${var.avatar}.png" + avatar = try(file("${local.avatar_path}"), null) == null ? "${local.avatar_path}" : null + + + token_scope_group_ids = [for g in values(data.gitlab_group.token_scope_groups) : g.id] + token_scope_project_ids = [for p in values(data.gitlab_project.token_scope_projects) : p.id] + + merged_scope_variables = merge( + local.allowed_project_types[var.project_type].scoped_variables, + var.scoped_variables + ) + + merged_environments = merge( + local.allowed_project_types[var.project_type].environments, + var.environments + ) + + merged_project_variables = merge( + local.allowed_project_types[var.project_type].variables, + { + PROJECT_TYPE = { + description = "Project Type" + value = var.project_type + protected = "false" + masked = "false" + }, + IS_ENABLED_CHECKMARX = { + description = "Enabled CheckMarx scan" + value = var.is_enabled_checkmarx == true ? "true" : "false" + protected = "false" + masked = "false" + }, + IS_ENABLED_SONARQUBE = { + description = "Enabled SonarQube scan" + value = var.is_enabled_sonarqube == true ? "true" : "false" + protected = "false" + masked = "false" + }, + SIB_APLIKACJA = { + description = "[UPW] ID Aplikacji zasobów SIB" + value = var.sib_aplikacja + protected = "false" + masked = "false" + } + }, + var.ci_variables + ) + + scoped_variable_map = { + for pair in flatten([ + for var_name, cfg in local.merged_scope_variables : [ + for env_scope, v in cfg.values : { + id = "${var_name}::${env_scope}" + data = { + key = var_name + environment_scope = env_scope != "" ? env_scope : "*" + value = v + protected = cfg.protected + masked = cfg.masked + description = cfg.description + } + } + ] + ]) : pair.id => pair.data + } } diff --git a/main.tf b/main.tf index 882e72a..4172bf8 100644 --- a/main.tf +++ b/main.tf @@ -1 +1,98 @@ -//Main resources +resource "gitlab_project" "project" { + name = var.name + description = var.description + namespace_id = data.gitlab_group.parent.id + initialize_with_readme = var.forked_from_project_id == 0 ? true : null + default_branch = var.default_branch + tags = toset(concat(local.allowed_project_types[var.project_type].tags, var.tags)) + ci_config_path = var.ci_config_path == null ? local.allowed_project_types[var.project_type].ci_config_path : var.ci_config_path + build_git_strategy = var.build_git_strategy + avatar = local.avatar == null ? null : "${local.avatar}" + avatar_hash = local.avatar == null ? null : filesha256("${local.avatar}") + archive_on_destroy = true + archived = var.archived + only_allow_merge_if_all_discussions_are_resolved = true + only_allow_merge_if_pipeline_succeeds = var.only_allow_merge_if_pipeline_succeeds + merge_pipelines_enabled = true + allow_merge_on_skipped_pipeline = var.allow_merge_on_skipped_pipeline + group_runners_enabled = var.group_runners_enabled + build_timeout = var.build_timeout + auto_cancel_pending_pipelines = var.auto_cancel_pending_pipelines + shared_runners_enabled = var.shared_runners_enabled + forked_from_project_id = var.forked_from_project_id == 0 ? null : var.forked_from_project_id + + lifecycle { + prevent_destroy = true + } +} + +resource "gitlab_project_push_rules" "push_rule" { + project = gitlab_project.project.id + commit_message_regex = var.is_enable_conventional_commits_push_rule == true ? "^((build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\\([-a-zA-Z0-9_]+\\))?(!)?(: (.*\\s*)*))|(Merge (.*\\s*)*)|(Initial commit$)" : "" +} + +resource "gitlab_branch_protection" "protected_branches" { + for_each = var.protected_branches + + project = gitlab_project.project.id + branch = each.key + push_access_level = each.value.push_access_level + merge_access_level = each.value.merge_access_level + allow_force_push = true +} + +resource "gitlab_tag_protection" "protected_tags" { + for_each = var.protected_tags + + project = gitlab_project.project.id + tag = each.key + create_access_level = each.value.create_access_level +} + +resource "gitlab_project_variable" "variable" { + for_each = local.merged_project_variables + + project = gitlab_project.project.id + key = each.key + value = each.value.value + description = each.value.description + protected = lookup(each.value, "protected", false) + masked = lookup(each.value, "masked", false) + environment_scope = lookup(each.value, "environment_scope", "*") +} + +resource "gitlab_project_variable" "scoped_variables" { + for_each = local.scoped_variable_map + + project = gitlab_project.project.id + key = each.value.key + value = each.value.value + environment_scope = each.value.environment_scope + protected = each.value.protected + masked = each.value.masked + description = each.value.description +} + +resource "gitlab_project_environment" "environments" { + for_each = local.merged_environments + + project = gitlab_project.project.id + name = each.key + external_url = each.value + stop_before_destroy = true +} + +resource "gitlab_project_custom_attribute" "custom_attributes" { + for_each = var.attributes + + project = gitlab_project.project.id + key = each.key + value = each.value +} + +resource "gitlab_project_job_token_scopes" "ci_token_scope" { + project = gitlab_project.project.id + enabled = true + target_group_ids = local.token_scope_group_ids + target_project_ids = local.token_scope_project_ids +} diff --git a/output.tf b/output.tf index c608d5f..e69de29 100644 --- a/output.tf +++ b/output.tf @@ -1,3 +0,0 @@ -output "example" { - //value = some_resource.example -} diff --git a/provider.tf b/provider.tf index f21e096..927c934 100644 --- a/provider.tf +++ b/provider.tf @@ -1,5 +1,8 @@ terraform { required_providers { - //Provider name + gitlab = { + source = "gitlabhq/gitlab" + version = "18.0.0" + } } } diff --git a/variable.tf b/variable.tf index f8598a8..6a7e26c 100644 --- a/variable.tf +++ b/variable.tf @@ -1,9 +1,237 @@ variable "name" { type = string - description = "name" + description = "Repository Name" +} + +variable "forked_from_project_id" { + type = number + description = "Forked from" + default = 0 } variable "description" { type = string - description = "description" + description = "Repository Description" } + +variable "parent_group" { + type = string + description = "Parent Group" +} + +variable "default_branch" { + type = string + description = "Default branch" + default = "master" +} + +variable "ci_config_path" { + type = string + description = "CI PATH" + default = null +} + +variable "tags" { + type = list(string) + description = "Tags" + default = [] +} + +variable "build_git_strategy" { + type = string + default = "clone" + description = "The Git strategy. Defaults to fetch." +} + +variable "archived" { + type = bool + default = false + description = "Archived project" +} + +variable "allowed_project_types_json" { + type = string + default = "" + description = "Path to allowed project types json" +} + +variable "project_type" { + type = string + description = "Project type" + default = "" + + validation { + condition = contains(keys(local.allowed_project_types), var.project_type) + error_message = "Unsupported project project_type" + } +} + +variable "allowed_avatar_types_json" { + type = string + default = "" + description = "Path to allowed avatar types json" +} + +variable "avatar" { + type = string + description = "Type of the avatar for the group (default: from type)" + default = "" + + validation { + condition = contains(local.allowed_avatar_types, var.avatar) + error_message = "Unsupported group type" + } +} + + +variable "is_enable_conventional_commits_push_rule" { + type = bool + default = false + description = "Enable conventional commits push rule" +} + +variable "protected_branches" { + type = map(object({ + push_access_level = string + merge_access_level = string + })) + description = "Protected branches" + default = { + "master" = { + push_access_level = "maintainer" + merge_access_level = "developer" + } + } +} + +variable "protected_tags" { + type = map(object({ + create_access_level = string + })) + description = "Protected tags" + default = { + "v*" = { + create_access_level = "maintainer" + } + } +} + +variable "is_enabled_checkmarx" { + type = bool + default = true + description = "Enable checkmarx" +} + +variable "is_enabled_sonarqube" { + type = bool + default = true + description = "Enable sonarqube" +} + + +variable "sib_aplikacja" { + type = string + description = "[UPW] ID Aplikacja w zasobach SIB" + default = "" +} + +variable "ci_variables" { + type = map(object({ + value = string + description = optional(string) + protected = optional(bool) + masked = optional(bool) + environment_scope = optional(string) + })) + description = "CI variables to be set for the group" + default = null +} + +variable "scoped_variables" { + description = <<-EOT + { + "VARIABLE_NAME" = { + description = "opis" + protected = optional(bool, false) + masked = optional(bool, false) + values = { + "environment_name" = "value" + "environment_2" = "value2" + } + } + } + EOT + type = map(object({ + description = optional(string, "") + protected = optional(bool, false) + masked = optional(bool, false) + values = map(string) + })) + default = {} +} + +variable "environments" { + type = map(string) + description = "Project environments" + default = {} +} + +variable "attributes" { + type = map(string) + default = {} + description = "custom attributes to be set for the project" +} + +variable "avatars_dir" { + description = "Avatars directory png files" + type = string + default = "" +} + +variable "only_allow_merge_if_pipeline_succeeds" { + description = "Set to true if you want allow merges only if a pipeline succeeds." + type = bool + default = false +} + +variable "allow_merge_on_skipped_pipeline" { + description = "Set to true if you want to treat skipped pipelines as if they finished with success." + type = bool + default = true +} + +variable "group_runners_enabled" { + description = "Enable group runners for this project." + type = bool + default = true +} + +variable "shared_runners_enabled" { + description = "Enable shared runners for this project." + type = bool + default = true +} + +variable "build_timeout" { + description = "The maximum amount of time, in seconds, that a job can run." + type = number + default = 3600 +} + +variable "auto_cancel_pending_pipelines" { + description = "Auto-cancel pending pipelines. This isn’t a boolean, but enabled/disabled." + type = string + default = "enabled" +} + +variable "token_scope_groups" { + description = "CI_JOB_TOKEN group allowlist" + type = list(string) + default = [] +} + +variable "token_scope_repositories" { + description = "CI_JOB_TOKEN repositories allowlist" + type = list(string) + default = [] +} \ No newline at end of file