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) | {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