From a34d93fb43120b410c85eeac17bf415838e1c948 Mon Sep 17 00:00:00 2001 From: simonebruzzechesse <60114646+simonebruzzechesse@users.noreply.github.com> Date: Tue, 27 Feb 2024 18:36:46 +0100 Subject: [PATCH] Gitlab blueprint (#2110) * add gitlab blueprint * add TODO.md --------- Co-authored-by: Julio Castillo --- blueprints/third-party-solutions/README.md | 6 + .../third-party-solutions/gitlab/README.md | 389 ++++++++++++++++++ .../third-party-solutions/gitlab/TODO.md | 20 + .../gitlab/assets/cloud-config.yaml | 118 ++++++ .../gitlab/assets/config.rb.tpl | 118 ++++++ .../gitlab/assets/sshd_config | 27 ++ .../third-party-solutions/gitlab/diagram.png | Bin 0 -> 83010 bytes .../third-party-solutions/gitlab/gitlab.tf | 130 ++++++ .../third-party-solutions/gitlab/main.tf | 44 ++ .../third-party-solutions/gitlab/outputs.tf | 56 +++ .../third-party-solutions/gitlab/services.tf | 92 +++++ .../third-party-solutions/gitlab/ssl.tf | 107 +++++ .../gitlab/terraform.tfvars.sample | 19 + .../third-party-solutions/gitlab/variables.tf | 139 +++++++ 14 files changed, 1265 insertions(+) create mode 100644 blueprints/third-party-solutions/gitlab/README.md create mode 100644 blueprints/third-party-solutions/gitlab/TODO.md create mode 100644 blueprints/third-party-solutions/gitlab/assets/cloud-config.yaml create mode 100644 blueprints/third-party-solutions/gitlab/assets/config.rb.tpl create mode 100644 blueprints/third-party-solutions/gitlab/assets/sshd_config create mode 100644 blueprints/third-party-solutions/gitlab/diagram.png create mode 100644 blueprints/third-party-solutions/gitlab/gitlab.tf create mode 100644 blueprints/third-party-solutions/gitlab/main.tf create mode 100644 blueprints/third-party-solutions/gitlab/outputs.tf create mode 100644 blueprints/third-party-solutions/gitlab/services.tf create mode 100644 blueprints/third-party-solutions/gitlab/ssl.tf create mode 100644 blueprints/third-party-solutions/gitlab/terraform.tfvars.sample create mode 100644 blueprints/third-party-solutions/gitlab/variables.tf diff --git a/blueprints/third-party-solutions/README.md b/blueprints/third-party-solutions/README.md index 13a441b2..94449181 100644 --- a/blueprints/third-party-solutions/README.md +++ b/blueprints/third-party-solutions/README.md @@ -26,4 +26,10 @@ The blueprints in this folder show how to automate installation of specific thir

These examples show how to deploy F5 BigIP-VE load balancers in GCP.

+
+ +### Gitlab + +

This blueprint shows how to deploy a Gitlab instance in GCP. The architecture is based on the reference described in the [official documentation](https://docs.gitlab.com/ee/administration/reference_architectures/1k_users.html) with managed services such as Cloud SQL, Memorystore and Cloud Storage.

+
\ No newline at end of file diff --git a/blueprints/third-party-solutions/gitlab/README.md b/blueprints/third-party-solutions/gitlab/README.md new file mode 100644 index 00000000..f720b8b0 --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/README.md @@ -0,0 +1,389 @@ +# Gitlab Blueprint + +This blueprint is responsible for provisioning a production ready Gitlab instance on the landing zone infrastructure. The [reference architecture](https://docs.gitlab.com/ee/administration/reference_architectures/1k_users.html) of this deployment target 1K users, updates to the current code is required in of HA and/or higher capacity requirements. + +The following diagram illustrates the high-level design of created resources, which can be adapted to specific requirements via variables: + +

+ Gitlab +

+ +## Table of contents + + +* [Gitlab Blueprint](#gitlab-blueprint) + * [Table of contents](#table-of-contents) + * [Managed Services for Seamless Operations](#managed-services-for-seamless-operations) + * [Object Storage <-> Google Cloud Storage](#object-storage-----google-cloud-storage) + * [Identity](#identity) + * [SAML Integration](#saml-integration) + * [Google Workspace Setup](#google-workspace-setup) + * [Others Identity Integration](#others-identity-integration) + * [Email](#email) + * [Sendgrid integration](#sendgrid-integration) + * [SSL Certificate Configuration](#ssl-certificate-configuration) + * [Networking and scalability](#networking-and-scalability) + * [HA](#ha) + * [Deployment](#deployment) + * [Step 0: Cloning the repository](#step-0--cloning-the-repository) + * [Step 2: Prepare the variables](#step-2--prepare-the-variables) + * [Step 3: Deploy resources](#step-3--deploy-resources) + * [Step 4: Use the created resources](#step-4--use-the-created-resources) + * [Reference and useful links](#reference-and-useful-links) + * [Files](#files) + * [Variables](#variables) + * [Outputs](#outputs) + + +## Managed Services for Seamless Operations + +This Gitlab installation prioritizes the use of Google Cloud managed services to +streamline infrastructure management and optimization. Here's a breakdown of the +managed services incorporated: + +1. [Google Cloud Storage](https://cloud.google.com/storage): is a highly + scalable and secure object storage service for storing and accessing data in + Google Cloud.

+2. [Cloud SQL PostgreSQL](https://cloud.google.com/sql/docs/postgres): Cloud SQL + for Postgres is a fully managed database service on Google Cloud Platform. It + eliminates database administration tasks, allowing you to focus on your + application, while offering high performance, automatic scaling, and secure + management of your PostgreSQL databases.

+3. [Memorystore](https://cloud.google.com/memorystore?hl=en): GCP Memorystore + offers a fully managed Redis service for in-memory data caching and + high-performance data access. + +Benefits: + +- Reduced Operational Overhead: Google handles infrastructure setup, + maintenance, and updates, freeing up your time and resources. +- Enhanced Security: Managed services often benefit from Google's comprehensive + security measures and expertise. +- Scalability: Easily adjust resource allocation to meet evolving demands. +- Cost Optimization: Pay for the resources you use, benefiting from Google's + infrastructure optimization. + Integration: Managed services seamlessly integrate with other GCP services, + promoting a cohesive cloud environment. + This module embraces managed services to deliver a resilient, scalable, and + cost-effective application architecture on Google Cloud. + +### Object Storage <-> Google Cloud Storage + +GitLab supports using an object storage service for holding numerous types of +data. It’s recommended over NFS and in general it’s better in larger setups as +object storage is typically much more performant, reliable, and scalable. + +A single storage connection to Cloud Storage is configured for all object types, +which leverages default Google Compute Engine credential (the so called " +consolidated form"). A Cloud Storage bucket is bootstrapped for each object +type, the table below summarized such a configuration: + +| Object Type | Description | Cloud Storage Bucket | +|------------------|----------------------------------------|-----------------------------------| +| artifacts | CI artifacts | ${prefix}-gitlab-artifacts | +| external_diffs | Merge request diffs | ${prefix}-mr-diffs | +| uploads | User uploads | ${prefix}-gitlab-uploads | +| lfs | Git Large File Storage objects | ${prefix}-gitlab-lfs | +| packages | Project packages (e.g. PyPI, Maven ..) | ${prefix}-gitlab-packages | +| dependency_proxy | Dependency Proxy | ${prefix}-gitlab-dependency-proxy | +| terraform_state | Terraform state files | ${prefix}-gitlab-terraform-state | +| pages | Pages | ${prefix}-gitlab-pages | + +For more information on Gitlab object storage and Google Cloud Storage +integration please refer to the official Gitlab documentation available at the +following [link](https://docs.gitlab.com/ee/administration/object_storage.html). + +- [PostgreSQL service](https://docs.gitlab.com/ee/administration/postgresql/external.html) + +Updated postgres configuration to match documentation, created required database +in postgres instance. + +- [Redis](https://docs.gitlab.com/ee/administration/redis/replication_and_failover_external.html) + +## Identity + +GitLab integrates with a number of OmniAuth providers as well as external +authentication and authorization providers such as Google Secure LDAP and many +other providers. +At this time this stage can deal with SAML integration for both user +authentication and provisioning, in order to setup SAML integration please +provide the saml block on gitlab_config variable. + +### SAML Integration + +This section details how configure GitLab to act as a SAML service provider ( +SP). This allows GitLab to consume assertions from a SAML identity provider ( +IdP), such as Cloud Identity, to authenticate users. Please find instructions +below for integration with: + +- [Google Workspace](#google-workspace-setup) + +#### Google Workspace Setup + +Setup of Google Workspace is documented in the official Gitlab documentation +available at the +following [link](https://docs.gitlab.com/ee/integration/saml.html#set-up-google-workspace) +which are also reported below for simplicity. + +Create a custom SAML webapp following instructions available at the +following [link](https://support.google.com/a/answer/6087519), providing these +information in the service provider configuration: + +| Configuration | Typical Value | Cloud Storage Bucket | +|-------------------|--------------------------------------------------|---------------------------------------------------------------------------------------------| +| Name of SAML App | Gitlab | Name of the app | +| ACS URL | https:///users/auth/saml/callback | Assertion Consumer Service URL. | +| GITLAB_DOMAIN | gitlab.example.com | Your GitLab instance domain. | +| Entity ID | https://gitlab.example.com | A value unique to your SAML application. Set it to the issuer in your GitLab configuration. | +| Name ID | EMAIL | Required value. Also known as name_identifier_format. | + +Then setup the following SAML attribute mappings: + +| Google Directory attributes | App attributes | +|--------------------------------|----------------| +| Basic information > Email | email | +| Basic Information > First name | first_name | +| Basic Information > Last name | last_name | + +After configuring the Google Workspace SAML application, record the following +information: + +| Value | Description | +|------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------| +| SSO URL | Setup in gitlab_config.saml.sso_target_url variable | +| Certificate (download) | Setup in gitlab_config.saml.idp_cert_fingerprint (obtain value with the following command `openssl x509 -in -noout -fingerprint -sha1`) | + +### Others Identity Integration + +- [OpenID Connect OmniAuth](https://docs.gitlab.com/ee/administration/auth/oidc.html#configure-google) +- [Google Secure LDAP](https://docs.gitlab.com/ee/administration/auth/ldap/google_secure_ldap.html) + +## Email + +### Gmail / Workspace + +- [ ] [documentation](https://docs.gitlab.com/ee/administration/incoming_email.html#gmail) + +### Sendgrid integration + +Use +the [Google Cloud Marketplace](https://console.cloud.google.com/marketplace/details/sendgrid-app/sendgrid-email) +to sign up for the SendGrid email service. Make a note of your SendGrid SMTP +account credentials, which include username, password, and hostname. Your SMTP +username and password are the same as what you used to sign up for the service. +The SendGrid hostname is smtp.sendgrid.net. +Create an API key: +Sign in to SendGrid and go to Settings > API Keys. + +1. Create an API key. +2. Select the permissions for the key. At a minimum, the key must have Mail send + permissions to send email. +3. Click Save to create the key. +4. SendGrid generates a new key. This is the only copy of the key, so make sure + that you copy the key and save it for later. + +Configure the sendgrid API key in the gitlab_config variable, under mail, +sendgrid arguments as per the following example: + +```terraform +gitlab_config = { + hostname = "gitlab.example.com" + mail = { + sendgrid = { + api_key = "test" + } + } +} +``` + +## SSL Certificate Configuration + +This module provides flexibility in configuring SSL certificates for the server. +You have two options: + +1. **Provide Your Own Certificates**: If you have existing SSL certificates, you + can place them in the certs folder within the module's directory. The module + will automatically detect and use them. + File Names: Ensure the files are named ${gitlab_hostname}.crt (for the + certificate) and + gitlab_hostname.key (for the private key). Although it is not required in + this stage it is mandatory to also place inside the certs folder the server + CA certificate which is later use to secure HTTPS access from the Gitlab + runner. Name of the CA certificate should be: ${gitlab_hostname}.ca.crt +2. **Use Automatically Generated Self-Signed Certificates**: If you don't + provide certificates, the module will generate a self-signed certificate for + immediate use. + Updating Later: You can replace the self-signed certificate with your own + certificates at any time by placing them in the certs folder and re-running + Terraform. + +**Important Notes:** + +Certificate Validation: Self-signed certificates are not validated by browsers +and will trigger warnings. Use them only for development or testing +environments. + +For more information on how to configure HTTPS on Gitlab please refer to the +original Gitlab documentation available at the +following [link](https://docs.gitlab.com/omnibus/settings/ssl/#configure-https-manually). + +## Networking and scalability + +- [Load balancer](https://docs.gitlab.com/ee/administration/load_balancer.html) + +## HA + +- [High Availability](http://ubimol.it/12.0/ee/administration/high_availability/README.html) + +### Deployment + +#### Step 0: Cloning the repository + +If you want to deploy from your Cloud Shell, click on the image below, sign in +if required and when the prompt appears, click on “confirm”. + +[![Open Cloudshell](../../../assets/images/cloud-shell-button.png)](https://shell.cloud.google.com/cloudshell/editor?cloudshell_git_repo=https%3A%2F%2Fgithub.com%2FGoogleCloudPlatform%2Fcloud-foundation-fabric&cloudshell_workspace=blueprints%2Fthird-party-solutions%2Fwordpress%2Fcloudrun) + +Otherwise, in your console of choice: + +```bash +git clone https://github.com/GoogleCloudPlatform/cloud-foundation-fabric +``` + +Before you deploy the architecture, you will need at least the following +information (for more precise configuration see the Variables section): + +* The project ID + +The VPC host project, VPC and subnets should already exist and the following networking requirements are satisfied: +- configured PSA for Cloud SQL on the VPC +- subnets configured with PGA and Cloud NAT for internet access +- Inbound firewall rule for IAP on port 22 +- Inbound firewall rule for TCP ports 80, 443, 2222 from proxy subnet CIDR (gitlab) + +#### Step 2: Prepare the variables + +Once you have the required information, head back to your cloned repository. +Make sure you’re in the directory of this tutorial (where this README is in). + +Configure the Terraform variables in your `terraform.tfvars` file. +See [terraform.tfvars.sample](terraform.tfvars.sample) as starting point - just +copy it to `terraform.tfvars` and edit the latter. See the variables +documentation below. + +#### Step 3: Deploy resources + +Initialize your Terraform environment and deploy the resources: + +```shell +terraform init +terraform apply +``` + +#### Step 4: Use the created resources + +Connect to squid-proxy for accessing gitlab instance using the gcloud command +available in the `ssh_to_bastion` terraform output. + +```bash +terraform output ssh_to_bastion +``` + +A gcloud command like the following should be available + +```bash +gcloud compute ssh squid-vm --project ${project} --zone europe-west8-b -- -L 3128:127.0.0.1:3128 -N -q -f +``` + +Set as system proxy ip 127.0.0.1 and port 3128 and connect to Gitlab hostname https://gitlab.gcp.example.com. +Use default admin password available in /run/gitlab/config/initial_root_password or reset admin password via the following command on the Docker container: + +```bash +gitlab-rake “gitlab:password:reset” +``` + +## Reference and useful links + +- [Reference architecture up to 1k users](https://docs.gitlab.com/ee/administration/reference_architectures/1k_users.html) +- [`/etc/gitlab/gitlab.rb` template](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-config-template/gitlab.rb.template) +- [`/etc/gitlab/gitlab.rb` default options](https://docs.gitlab.com/ee/administration/package_information/defaults.html) + + + +## Files + +| name | description | modules | resources | +|---|---|---|---| +| [gitlab.tf](./gitlab.tf) | None | compute-vm · iam-service-account · net-lb-int | | +| [main.tf](./main.tf) | Module-level locals and resources. | project | | +| [outputs.tf](./outputs.tf) | Module outputs. | | | +| [services.tf](./services.tf) | None | cloudsql-instance · gcs | google_redis_instance | +| [ssl.tf](./ssl.tf) | None | | tls_cert_request · tls_locally_signed_cert · tls_private_key · tls_self_signed_cert | +| [variables.tf](./variables.tf) | Module variables. | | | + +## Variables + +| name | description | type | required | default | producer | +|---|---|:---:|:---:|:---:|:---:| +| [gitlab_instance_config](variables.tf#L69) | Gitlab Compute Engine instance config. | object({…}) | ✓ | | | +| [network_config](variables.tf#L89) | Shared VPC network configurations to use for Gitlab Runner VM. | object({…}) | ✓ | | | +| [prefix](variables.tf#L98) | Prefix used for resource names. | string | ✓ | | | +| [project_id](variables.tf#L117) | Project id, references existing project if `project_create` is null. | string | ✓ | | | +| [region](variables.tf#L136) | GCP Region. | string | ✓ | | | +| [admin_principals](variables.tf#L17) | Users, groups and/or service accounts that are assigned roles, in IAM format (`group:foo@example.com`). | list(string) | | [] | | +| [cloudsql_config](variables.tf#L23) | Cloud SQL Postgres config. | object({…}) | | {} | | +| [gcs_config](variables.tf#L34) | GCS for Object Storage config. | object({…}) | | {} | | +| [gitlab_config](variables.tf#L45) | Gitlab configuration. | object({…}) | | {} | | +| [project_create](variables.tf#L108) | Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format. | object({…}) | | null | | +| [redis_config](variables.tf#L122) | Redis Config. | object({…}) | | {} | | + +## Outputs + +| name | description | sensitive | consumers | +|---|---|:---:|---| +| [gitlab_ilb_ip](outputs.tf#L26) | Gitlab Internal Load Balancer IP Address. | | | +| [instance](outputs.tf#L31) | Gitlab compute engine instance. | | | +| [postgresql_users](outputs.tf#L36) | Gitlab postgres user password. | ✓ | | +| [project](outputs.tf#L42) | GCP project. | | | +| [ssh_to_gitlab](outputs.tf#L47) | gcloud command to ssh gitlab instance. | | | +| [ssl_certs](outputs.tf#L52) | Gitlab SSL Certificates. | ✓ | | + +## Test + +```hcl +module "test" { + source = "./fabric/blueprints/third-party-solutions/gitlab" + gitlab_config = { + hostname = "gitlab.gcp.example.com" + mail = { + sendgrid = { + api_key = "sample_api_key" + } + } + saml = { + idp_cert_fingerprint = "67:90:96.....REPLACE_ME" + sso_target_url = "https://accounts.google.com/o/saml2/idp?idpid=REPLACE_ME" + } + } + gitlab_instance_config = { + replica_zone = "europe-west8-c" + zone = "europe-west8-b" + data_disk = { + replica_zone = "europe-west8-c" + } + } + network_config = { + host_project = "host-project" + network_self_link = "https://www.googleapis.com/compute/v1/projects/prod-net-landing-0/global/networks/prod-landing-0" + subnet_self_link = "https://www.googleapis.com/compute/v1/projects/prod-net-landing-0/regions/europe-west1/subnetworks/landing-default-ew1" + } + prefix = "prefix" + project_create = { + billing_account_id = "1234-ABCD-1234" + parent = "folders/1234563" + } + project_id = "my-project" + region = "europe-west8" +} +# tftest modules=14 resources=50 +``` diff --git a/blueprints/third-party-solutions/gitlab/TODO.md b/blueprints/third-party-solutions/gitlab/TODO.md new file mode 100644 index 00000000..e6eae43e --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/TODO.md @@ -0,0 +1,20 @@ +# Gitlab TODOs + +- Integrations + - [x] Identity + - [x] SAML + - [ ] Email: + - [ ] Gmail / Workspace + - [x] Sendgrid +- [x] ILB +- [ ] MIG +- [ ] Gitaly +- [x] HTTPS SSL +- [x] Gitlab SSH on port 2222 +- [x] Check object store, use GCS wherever possible +- [x] Cloud SQL HA +- Memorystore + - [ ] HA + - [ ] PSC instead of PSA +- [x] Integration with Cloud Logging for Gitlab running on Docker container +- [ ] Integrate with Certificate Manager for SSH certificates diff --git a/blueprints/third-party-solutions/gitlab/assets/cloud-config.yaml b/blueprints/third-party-solutions/gitlab/assets/cloud-config.yaml new file mode 100644 index 00000000..cfce725e --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/assets/cloud-config.yaml @@ -0,0 +1,118 @@ +#cloud-config + +# Copyright 2024 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. + +# https://hub.docker.com/r/nginx/nginx/ +# https://nginx.io/manual/toc/#installation + +write_files: + - path: /var/lib/docker/daemon.json + permissions: '0644' + owner: root + content: | + { + "live-restore": true, + "storage-driver": "overlay2", + "log-driver": "gcplogs", + "log-opts": { + "gcp-meta-name": "gitlab-0", + "max-size": "1024m" + } + } + - path: /tmp/gitlab/config/gitlab.rb + permissions: '0600' + owner: root + content: | + ${gitlab_rb} + - path: /tmp/gitlab/ssl/${gitlab_cert_name}.key + permissions: '0600' + owner: root + content: | + ${gitlab_ssl_key} + - path: /tmp/gitlab/ssl/${gitlab_cert_name}.crt + permissions: '0600' + owner: root + content: | + ${gitlab_ssl_crt} + - path: /tmp/gitlab/sshd_config + permissions: '0644' + owner: root + content: | + ${gitlab_sshd_config} + + - path: /etc/systemd/system/gitlab-data.service + permissions: '0644' + owner: root + content: | + [Unit] + Description=Gitlab data disk + ConditionPathExists=/dev/disk/by-id/google-data + Before=gitlab.service + [Service] + Type=oneshot + ExecStart=/bin/mkdir -p /run/gitlab + ExecStart=/bin/bash -c \ + "/bin/lsblk -fn -o FSTYPE \ + /dev/disk/by-id/google-data |grep ext4 \ + || mkfs.ext4 -m 0 -F -E lazy_itable_init=0,lazy_journal_init=0,discard \ + /dev/disk/by-id/google-data" + ExecStart=/bin/bash -c \ + "mount |grep /run/gitlab \ + || mount -t ext4 /dev/disk/by-id/google-data /run/gitlab" + ExecStart=/sbin/resize2fs /dev/disk/by-id/google-data + ExecStart=/bin/mkdir -p /run/gitlab/config + ExecStart=/bin/mkdir -p /run/gitlab/ssl + ExecStart=/bin/mv /tmp/gitlab/config/gitlab.rb /run/gitlab/config/gitlab.rb + ExecStart=/bin/mv /tmp/gitlab/sshd_config /run/gitlab/sshd_config + ExecStart=/bin/bash -c "base64 -d -i /tmp/gitlab/ssl/${gitlab_cert_name}.key > /run/gitlab/ssl/${gitlab_cert_name}.key" + ExecStart=/bin/bash -c "base64 -d -i /tmp/gitlab/ssl/${gitlab_cert_name}.crt > /run/gitlab/ssl/${gitlab_cert_name}.crt" + RemainAfterExit=true + + # https://docs.gitlab.com/ee/install/docker.html#pre-configure-docker-container + + - path: /etc/systemd/system/gitlab.service + permissions: '0644' + owner: root + content: | + [Unit] + Description=Start gitlab container + After=gitlab-data.service gcr-online.target docker.socket + Wants=gitlab-data.service gcr-online.target docker.socket docker-events-collector.service + [Service] + Environment="HOME=/home/gitlab" + ExecStartPre=/usr/bin/docker-credential-gcr configure-docker + ExecStartPre=mkdir -p /run/gitlab + ExecStart=/usr/bin/docker run --rm --name=gitlab \ + --hostname ${gitlab_config.hostname} \ + --shm-size 256m \ + --env GITLAB_OMNIBUS_CONFIG="" \ + --publish 443:443 \ + --publish 80:80 \ + --publish 2222:2222 \ + -v /run/gitlab/config:/etc/gitlab \ + -v /run/gitlab/ssl:/etc/gitlab/ssl \ + -v /run/gitlab/logs:/var/log/gitlab \ + -v /run/gitlab/data:/var/opt/gitlab \ + -v /run/gitlab/sshd_config:/assets/sshd_config \ + gitlab/gitlab-ce + ExecStop=/usr/bin/docker stop gitlab + +runcmd: + - systemctl start node-problem-detector + - iptables -I INPUT 1 -p tcp -m tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT + - iptables -I INPUT 1 -p tcp -m tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT + - iptables -I INPUT 1 -p tcp -m tcp --dport 2222 -m state --state NEW,ESTABLISHED -j ACCEPT + - systemctl daemon-reload + - systemctl start gitlab diff --git a/blueprints/third-party-solutions/gitlab/assets/config.rb.tpl b/blueprints/third-party-solutions/gitlab/assets/config.rb.tpl new file mode 100644 index 00000000..8aaf0c5e --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/assets/config.rb.tpl @@ -0,0 +1,118 @@ +# Copyright 2024 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. + +# /etc/gitlab/gitlab.rb + +external_url "https://${hostname}" +letsencrypt['enable'] = false +nginx['redirect_http_to_https'] = true + +# https://docs.gitlab.com/omnibus/settings/redis.html +gitlab_rails['redis_enable_client'] = false +gitlab_rails['redis_host'] = '${redis.host}' +gitlab_rails['redis_port'] = ${redis.port} +# TODO: use auth +# gitlab_rails['redis_password'] = nil +redis['enable'] = false + +# https://docs.gitlab.com/omnibus/settings/database.html#using-a-non-packaged-postgresql-database-management-server +postgresql['enable'] = false +gitlab_rails['db_adapter'] = 'postgresql' +gitlab_rails['db_encoding'] = 'utf8' +gitlab_rails['db_host'] = '${cloudsql.host}' +gitlab_rails['db_port'] = 5432 +gitlab_rails['db_password'] = '${cloudsql.password}' + +# https://docs.gitlab.com/ee/administration/object_storage.html#google-cloud-storage-gcs +# Consolidated object storage configuration +gitlab_rails['object_store']['enabled'] = true +gitlab_rails['object_store']['proxy_download'] = true +gitlab_rails['object_store']['connection'] = { + 'provider' => 'Google', + 'google_project' => '${project_id}', + 'google_application_default' => true +} +# full example using the consolidated form +# https://docs.gitlab.com/ee/administration/object_storage.html#full-example-using-the-consolidated-form-and-amazon-s3 +gitlab_rails['object_store']['objects']['artifacts']['bucket'] = '${prefix}-gitlab-artifacts' +gitlab_rails['object_store']['objects']['external_diffs']['bucket'] = '${prefix}-gitlab-mr-diffs' +gitlab_rails['object_store']['objects']['lfs']['bucket'] = '${prefix}-gitlab-lfs' +gitlab_rails['object_store']['objects']['uploads']['bucket'] = '${prefix}-gitlab-uploads' +gitlab_rails['object_store']['objects']['packages']['bucket'] = '${prefix}-gitlab-packages' +gitlab_rails['object_store']['objects']['dependency_proxy']['bucket'] = '${prefix}-gitlab-dependency-proxy' +gitlab_rails['object_store']['objects']['terraform_state']['bucket'] = '${prefix}-gitlab-terraform-state' +gitlab_rails['object_store']['objects']['pages']['bucket'] = '${prefix}-gitlab-pages' + +# SAML configuration +# https://docs.gitlab.com/ee/integration/saml.html +%{ if saml != null } +gitlab_rails['omniauth_enabled'] = true +gitlab_rails['omniauth_external_providers'] = ['saml'] +# create new user in case of sign in with SAML provider +gitlab_rails['omniauth_allow_single_sign_on'] = ['saml'] +# do not force approval from admins for newly created users +gitlab_rails['omniauth_block_auto_created_users'] = false +# automatically link a first-time SAML sign-in with existing GitLab users if their email addresses match +gitlab_rails['omniauth_auto_link_saml_user'] = true +# Force user redirection to SAML +# To bypass the auto sign-in setting, append ?auto_sign_in=false in the sign in URL, for example: https://gitlab.example.com/users/sign_in?auto_sign_in=false. +%{ if saml.forced } +gitlab_rails['omniauth_auto_sign_in_with_provider'] = 'saml' +%{ endif } +# SHA1 Fingerprint +gitlab_rails['omniauth_providers'] = [ + { + name: "saml", + label: "SAML", + args: { + assertion_consumer_service_url: "https://${hostname}/users/auth/saml/callback", + idp_cert_fingerprint: '${saml.idp_cert_fingerprint}', + idp_sso_target_url: '${saml.sso_target_url}', + issuer: "https://${hostname}", + name_identifier_format: "${saml.name_identifier_format}" + } + } +] +%{ endif } + + +# mail configuration +%{ if mail.sendgrid != null } +gitlab_rails['smtp_enable'] = true +gitlab_rails['smtp_address'] = "smtp.sendgrid.net" +gitlab_rails['smtp_port'] = 587 +gitlab_rails['smtp_user_name'] = "apikey" +gitlab_rails['smtp_password'] = "${mail.sendgrid.api_key}" +gitlab_rails['smtp_domain'] = "smtp.sendgrid.net" +gitlab_rails['smtp_authentication'] = "plain" +gitlab_rails['smtp_enable_starttls_auto'] = true +gitlab_rails['smtp_tls'] = false +# If use Single Sender Verification You must configure from. If not fail +# 550 The from address does not match a verified Sender Identity. Mail cannot be sent until this error is resolved. +# Visit https://sendgrid.com/docs/for-developers/sending-email/sender-identity/ to see the Sender Identity requirements +%{ if try(mail.sendgrid.email_from != null, false) } +gitlab_rails['gitlab_email_from'] = '${mail.sendgrid.email_from}' +%{ endif } +%{ if try(mail.sendgrid.email_reply_to != null, false) } +gitlab_rails['email_reply_to'] = '${mail.sendgrid.email_reply_to}' +%{ endif } +%{ endif } + + +gitlab_rails['gitlab_shell_ssh_port'] = 2222 +# gitlab_sshd['enable'] = true +# gitlab_sshd['listen_address'] = '[::]:2222' + +# https://docs.gitlab.com/omnibus/installation/index.html#set-up-the-initial-password +# gitlab_rails['initial_root_password'] = '' \ No newline at end of file diff --git a/blueprints/third-party-solutions/gitlab/assets/sshd_config b/blueprints/third-party-solutions/gitlab/assets/sshd_config new file mode 100644 index 00000000..942dd5f8 --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/assets/sshd_config @@ -0,0 +1,27 @@ +Port 2222 +ChallengeResponseAuthentication no +HostKey /etc/gitlab/ssh_host_rsa_key +HostKey /etc/gitlab/ssh_host_ecdsa_key +HostKey /etc/gitlab/ssh_host_ed25519_key +Protocol 2 +PermitRootLogin no +PasswordAuthentication no +MaxStartups 100:30:200 +AllowUsers git +PrintMotd no +PrintLastLog no +PubkeyAuthentication yes +AuthorizedKeysFile %h/.ssh/authorized_keys /gitlab-data/ssh/authorized_keys +AuthorizedKeysCommand /opt/gitlab/embedded/service/gitlab-shell/bin/gitlab-shell-authorized-keys-check git %u %k +AuthorizedKeysCommandUser git + +# With "UsePAM yes" the "!" is seen as a password disabled account and not fully locked so ssh public key login works +# Please make sure that the account is created without passwordlogin ("*" in /etc/shadow) or configure pam. +# Issue #5891 https://gitlab.com/gitlab-org/omnibus-gitlab +UsePAM no + +# Disabling use DNS in ssh since it tends to slow connecting +UseDNS no + +# Enable the use of Git protocol v2 +AcceptEnv GIT_PROTOCOL diff --git a/blueprints/third-party-solutions/gitlab/diagram.png b/blueprints/third-party-solutions/gitlab/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..d3f43ab0342e017922cd2c5a2f67b011e89998c8 GIT binary patch literal 83010 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 literal 0 HcmV?d00001 diff --git a/blueprints/third-party-solutions/gitlab/gitlab.tf b/blueprints/third-party-solutions/gitlab/gitlab.tf new file mode 100644 index 00000000..29ee4c5d --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/gitlab.tf @@ -0,0 +1,130 @@ +/** + * Copyright 2024 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 { + gitlab_rb = templatefile("${path.module}/assets/config.rb.tpl", { + project_id = module.project.project_id + cloudsql = { + host = module.db.instances.primary.private_ip_address + password = module.db.user_passwords.gitlab + } + mail = var.gitlab_config.mail + redis = { + host = google_redis_instance.cache.host + port = google_redis_instance.cache.port + } + prefix = var.prefix + saml = var.gitlab_config.saml + hostname = var.gitlab_config.hostname + }) + gitlab_ssl_crt = local.self_signed_ssl_certs_required ? tls_locally_signed_cert.gitlab_server_singed_cert.0.cert_pem : file("${path.module}/certs/${var.gitlab_config.hostname}.crt") + gitlab_ssl_key = local.self_signed_ssl_certs_required ? tls_private_key.gitlab_server_key.0.private_key_pem : file("${path.module}/certs/${var.gitlab_config.hostname}.key") + gitlab_ssl_ca_crt = local.self_signed_ssl_certs_required ? tls_self_signed_cert.gitlab_ca_cert.0.cert_pem : file("${path.module}/certs/${var.gitlab_config.hostname}.ca.crt") + gitlab_ssl_ca_key = local.self_signed_ssl_certs_required ? tls_private_key.gitlab_ca_private_key.0.private_key_pem : "" + self_signed_ssl_certs_required = fileexists("${path.module}/certs/${var.gitlab_config.hostname}.crt") && fileexists("${path.module}/certs/${var.gitlab_config.hostname}.key") && fileexists("${path.module}/certs/${var.gitlab_config.hostname}.ca.crt") ? false : true + gitlab_user_data = templatefile("${path.module}/assets/cloud-config.yaml", { + gitlab_config = var.gitlab_config + gitlab_rb = indent(6, local.gitlab_rb) + gitlab_sshd_config = indent(6, file("${path.module}/assets/sshd_config")) + gitlab_cert_name = var.gitlab_config.hostname + gitlab_ssl_key = indent(6, base64encode(local.gitlab_ssl_key)) + gitlab_ssl_crt = indent(6, base64encode(local.gitlab_ssl_crt)) + }) +} + +module "gitlab-sa" { + source = "../../../modules/iam-service-account" + project_id = module.project.project_id + name = var.gitlab_instance_config.name + display_name = "Gitlab instance service account" + iam = { + "roles/iam.serviceAccountTokenCreator" = [module.gitlab-sa.iam_email] + } + iam_project_roles = { + (module.project.project_id) = [ + "roles/logging.logWriter", + "roles/monitoring.metricWriter", + "roles/storage.admin" + ] + } +} + +module "gitlab-instance" { + source = "../../../modules/compute-vm" + project_id = module.project.project_id + zone = var.gitlab_instance_config.zone + name = var.gitlab_instance_config.name + instance_type = var.gitlab_instance_config.instance_type + boot_disk = { + initialize_params = { + image = "projects/cos-cloud/global/images/family/cos-stable" + size = var.gitlab_instance_config.boot_disk.size + type = var.gitlab_instance_config.boot_disk.type + } + } + attached_disks = [ + { + name = "data" + size = var.gitlab_instance_config.data_disk.size + type = var.gitlab_instance_config.data_disk.type + options = { + replica_zone = var.gitlab_instance_config.replica_zone + } + } + ] + network_interfaces = [ + { + network = var.network_config.network_self_link + subnetwork = var.network_config.subnet_self_link + } + ] + tags = var.gitlab_instance_config.network_tags + metadata = { + user-data = local.gitlab_user_data + google-logging-enabled = "true" + } + service_account = { + email = module.gitlab-sa.email + } +} + +module "ilb" { + source = "../../../modules/net-lb-int" + project_id = module.project.project_id + region = var.region + name = "ilb" + service_label = "ilb" + vpc_config = { + network = var.network_config.network_self_link + subnetwork = var.network_config.subnet_self_link + } + group_configs = { + gitlab = { + zone = var.gitlab_instance_config.zone + instances = [ + module.gitlab-instance.self_link + ] + } + } + backends = [ + { group = module.ilb.groups.gitlab.self_link } + ] + health_check_config = { + https = { + port = 443 + } + } +} diff --git a/blueprints/third-party-solutions/gitlab/main.tf b/blueprints/third-party-solutions/gitlab/main.tf new file mode 100644 index 00000000..054ea8fc --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/main.tf @@ -0,0 +1,44 @@ +/** + * Copyright 2024 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. + */ + +module "project" { + source = "../../../modules/project" + parent = try(var.project_create.parent, null) + billing_account = try(var.project_create.billing_account_id, null) + prefix = var.project_create == null ? null : var.prefix + name = var.project_id + project_create = var.project_create != null + services = [ + "compute.googleapis.com", + "memcache.googleapis.com", + "redis.googleapis.com", + "sqladmin.googleapis.com", + "sql-component.googleapis.com", + "stackdriver.googleapis.com", + "dns.googleapis.com", + "iam.googleapis.com", + ] + shared_vpc_service_config = { + attach = true + host_project = var.network_config.host_project + service_identity_iam = { + "roles/compute.networkUser" = [ + "cloudservices", "compute" + ] + } + network_users = var.admin_principals + } +} diff --git a/blueprints/third-party-solutions/gitlab/outputs.tf b/blueprints/third-party-solutions/gitlab/outputs.tf new file mode 100644 index 00000000..e578f0fc --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/outputs.tf @@ -0,0 +1,56 @@ +/** + * Copyright 2024 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 { + ssl_certs = { + "${var.gitlab_config.hostname}.crt" = local.gitlab_ssl_crt + "${var.gitlab_config.hostname}.key" = local.gitlab_ssl_key, + "${var.gitlab_config.hostname}.ca.crt" = local.gitlab_ssl_ca_crt, + "${var.gitlab_config.hostname}.ca.key" = local.gitlab_ssl_ca_key + } +} + +output "gitlab_ilb_ip" { + description = "Gitlab Internal Load Balancer IP Address." + value = module.ilb.forwarding_rule_addresses[""] +} + +output "instance" { + description = "Gitlab compute engine instance." + value = module.gitlab-instance.instance +} + +output "postgresql_users" { + description = "Gitlab postgres user password." + sensitive = true + value = module.db.user_passwords +} + +output "project" { + description = "GCP project." + value = module.project +} + +output "ssh_to_gitlab" { + description = "gcloud command to ssh gitlab instance." + value = nonsensitive("gcloud compute ssh ${module.gitlab-instance.instance.name} --project ${module.project.project_id} --zone ${module.gitlab-instance.instance.zone} -- -L 8080:127.0.0.1:80 -L 2222:127.0.0.1:2222 -L 8443:127.0.0.1:443 -N -q -f") +} + +output "ssl_certs" { + description = "Gitlab SSL Certificates." + value = local.ssl_certs + sensitive = true +} diff --git a/blueprints/third-party-solutions/gitlab/services.tf b/blueprints/third-party-solutions/gitlab/services.tf new file mode 100644 index 00000000..b5168be9 --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/services.tf @@ -0,0 +1,92 @@ +/** + * Copyright 2024 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 { + gitlab_buckets = [ + "gitlab-artifacts", "gitlab-mr-diffs", "gitlab-lfs", "gitlab-uploads", + "gitlab-packages", "gitlab-dependency-proxy", "gitlab-terraform-state", + "gitlab-pages" + ] +} + +####################################################################### +# GITLAB MANAGED SERVICES # +####################################################################### + +# https://docs.gitlab.com/ee/install/requirements.html#database +module "db" { + source = "../../../modules/cloudsql-instance" + project_id = module.project.project_id + region = var.region + name = var.cloudsql_config.name + availability_type = var.gitlab_config.ha_required ? "REGIONAL" : "ZONAL" + network_config = { + authorized_networks = {} + connectivity = { + psa_config = { + private_network = var.network_config.network_self_link + } + } + } + database_version = var.cloudsql_config.database_version + databases = [ + "gitlabhq_production" + ] + tier = var.cloudsql_config.tier + users = { + # generate password for user1 + gitlab = { + password = null + type = "BUILT_IN" + } + } +} + +# https://docs.gitlab.com/ee/install/requirements.html#redis +resource "google_redis_instance" "cache" { + project = module.project.project_id + region = var.region + name = var.redis_config.name + tier = var.redis_config.tier + memory_size_gb = var.redis_config.memory_size_gb + authorized_network = var.network_config.network_self_link + connect_mode = "PRIVATE_SERVICE_ACCESS" + + redis_version = var.redis_config.version + display_name = "Gitlab Redis Instance" + persistence_config { + persistence_mode = var.redis_config.persistence_mode + rdb_snapshot_period = var.redis_config.rdb_snapshot_period + } +} + +# https://docs.gitlab.com/ee/administration/object_storage.html#google-cloud-storage-gcs +module "gitlab_object_storage" { + source = "../../../modules/gcs" + for_each = toset(local.gitlab_buckets) + project_id = module.project.project_id + prefix = var.prefix + name = each.key + storage_class = var.gcs_config.storage_class + location = var.gcs_config.location + versioning = var.gcs_config.enable_versioning + iam = { + "roles/storage.objectUser" = [ + "serviceAccount:${module.gitlab-sa.email}", + ] + } +} diff --git a/blueprints/third-party-solutions/gitlab/ssl.tf b/blueprints/third-party-solutions/gitlab/ssl.tf new file mode 100644 index 00000000..96dd7e3d --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/ssl.tf @@ -0,0 +1,107 @@ +/** + * Copyright 2024 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 { + cert_subjects = [ + { + country = "IT" + province = "Lombardy" + locality = "Milan" + organization = "Example" + organizational_unit = "Example" + } + ] +} + +####################################################################### +# GITLAB CA PRIVATE KEY # +####################################################################### + +resource "tls_private_key" "gitlab_ca_private_key" { + count = local.self_signed_ssl_certs_required ? 1 : 0 + algorithm = "RSA" +} + +####################################################################### +# GITLAB CA CERT # +####################################################################### + +resource "tls_self_signed_cert" "gitlab_ca_cert" { + count = local.self_signed_ssl_certs_required ? 1 : 0 + private_key_pem = tls_private_key.gitlab_ca_private_key.0.private_key_pem + is_ca_certificate = true + dynamic "subject" { + for_each = toset(local.cert_subjects) + content { + country = subject.value.country + province = subject.value.province + locality = subject.value.locality + common_name = "Gitlab CA" + organization = subject.value.organization + organizational_unit = subject.value.organizational_unit + } + } + validity_period_hours = 43800 // 1825 days or 5 years + allowed_uses = [ + "digital_signature", + "cert_signing", + "crl_signing", + ] +} + +####################################################################### +# SERVER CERT SIGNED BY CA # +####################################################################### + +resource "tls_private_key" "gitlab_server_key" { + count = local.self_signed_ssl_certs_required ? 1 : 0 + algorithm = "RSA" +} + +# Create CSR for Gitlab Server certificate +resource "tls_cert_request" "gitlab_server_csr" { + count = local.self_signed_ssl_certs_required ? 1 : 0 + private_key_pem = tls_private_key.gitlab_server_key.0.private_key_pem + dns_names = [var.gitlab_config.hostname] + + dynamic "subject" { + for_each = toset(local.cert_subjects) + content { + country = subject.value.country + province = subject.value.province + locality = subject.value.locality + common_name = "Gitlab" + organization = subject.value.organization + organizational_unit = subject.value.organizational_unit + } + } +} + +resource "tls_locally_signed_cert" "gitlab_server_singed_cert" { + count = local.self_signed_ssl_certs_required ? 1 : 0 + cert_request_pem = tls_cert_request.gitlab_server_csr.0.cert_request_pem + ca_private_key_pem = tls_private_key.gitlab_ca_private_key.0.private_key_pem + ca_cert_pem = tls_self_signed_cert.gitlab_ca_cert.0.cert_pem + + validity_period_hours = 43800 + + allowed_uses = [ + "digital_signature", + "key_encipherment", + "server_auth", + "client_auth", + ] +} diff --git a/blueprints/third-party-solutions/gitlab/terraform.tfvars.sample b/blueprints/third-party-solutions/gitlab/terraform.tfvars.sample new file mode 100644 index 00000000..e76642af --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/terraform.tfvars.sample @@ -0,0 +1,19 @@ +gitlab_config = { + hostname = "gitlab.gcp.example.com" + mail = { + sendgrid = { + api_key = "sample_api_key" + } + } + saml = { + idp_cert_fingerprint = "67:90:96.....REPLACE_ME" + sso_target_url = "https://accounts.google.com/o/saml2/idp?idpid=REPLACE_ME" + } +} +network_config = { + host_project = "host-project" + network_self_link = "network_self_link" + subnet_self_link = "subnetwork_self_link" +} +prefix = "prefix" +project_id = "prod-gitlab-0" \ No newline at end of file diff --git a/blueprints/third-party-solutions/gitlab/variables.tf b/blueprints/third-party-solutions/gitlab/variables.tf new file mode 100644 index 00000000..7d6d2445 --- /dev/null +++ b/blueprints/third-party-solutions/gitlab/variables.tf @@ -0,0 +1,139 @@ +/** + * Copyright 2024 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 "admin_principals" { + description = "Users, groups and/or service accounts that are assigned roles, in IAM format (`group:foo@example.com`)." + type = list(string) + default = [] +} + +variable "cloudsql_config" { + description = "Cloud SQL Postgres config." + type = object({ + name = optional(string, "gitlab-0") + database_version = optional(string, "POSTGRES_13") + tier = optional(string, "db-custom-2-8192") + }) + default = {} + nullable = false +} + +variable "gcs_config" { + description = "GCS for Object Storage config." + type = object({ + enable_versioning = optional(bool, false) + location = optional(string, "EU") + storage_class = optional(string, "STANDARD") + }) + default = {} + nullable = false +} + +variable "gitlab_config" { + description = "Gitlab configuration." + type = object({ + hostname = optional(string, "gitlab.gcp.example.com") + mail = optional(object({ + enabled = optional(bool, false) + sendgrid = optional(object({ + api_key = optional(string) + email_from = optional(string, null) + email_reply_to = optional(string, null) + }), null) + }), {}) + saml = optional(object({ + forced = optional(bool, false) + idp_cert_fingerprint = string + sso_target_url = string + name_identifier_format = optional(string, "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress") + }), null) + ha_required = optional(bool, false) + }) + default = {} + nullable = false +} + +variable "gitlab_instance_config" { + description = "Gitlab Compute Engine instance config." + type = object({ + instance_type = optional(string, "n1-highcpu-8") + name = optional(string, "gitlab-0") + network_tags = optional(list(string), []) + replica_zone = optional(string) + zone = optional(string) + boot_disk = optional(object({ + size = optional(number, 20) + type = optional(string, "pd-standard") + }), {}) + data_disk = optional(object({ + size = optional(number, 100) + type = optional(string, "pd-ssd") + replica_zone = optional(string) + }), {}) + }) +} + +variable "network_config" { + description = "Shared VPC network configurations to use for Gitlab Runner VM." + type = object({ + host_project = optional(string) + network_self_link = string + subnet_self_link = string + }) +} + +variable "prefix" { + description = "Prefix used for resource names." + type = string + nullable = false + validation { + condition = var.prefix != "" + error_message = "Prefix cannot be empty." + } +} + +variable "project_create" { + description = "Provide values if project creation is needed, uses existing project if null. Parent is in 'folders/nnn' or 'organizations/nnn' format." + type = object({ + billing_account_id = string + parent = string + }) + default = null +} + +variable "project_id" { + description = "Project id, references existing project if `project_create` is null." + type = string +} + +variable "redis_config" { + description = "Redis Config." + type = object({ + memory_size_gb = optional(number, 1) + name = optional(string, "gitlab-0") + persistence_mode = optional(string, "RDB") + rdb_snapshot_period = optional(string, "TWELVE_HOURS") + tier = optional(string, "BASIC") + version = optional(string, "REDIS_6_X") + }) + default = {} + nullable = false +} + +variable "region" { + description = "GCP Region." + type = string +}