Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions .github/workflows/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,14 @@ jobs:
TF_VAR_email: ${{ secrets.EMAIL }}
TF_VAR_do_token: ${{ secrets.DO_TOKEN }}
TF_VAR_do_dns_token: ${{ secrets.DO_DNS_TOKEN }}
TF_VAR_spaces_access_id: ${{ secrets.SPACES_ACCESS_ID }}
TF_VAR_spaces_secret_key: ${{ secrets.SPACES_SECRET_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.SPACES_ACCESS_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.SPACES_SECRET_KEY }}
TF_VAR_cloudflare_account_id: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
TF_VAR_cloudflare_api_token: ${{ secrets.CLOUDFLARE_API_TOKEN }}
TF_VAR_cloudflare_r2_endpoint: ${{ secrets.R2_ENDPOINT }}
TF_VAR_cloudflare_r2_access_key_id: ${{ secrets.R2_ACCESS_KEY_ID }}
TF_VAR_cloudflare_r2_secret_access_key: ${{ secrets.R2_SECRET_ACCESS_KEY }}
AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
TF_VAR_sendgrid_api_key: ${{ secrets.SENDGRID_API_KEY }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if: github.ref == 'refs/heads/main'
needs: validate
steps:
Expand Down
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.env

TODO.md

# Terraform

Expand Down
18 changes: 18 additions & 0 deletions helm/gitlab/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,24 @@ global:
minio:
enabled: false

email:
display_name: GitLab
from: gitlab@${domain}
reply_to: noreply@${domain}

smtp:
enabled: true
domain: smtp.sendgrid.net
address: smtp.sendgrid.net
port: 587
user_name: apikey
password:
secret: gitlab-sendgrid-secret
key: password
tls: false
starttls_auto: true
openssl_verify_mode: peer

time_zone: UTC

extraEnv:
Expand Down
5 changes: 3 additions & 2 deletions terraform/helm.tf
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ resource "helm_release" "gitlab" {
postgres_username = digitalocean_database_cluster.postgres.user
redis_host = digitalocean_database_cluster.valkey.private_host
redis_port = digitalocean_database_cluster.valkey.port
buckets = {for key, bucket in digitalocean_spaces_bucket.gitlab : key => bucket.name}
buckets = {for key, bucket in cloudflare_r2_bucket.gitlab : key => bucket.name}
})
]

Expand All @@ -121,7 +121,8 @@ resource "helm_release" "gitlab" {
kubernetes_secret_v1.gitlab_initial_root_password,
kubernetes_secret_v1.gitlab_postgres,
kubernetes_secret_v1.gitlab_redis,
kubernetes_secret_v1.gitlab_s3_main
kubernetes_secret_v1.gitlab_s3_main,
kubernetes_secret_v1.gitlab_sendgrid_secret
]
}

Expand Down
27 changes: 20 additions & 7 deletions terraform/kubernetes.tf
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ resource "kubernetes_secret_v1" "gitlab_s3_main" {
connection = yamlencode({
provider = "AWS"
region = var.region
endpoint = "https://${var.region}.digitaloceanspaces.com"
aws_access_key_id = var.spaces_access_id
aws_secret_access_key = var.spaces_secret_key
endpoint = var.cloudflare_r2_endpoint
aws_access_key_id = var.cloudflare_account_id
aws_secret_access_key = var.cloudflare_api_token
path_style = true
})
}
Expand All @@ -125,7 +125,7 @@ resource "kubernetes_secret_v1" "gitlab_s3_main" {
# accesskey = var.spaces_access_id
# secretkey = var.spaces_secret_key
# region = var.region
# regionendpoint = "https://${var.region}.digitaloceanspaces.com"
# regionendpoint = ${{ secrets.R2_ACCESS_KEY_ID }}
# bucket = digitalocean_spaces_bucket.gitlab["registry"].name
# })
# }
Expand All @@ -143,16 +143,29 @@ resource "kubernetes_secret_v1" "gitlab_s3_backup" {
connection = yamlencode({
provider = "AWS"
region = var.region
endpoint = "https://${var.region}.digitaloceanspaces.com"
aws_access_key_id = var.spaces_access_id
aws_secret_access_key = var.spaces_secret_key
endpoint = var.cloudflare_r2_endpoint
aws_access_key_id = var.cloudflare_account_id
aws_secret_access_key = var.cloudflare_api_token
path_style = true
})
}

type = "Opaque"
}

resource "kubernetes_secret_v1" "gitlab_sendgrid_secret" {
metadata {
name = "gitlab-sendgrid-secret"
namespace = kubernetes_namespace_v1.gitlab.metadata[0].name
}

data = {
password = var.sendgrid_api_key
}

type = "Opaque"
}

resource "time_sleep" "wait_for_lb" {
depends_on = [ helm_release.ingress_nginx ]
create_duration = "120s"
Expand Down
8 changes: 2 additions & 6 deletions terraform/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,6 @@ output "valkey_password" {
}


output "spaces_endpoint" {
value = "${var.region}.digitaloceanspaces.com"
}

output "spaces_buckets" {
value = { for k, b in digitalocean_spaces_bucket.gitlab : k => b.name }
output "r2_buckets" {
value = { for k, b in cloudflare_r2_bucket.gitlab : k => b.name }
}
6 changes: 4 additions & 2 deletions terraform/providers.tf
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
provider "digitalocean" {
token = var.do_token
spaces_access_id = var.spaces_access_id
spaces_secret_key = var.spaces_secret_key
}

provider "cloudflare" {
api_token = var.cloudflare_api_token
}

provider "kubernetes" {
Expand Down
21 changes: 21 additions & 0 deletions terraform/s3.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
locals {
buckets = toset([
"artifacts", "lfs", "uploads", "packages",
"registry", "pages", "backups", "tmp", "ci-secure-files",
"dependency-proxy", "terraform-state"
])
}

resource "random_id" "suffix" {
byte_length = 3
}
Comment on lines +9 to +11
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

keepers + prevent_destroy on random_id is contradictory.

If var.cluster_name ever changes, keepers will mark this resource for replacement, but prevent_destroy = true will then abort the plan with an error. If the goal is to lock the suffix permanently, drop keepers. If the goal is to rotate it with cluster name changes, drop prevent_destroy.


resource "cloudflare_r2_bucket" "gitlab" {
for_each = local.buckets
account_id = var.cloudflare_account_id
name = "${var.cluster_name}-${each.key}-${random_id.suffix.hex}"
jurisdiction = var.r2_jurisdiction
lifecycle {
prevent_destroy = true
}
}
Comment on lines +13 to +21
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Bucket destroy/rename risk and naming-length concerns.

  1. cloudflare_r2_bucket.gitlab lacks lifecycle { prevent_destroy = true } even though random_id.suffix has it. Any change to var.cluster_name, each.key, or var.r2_jurisdiction recreates the bucket — R2 cannot rename, so this means destroy + recreate and data loss. Add prevent_destroy here too, given these buckets back GitLab artifacts/registry/backups.
  2. R2 bucket names must be 3–63 chars, lowercase alphanumerics + hyphens. With keys like ci-secure-files (15) and dependency-proxy (16) plus 6 hex chars and two dashes, cluster_name is effectively capped at ~40 chars. Consider validating cluster_name length, or hashing instead of concatenating.
🔧 Proposed fix
 resource "cloudflare_r2_bucket" "gitlab" {
     for_each = local.buckets
     account_id = var.cloudflare_account_id
     name = "${var.cluster_name}-${each.key}-${random_id.suffix.hex}"
     jurisdiction = var.r2_jurisdiction
+
+    lifecycle {
+        prevent_destroy = true
+    }
 }

18 changes: 0 additions & 18 deletions terraform/spaces.tf

This file was deleted.

29 changes: 27 additions & 2 deletions terraform/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,32 @@ variable "do_dns_token" {
sensitive = true
}

variable "spaces_access_id" {
variable "cloudflare_account_id" {
type = string
sensitive = true
}

variable "spaces_secret_key" {
variable "cloudflare_api_token" {
type = string
sensitive = true
}

variable "cloudflare_r2_endpoint" {
type = string
sensitive = true
}

variable "cloudflare_r2_access_key_id" {
type = string
sensitive = true
}

variable "cloudflare_r2_secret_access_key" {
type = string
sensitive = true
}

variable "sendgrid_api_key" {
type = string
sensitive = true
}
Expand All @@ -24,6 +44,11 @@ variable "region" {
default = "ams3"
}

variable "r2_jurisdiction" {
type = string
default = "eu"
}
Comment on lines +47 to +50
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🌐 Web query:

Cloudflare R2 supported jurisdictions list (default, eu, fedramp)

💡 Result:

Cloudflare R2 supports three jurisdictions for data storage: default (no restriction, data stored globally), eu (European Union), and fedramp (FedRAMP, requires Enterprise customers).

Citations:


Add validation block to constrain r2_jurisdiction to supported values.

Cloudflare R2 only accepts three jurisdictions: default, eu, and fedramp. Without a validation block, an invalid value will only fail at terraform apply time with an API error. Adding validation catches misconfiguration earlier.

♻️ Proposed validation
 variable "r2_jurisdiction" {
     type = string
     default = "eu"
+    validation {
+      condition     = contains(["default", "eu", "fedramp"], var.r2_jurisdiction)
+      error_message = "r2_jurisdiction must be one of: default, eu, fedramp."
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
variable "r2_jurisdiction" {
type = string
default = "eu"
}
variable "r2_jurisdiction" {
type = string
default = "eu"
validation {
condition = contains(["default", "eu", "fedramp"], var.r2_jurisdiction)
error_message = "r2_jurisdiction must be one of: default, eu, fedramp."
}
}


variable "cluster_name" {
type = string
default = "gitlab"
Expand Down
4 changes: 4 additions & 0 deletions terraform/versions.tf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ terraform {
source = "digitalocean/digitalocean"
version = "~> 2.81.0"
}
cloudflare = {
source = "cloudflare/cloudflare"
version = "~> 5.19.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 3.0.1"
Expand Down