this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(infra)!: use Cloudflare R2 as tfstate backend

Khue Doan 58a78768 7950d854

+250 -39
+1
Makefile
··· 38 38 terragrunt hcl format 39 39 tofu fmt -recursive 40 40 cd controller && go fmt ./... 41 + cd infra/modules/tfstate && go fmt ./... 41 42 cd test/e2e && go fmt ./... 42 43 43 44 update:
+13
infra/modules/tfstate/go.mod
··· 1 + module tfstate 2 + 3 + go 1.24.3 4 + 5 + require github.com/cloudflare/cloudflare-go v0.115.0 6 + 7 + require ( 8 + github.com/goccy/go-json v0.10.5 // indirect 9 + github.com/google/go-querystring v1.1.0 // indirect 10 + golang.org/x/net v0.34.0 // indirect 11 + golang.org/x/text v0.21.0 // indirect 12 + golang.org/x/time v0.9.0 // indirect 13 + )
+24
infra/modules/tfstate/go.sum
··· 1 + github.com/cloudflare/cloudflare-go v0.115.0 h1:84/dxeeXweCc0PN5Cto44iTA8AkG1fyT11yPO5ZB7sM= 2 + github.com/cloudflare/cloudflare-go v0.115.0/go.mod h1:Ds6urDwn/TF2uIU24mu7H91xkKP8gSAHxQ44DSZgVmU= 3 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 4 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 + github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= 6 + github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= 7 + github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 8 + github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 9 + github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 10 + github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= 11 + github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= 12 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 13 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 14 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 15 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 16 + golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= 17 + golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= 18 + golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 19 + golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 20 + golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 21 + golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 22 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 23 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 24 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+48
infra/modules/tfstate/main.go
··· 1 + package main 2 + 3 + import ( 4 + "context" 5 + "flag" 6 + "log" 7 + 8 + "github.com/cloudflare/cloudflare-go" 9 + ) 10 + 11 + func main() { 12 + var apiToken, accountID, bucket string 13 + flag.StringVar(&apiToken, "api-token", "", "Cloudflare account ID") 14 + flag.StringVar(&accountID, "account-id", "", "Cloudflare account ID") 15 + flag.StringVar(&bucket, "bucket", "", "Cloudflare R2 bucket name") 16 + flag.Parse() 17 + 18 + if apiToken == "" || accountID == "" || bucket == "" { 19 + log.Fatal("--api-token, --account-id and --bucket must be provided") 20 + } 21 + 22 + resourceContainer := cloudflare.AccountIdentifier(accountID) 23 + ctx := context.Background() 24 + 25 + api, err := cloudflare.NewWithAPIToken(apiToken) 26 + if err != nil { 27 + log.Fatalf("failed to create Cloudflare API client: %v", err) 28 + } 29 + 30 + existingBucket, err := api.GetR2Bucket(ctx, resourceContainer, bucket) 31 + if err == nil { 32 + log.Printf("bucket %q already exists: %+v\n", bucket, existingBucket) 33 + return 34 + } 35 + 36 + createdBucket, err := api.CreateR2Bucket( 37 + ctx, 38 + resourceContainer, 39 + cloudflare.CreateR2BucketParameters{ 40 + Name: bucket, 41 + }, 42 + ) 43 + if err != nil { 44 + log.Fatalf("failed to create bucket: %v", err) 45 + } 46 + 47 + log.Printf("created bucket: %+v\n", createdBucket) 48 + }
+24
infra/production/metal/vn-south-1/bootstrap/.terraform.lock.hcl
··· 1 + # This file is maintained automatically by "tofu init". 2 + # Manual edits may be lost in future updates. 3 + 4 + provider "registry.opentofu.org/hashicorp/oci" { 5 + version = "7.6.0" 6 + hashes = [ 7 + "h1:fvwp3c4OPhuKW9gBVW8eInqBE/lXZx/gLFEZRUsHuYE=", 8 + "zh:07b8c1b8ddbd9cc62296f8573a8f89d282978b0cf3046dd354028811ac371cc6", 9 + "zh:17b42ca6aee04b3c903366d029311e7ffbea630882a93d418fbb364a3d91d8bf", 10 + "zh:1fb9c7841fb556e4d580dc41d906efd40c0d1d7659275b4bb5929f331ee1fea2", 11 + "zh:20325dbb45c495baa0b885dac1c79992998b98415260317e8ca3a45c5d0a7561", 12 + "zh:216a5eb1a5ebcb61feb9bd1b2551d9a0ccbde35857d4bb66eeba7ef240ac8ba3", 13 + "zh:4adedb1709478fb9778cf95d6ba649feb7d508380957c97edcdf5e8ff3f686c8", 14 + "zh:62b6c705523ceff1b55ab8863e4ec80829b15e4890a9e9d8f0ed4dcb73630a97", 15 + "zh:7aec9c64f308cf3eb9d85ebd2dc2518c97ef71c86c75bb64507c0c22f8dd6c01", 16 + "zh:8131877e0ec3ec868b09d3e3e4d6e0b7706989e6bd30ed4aeba3695f6de04888", 17 + "zh:9978257ee7fd7a0ea090e1c8d085e93687e43d0a7607271bc8011c282b7160a4", 18 + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 19 + "zh:b51e7e20316c0181025628e32daf1f6760d42dd53f9f9de135a41ad893a1d1c7", 20 + "zh:c9aaafa914f161587dd3ab3b9b0a867da303f8c41256dfe795414364830d6180", 21 + "zh:f3db696340ea890cb41adf9bdae19e6b0132bc29a731260145cbf64f56831a18", 22 + "zh:fea0c8888a0e2b8d3523f6888a7faa3aa13df79d954089d776db4f9a96d2215f", 23 + ] 24 + }
+16
infra/production/metal/vn-south-1/bootstrap/terragrunt.hcl
··· 1 + include "root" { 2 + path = find_in_parent_folders("root.hcl") 3 + expose = true 4 + } 5 + 6 + dependency "cluster" { 7 + config_path = "../cluster" 8 + 9 + mock_outputs = {} 10 + } 11 + 12 + terraform { 13 + source = "../../../../modules//empty" 14 + } 15 + 16 + inputs = {}
+24
infra/production/metal/vn-south-1/cluster/.terraform.lock.hcl
··· 1 + # This file is maintained automatically by "tofu init". 2 + # Manual edits may be lost in future updates. 3 + 4 + provider "registry.opentofu.org/hashicorp/oci" { 5 + version = "7.6.0" 6 + hashes = [ 7 + "h1:fvwp3c4OPhuKW9gBVW8eInqBE/lXZx/gLFEZRUsHuYE=", 8 + "zh:07b8c1b8ddbd9cc62296f8573a8f89d282978b0cf3046dd354028811ac371cc6", 9 + "zh:17b42ca6aee04b3c903366d029311e7ffbea630882a93d418fbb364a3d91d8bf", 10 + "zh:1fb9c7841fb556e4d580dc41d906efd40c0d1d7659275b4bb5929f331ee1fea2", 11 + "zh:20325dbb45c495baa0b885dac1c79992998b98415260317e8ca3a45c5d0a7561", 12 + "zh:216a5eb1a5ebcb61feb9bd1b2551d9a0ccbde35857d4bb66eeba7ef240ac8ba3", 13 + "zh:4adedb1709478fb9778cf95d6ba649feb7d508380957c97edcdf5e8ff3f686c8", 14 + "zh:62b6c705523ceff1b55ab8863e4ec80829b15e4890a9e9d8f0ed4dcb73630a97", 15 + "zh:7aec9c64f308cf3eb9d85ebd2dc2518c97ef71c86c75bb64507c0c22f8dd6c01", 16 + "zh:8131877e0ec3ec868b09d3e3e4d6e0b7706989e6bd30ed4aeba3695f6de04888", 17 + "zh:9978257ee7fd7a0ea090e1c8d085e93687e43d0a7607271bc8011c282b7160a4", 18 + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 19 + "zh:b51e7e20316c0181025628e32daf1f6760d42dd53f9f9de135a41ad893a1d1c7", 20 + "zh:c9aaafa914f161587dd3ab3b9b0a867da303f8c41256dfe795414364830d6180", 21 + "zh:f3db696340ea890cb41adf9bdae19e6b0132bc29a731260145cbf64f56831a18", 22 + "zh:fea0c8888a0e2b8d3523f6888a7faa3aa13df79d954089d776db4f9a96d2215f", 23 + ] 24 + }
+10
infra/production/metal/vn-south-1/cluster/terragrunt.hcl
··· 1 + include "root" { 2 + path = find_in_parent_folders("root.hcl") 3 + expose = true 4 + } 5 + 6 + terraform { 7 + source = "../../../../modules//empty" 8 + } 9 + 10 + inputs = {}
infra/production/metal/vn-south-1/terragrunt.stack.hcl infra/modules/empty/variables.tf
-37
infra/production/oracle/root.hcl
··· 1 - locals { 2 - secrets = yamldecode(sops_decrypt_file(find_in_parent_folders("secrets.yaml"))) 3 - } 4 - 5 - # TODO split into multiple modules, and use a more flexible state backend 6 - generate "backend" { 7 - path = "backend.tf" 8 - if_exists = "overwrite_terragrunt" 9 - contents = <<EOF 10 - terraform { 11 - backend "remote" { 12 - hostname = "app.terraform.io" 13 - organization = "khuedoan" 14 - 15 - workspaces { 16 - name = "cloudlab" 17 - } 18 - } 19 - } 20 - EOF 21 - } 22 - 23 - generate "provider" { 24 - path = "provider.tf" 25 - if_exists = "overwrite_terragrunt" 26 - contents = <<EOF 27 - provider "oci" { 28 - tenancy_ocid = "${local.secrets.oracle_tenancy_ocid}" 29 - user_ocid = "${local.secrets.oracle_user_ocid}" 30 - fingerprint = "${local.secrets.oracle_fingerprint}" 31 - private_key = <<EOT 32 - ${local.secrets.oracle_private_key} 33 - EOT 34 - region = "${local.secrets.oracle_region}" 35 - } 36 - EOF 37 - }
+42
infra/production/root.hcl
··· 1 + locals { 2 + secrets = yamldecode(sops_decrypt_file(find_in_parent_folders("secrets.yaml"))) 3 + env = "production" 4 + } 5 + 6 + generate "backend" { 7 + path = "backend.tf" 8 + if_exists = "overwrite_terragrunt" 9 + contents = <<EOF 10 + terraform { 11 + backend "s3" { 12 + bucket = "tfstate-${local.env}" 13 + key = "${path_relative_to_include()}/tfstate.json" 14 + region = "auto" 15 + skip_credentials_validation = true 16 + skip_metadata_api_check = true 17 + skip_region_validation = true 18 + skip_requesting_account_id = true 19 + skip_s3_checksum = true 20 + use_path_style = true 21 + access_key = "${local.secrets.cloudflare_tfstate_access_key}" 22 + secret_key = "${local.secrets.cloudflare_tfstate_secret_key}" 23 + endpoints = { s3 = "https://${local.secrets.cloudflare_account_id}.r2.cloudflarestorage.com" } 24 + } 25 + } 26 + EOF 27 + } 28 + generate "provider" { 29 + path = "provider.tf" 30 + if_exists = "overwrite_terragrunt" 31 + contents = <<EOF 32 + provider "oci" { 33 + tenancy_ocid = "${local.secrets.oracle_tenancy_ocid}" 34 + user_ocid = "${local.secrets.oracle_user_ocid}" 35 + fingerprint = "${local.secrets.oracle_fingerprint}" 36 + private_key = <<EOT 37 + ${local.secrets.oracle_private_key} 38 + EOT 39 + region = "${local.secrets.oracle_region}" 40 + } 41 + EOF 42 + }
+6 -2
infra/production/secrets.yaml
··· 1 + cloudflare_tfstate_api_token: ENC[AES256_GCM,data:DxXUl9P0eQOUIIQnAG1iuxqekhZR06ZKuZTF/GvkTortOizuHmyOgA==,iv:SGinOE7FE319xKdeGL91w6Kvq+ou6qYJzW9ygrEt+Vs=,tag:7YTob3qeQN8X9J0peXfP5g==,type:str] 2 + cloudflare_tfstate_access_key: ENC[AES256_GCM,data:h08FEnfnt4/tmPJQUQpVj7xHEk98w4+Jf5xgU5H8qzk=,iv:lKWaurdpfBqNRknrLj8VLkQB1O0Jkr7RX+ps87y6WYA=,tag:QrdTN3AT3pWtLfGdIhNcZg==,type:str] 3 + cloudflare_tfstate_secret_key: ENC[AES256_GCM,data:YLbjJ2OQp4QCwRmATKVUQ+sM/qYjJpyoOv/LYsVrTOgHPB1M5/W7e2+jpJTUTezsWTaQ8HKLKaMzH2/Vp0zTGA==,iv:IhT0arMtAy+4nvMVbDxVVflmntAM7d2msashoIu8/do=,tag:QTV+zc7Ve1GToIeJ2AYQNA==,type:str] 4 + cloudflare_account_id: ENC[AES256_GCM,data:zj6mfdohnYRlptkdPThaOVkUV9cSQn0Q9Lq9m4T+e9g=,iv:orAlN13bR1Vni7aZ3iNWx5tfjODX/oKrhZijSnAWu4Y=,tag:Qsq6K3K2uDBeossojArCxg==,type:str] 1 5 oracle_tenancy_ocid: ENC[AES256_GCM,data:DQPku1J4WEppuhxP+gFcf7LZ3jXAdLjgr6X3RpKYSM/aF2cnsKqr56CObLLtUje4C2mmgyoFQebCfSuhjV+2+jj9PdecUBeka161zW20xA==,iv:Kr1yfvzjnYopcU4LrTEkLbrL5BWnCAmpoy9YFe39IPM=,tag:xFmBeYRoj9KqmuuzFIAzpw==,type:str] 2 6 oracle_user_ocid: ENC[AES256_GCM,data:GRDrBHRz/W3AEt/zixn6n+ueqSNDtZFB4EBto2TQo9Rik6vWr2kSbwq2UYX9bYCPMlLQrTrT/L2tv8Ln0KJIC8O5zuLLAN+LkBLYZQ==,iv:L7YU03HE6aQTjczSz/GZH3YFL3sgwPyomoavRvRuqso=,tag:86evG2ijpy2b7/v9rkg6PQ==,type:str] 3 7 oracle_fingerprint: ENC[AES256_GCM,data:HL7NqWBTJ0f9hXfA9HyyMDErLMDbmK5QsyMKr2Kg+w8Qo9h1891+KuhGpe7K+SQ=,iv:vBak+FMghPYeuDbtfzMyEeEPfdQtbvpUXEVJGfzqslY=,tag:C057JRlVzWhbOoVUee82aw==,type:str] ··· 15 19 bzdTeVZWYVIwM1VyUm5UM0pQZW5KYlUKWw7Fq17anbkfY6U3HCr00OKEfffRL/TM 16 20 zvtvDaWLg2ZilP7kWM7YbqyTi4XqjLXKldOblwIcF7qA5E/OfbbzXw== 17 21 -----END AGE ENCRYPTED FILE----- 18 - lastmodified: "2025-06-12T10:24:24Z" 19 - mac: ENC[AES256_GCM,data:5v7fpxak56kw+PUmvJZnMfzNzVn7pBLHJWqGr7RGg3tTF7k2HW9SAUAnLr3n/tB1QrE7N9ljZ8YoUOSn6qbcrj0FyF+SmWgUH+/M+SHzBPqROSn8VyqMX4lv+4p3KDJOCGa9l3LUUBzwKs6uu42FG5nHrB7iG/YHz44PqoL5ldE=,iv:5OZGUwKslfqM7i1XDMYh12uttuqzlgHNVO1r2hsn+fg=,tag:IiZawvODPvTzgTfwbZT4vw==,type:str] 22 + lastmodified: "2025-06-20T18:37:49Z" 23 + mac: ENC[AES256_GCM,data:5JnlwvHoz4yEr8UTFSNzlXLZ2LAXaz5vcGpzfeZGSta2sDbr3Rtt6GZ/GuyksAwZTCu19O1eUhu/2ewHa63Ls6M7TYtXB1T0RIQmT+L2MLImr4QLbdJHIne0MPd76mB3LYpfPU7bhx+drPiUfuflAuJa80V/tNZR1j40qSVwryU=,iv:YcCoJ3kJApIeM0Wjf1yBg8wQOga/nygDX47G6XTwffA=,tag:KQJLV9uYVn+o8UbmZ29V+w==,type:str] 20 24 unencrypted_suffix: _unencrypted 21 25 version: 3.10.2
+24
infra/production/tfstate/.terraform.lock.hcl
··· 1 + # This file is maintained automatically by "tofu init". 2 + # Manual edits may be lost in future updates. 3 + 4 + provider "registry.opentofu.org/hashicorp/oci" { 5 + version = "7.6.0" 6 + hashes = [ 7 + "h1:fvwp3c4OPhuKW9gBVW8eInqBE/lXZx/gLFEZRUsHuYE=", 8 + "zh:07b8c1b8ddbd9cc62296f8573a8f89d282978b0cf3046dd354028811ac371cc6", 9 + "zh:17b42ca6aee04b3c903366d029311e7ffbea630882a93d418fbb364a3d91d8bf", 10 + "zh:1fb9c7841fb556e4d580dc41d906efd40c0d1d7659275b4bb5929f331ee1fea2", 11 + "zh:20325dbb45c495baa0b885dac1c79992998b98415260317e8ca3a45c5d0a7561", 12 + "zh:216a5eb1a5ebcb61feb9bd1b2551d9a0ccbde35857d4bb66eeba7ef240ac8ba3", 13 + "zh:4adedb1709478fb9778cf95d6ba649feb7d508380957c97edcdf5e8ff3f686c8", 14 + "zh:62b6c705523ceff1b55ab8863e4ec80829b15e4890a9e9d8f0ed4dcb73630a97", 15 + "zh:7aec9c64f308cf3eb9d85ebd2dc2518c97ef71c86c75bb64507c0c22f8dd6c01", 16 + "zh:8131877e0ec3ec868b09d3e3e4d6e0b7706989e6bd30ed4aeba3695f6de04888", 17 + "zh:9978257ee7fd7a0ea090e1c8d085e93687e43d0a7607271bc8011c282b7160a4", 18 + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", 19 + "zh:b51e7e20316c0181025628e32daf1f6760d42dd53f9f9de135a41ad893a1d1c7", 20 + "zh:c9aaafa914f161587dd3ab3b9b0a867da303f8c41256dfe795414364830d6180", 21 + "zh:f3db696340ea890cb41adf9bdae19e6b0132bc29a731260145cbf64f56831a18", 22 + "zh:fea0c8888a0e2b8d3523f6888a7faa3aa13df79d954089d776db4f9a96d2215f", 23 + ] 24 + }
+18
infra/production/tfstate/terragrunt.hcl
··· 1 + include "root" { 2 + path = find_in_parent_folders("root.hcl") 3 + expose = true 4 + } 5 + 6 + terraform { 7 + source = "../../modules//tfstate" 8 + 9 + before_hook "bootstrap_tfstate" { 10 + commands = ["init", "plan", "apply"] 11 + execute = [ 12 + "go", "run", ".", 13 + "--api-token=${include.root.locals.secrets.cloudflare_tfstate_api_token}", 14 + "--account-id=${include.root.locals.secrets.cloudflare_account_id}", 15 + "--bucket=tfstate-${include.root.locals.env}", 16 + ] 17 + } 18 + }