From ddfec7f7a8512940adb8e4a5c0353950cb230a51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Taneli=20Lepp=C3=A4?= Date: Tue, 21 Jun 2022 10:45:27 +0200 Subject: [PATCH] Allow creating repositories in Gitlab via Terraform. --- fast/stages/00-bootstrap/README.md | 21 +++++++ fast/stages/00-bootstrap/cicd.tf | 28 ++++++++- .../stages/00-bootstrap/identity-providers.tf | 4 +- .../00-bootstrap/modules/gitlab/main.tf | 61 +++++++++++++++++++ .../00-bootstrap/modules/gitlab/outputs.tf | 30 +++++++++ .../00-bootstrap/modules/gitlab/variables.tf | 31 ++++++++++ .../00-bootstrap/modules/gitlab/versions.tf | 23 +++++++ fast/stages/00-bootstrap/variables.tf | 30 +++++++++ 8 files changed, 224 insertions(+), 4 deletions(-) create mode 100644 fast/stages/00-bootstrap/modules/gitlab/main.tf create mode 100644 fast/stages/00-bootstrap/modules/gitlab/outputs.tf create mode 100644 fast/stages/00-bootstrap/modules/gitlab/variables.tf create mode 100644 fast/stages/00-bootstrap/modules/gitlab/versions.tf diff --git a/fast/stages/00-bootstrap/README.md b/fast/stages/00-bootstrap/README.md index 5ef1bfb4e9..05fb59098f 100644 --- a/fast/stages/00-bootstrap/README.md +++ b/fast/stages/00-bootstrap/README.md @@ -358,10 +358,20 @@ federated_identity_providers = { github-sample = { attribute_condition = "attribute.repository_owner==\"my-github-org\"" issuer = "github" + issuer_uri = null + allowed_audiences = null } gitlab-sample = { attribute_condition = "attribute.namespace_path==\"my-gitlab-org\"" issuer = "gitlab" + issuer_uri = null + allowed_audiences = null + } + gitlab-ce-sample = { + attribute_condition = "attribute.namespace_path==\"my-gitlab-org\"" + issuer = "gitlab-ce" + issuer_uri = "https://gitlab.fast.example.com" + allowed_audiences = ["https://gitlab.fast.example.com"] } } ``` @@ -380,14 +390,18 @@ cicd_repositories = { branch = null identity_provider = "github-sample" name = "my-gh-org/fast-bootstrap" + description = "Google Cloud organization bootstrapping" type = "github" } resman = { branch = "main" identity_provider = "github-sample" name = "my-gh-org/fast-resman" + description = "Google Cloud organization resource management" type = "github" } + networking = null + security = null } ``` @@ -395,6 +409,13 @@ The `type` attribute can be set to one of the supported repository types: `githu Once the stage is applied the generated output files will contain pre-configured workflow files for each repository, that will use Workload Identity Federation via a dedicated service account for each repository to impersonate the automation service account for the stage. +For Gitlab, you can use Terraform to automate creation of the repositories: + + - Set `gitlab_url` if you are using Gitlab CE (self-hosted) + - Set `create = true` (and/or `create_group = false`) in `cicd_repositories` + - Set `GITLAB_TOKEN` environment variable with a Gitlab access token that has the + necessary permissions to create the groups and projects + The remaining configuration is manual, as it regards the repositories themselves: - create a repository for modules diff --git a/fast/stages/00-bootstrap/cicd.tf b/fast/stages/00-bootstrap/cicd.tf index b4032c7665..73927d6440 100644 --- a/fast/stages/00-bootstrap/cicd.tf +++ b/fast/stages/00-bootstrap/cicd.tf @@ -23,12 +23,26 @@ locals { v != null && ( - v.type == "sourcerepo" + try(v.type, null) == "sourcerepo" || - contains(keys(local.identity_providers), coalesce(v.identity_provider, ":")) + contains(keys(local.identity_providers), coalesce(try(v.identity_provider, null), ":")) ) && - fileexists("${path.module}/templates/workflow-${v.type}.yaml") + fileexists(format("${path.module}/templates/workflow-%s.yaml", try(v.type, ""))) + ) + } + gitlab_cicd_repositories = { + for k, v in coalesce(var.cicd_repositories, {}) : k => v + if( + v != null + && + ( + try(v.type, null) == "gitlab" + || + contains(keys(local.identity_providers), coalesce(try(v.identity_provider, null), ":")) + ) + && + fileexists(format("${path.module}/templates/workflow-%s.yaml", try(v.type, ""))) ) } cicd_workflow_providers = { @@ -117,3 +131,11 @@ module "automation-tf-cicd-sa" { (module.automation-tf-output-gcs.name) = ["roles/storage.objectViewer"] } } + +# gitlab +module "automation-tf-gitlab-cicd-repo" { + source = "./modules/gitlab" + + gitlab_url = var.gitlab_url + cicd_repositories = local.gitlab_cicd_repositories +} diff --git a/fast/stages/00-bootstrap/identity-providers.tf b/fast/stages/00-bootstrap/identity-providers.tf index 7d4f2c6abd..ee5bbbe1be 100644 --- a/fast/stages/00-bootstrap/identity-providers.tf +++ b/fast/stages/00-bootstrap/identity-providers.tf @@ -19,7 +19,9 @@ locals { identity_providers = { for k, v in var.federated_identity_providers : k => merge( - v, lookup(local.identity_providers_defs, v.issuer, {}) + v, lookup(local.identity_providers_defs, v.issuer, {}), + { issuer_uri = lookup(v, "issuer_uri", v.issuer_uri) }, + { allowed_audiences = lookup(v, "allowed_audiences", v.allowed_audiences) } ) } identity_providers_defs = { diff --git a/fast/stages/00-bootstrap/modules/gitlab/main.tf b/fast/stages/00-bootstrap/modules/gitlab/main.tf new file mode 100644 index 0000000000..0a1917a091 --- /dev/null +++ b/fast/stages/00-bootstrap/modules/gitlab/main.tf @@ -0,0 +1,61 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + cicd_repositories = { for k, v in var.cicd_repositories : k => merge(v, + { group = join("/", slice(split("/", v.name), 0, length(split("/", v.name)) - 1)) }, + { name = element(split("/", v.name), length(split("/", v.name)) - 1) }, + { create_group = try(v.create_group, true) }) + if v != null } + gitlab_create_groups = distinct([for k, v in local.cicd_repositories : v.group if try(v.create_group, false)]) + gitlab_existing_groups = distinct([for k, v in local.cicd_repositories : v.group if !try(v.create_group, false)]) +} + +provider "gitlab" { + base_url = var.gitlab_url +} + +data "gitlab_group" "group" { + for_each = toset(local.gitlab_existing_groups) + full_path = each.value +} + +data "gitlab_project" "projects" { + for_each = { for name, repo in local.cicd_repositories : name => repo if !try(repo.create, true) } + id = format("%s/%s", each.value.group, each.value.name) +} + +resource "gitlab_group" "group" { + for_each = toset(local.gitlab_create_groups) + + name = each.value + path = each.value + description = "Cloud Foundation Fabric FAST: github.com/GoogleCloudPlatform/cloud-foundation-fabric/tree/master/fast/" +} + +resource "gitlab_project" "projects" { + for_each = { for name, repo in local.cicd_repositories : name => repo if try(repo.create, true) } + + name = each.value.name + namespace_id = each.value.create_group ? gitlab_group.group[each.value.group].id : data.gitlab_group.group[each.value.group].id + description = each.value.description + + visibility_level = var.gitlab_project_visibility + auto_devops_enabled = false +} + + + diff --git a/fast/stages/00-bootstrap/modules/gitlab/outputs.tf b/fast/stages/00-bootstrap/modules/gitlab/outputs.tf new file mode 100644 index 0000000000..760401ac78 --- /dev/null +++ b/fast/stages/00-bootstrap/modules/gitlab/outputs.tf @@ -0,0 +1,30 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +locals { + repository_urls = { for k, v in local.cicd_repositories : k => v.create ? gitlab_project.projects[k].http_url_to_repo : data.gitlab_project.projects[k].http_url_to_repo } + repository_ssh = { for k, v in local.cicd_repositories : k => v.create ? gitlab_project.projects[k].ssh_url_to_repo : data.gitlab_project.projects[k].ssh_url_to_repo } +} + +output "repository_urls" { + description = "Repository HTTPS clone URLs" + value = local.repository_urls +} + +output "repository_ssh" { + description = "Repository SSH clone URLs" + value = local.repository_ssh +} diff --git a/fast/stages/00-bootstrap/modules/gitlab/variables.tf b/fast/stages/00-bootstrap/modules/gitlab/variables.tf new file mode 100644 index 0000000000..dbcc1db917 --- /dev/null +++ b/fast/stages/00-bootstrap/modules/gitlab/variables.tf @@ -0,0 +1,31 @@ +/** + * Copyright 2022 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "gitlab_url" { + description = "Gitlab URL" + type = string +} + +variable "cicd_repositories" { + description = "CI/CD repository configuration." + type = any +} + +variable "gitlab_project_visibility" { + description = "Project visibility setting for FAST projects" + type = string + default = "private" +} diff --git a/fast/stages/00-bootstrap/modules/gitlab/versions.tf b/fast/stages/00-bootstrap/modules/gitlab/versions.tf new file mode 100644 index 0000000000..c3bd5aede1 --- /dev/null +++ b/fast/stages/00-bootstrap/modules/gitlab/versions.tf @@ -0,0 +1,23 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_version = ">= 1.1.0" + required_providers { + gitlab = { + source = "gitlabhq/gitlab" + version = ">= 3.15.0" + } + } +} diff --git a/fast/stages/00-bootstrap/variables.tf b/fast/stages/00-bootstrap/variables.tf index a08fedb1c5..8a8779e792 100644 --- a/fast/stages/00-bootstrap/variables.tf +++ b/fast/stages/00-bootstrap/variables.tf @@ -35,13 +35,35 @@ variable "cicd_repositories" { branch = string identity_provider = string name = string + description = string type = string + create = bool }) resman = object({ branch = string identity_provider = string name = string + description = string type = string + create = bool + }) + networking = object({ + branch = string + identity_provider = string + name = string + description = string + type = string + create = bool + create_group = bool + }) + security = object({ + branch = string + identity_provider = string + name = string + description = string + type = string + create = bool + create_group = bool }) }) default = null @@ -74,6 +96,12 @@ variable "cicd_repositories" { } } +variable "gitlab_url" { + description = "Gitlab URL." + type = string + default = "https://gitlab.com" +} + variable "custom_role_names" { description = "Names of custom roles defined at the org level." type = object({ @@ -91,6 +119,8 @@ variable "federated_identity_providers" { type = map(object({ attribute_condition = string issuer = string + allowed_audiences = list(string) + issuer_uri = string })) default = {} nullable = false