From 167f256fb3a0f4c7737a140025f407d5a68cb94b Mon Sep 17 00:00:00 2001 From: Henry Ferrara Date: Mon, 14 Jul 2025 12:22:31 -0400 Subject: [PATCH 1/3] feat: initial helm for netbox dev instance feat: add github action checks --- .github/dependabot.yaml | 18 +++ .github/workflows/dev-checks.yaml | 29 +++++ .github/workflows/prod-checks.yaml | 37 +++++++ .gitignore | 45 ++++++++ .terraform-docs.yml | 44 ++++++++ atlantis.yaml | 8 ++ deployment/app/.gitignore | 1 + deployment/app/NOTE.md | 6 + deployment/app/default.yaml | 0 deployment/app/dev.yaml | 2 + deployment/app/helmfile.yaml.gotmpl | 153 ++++++++++++++++++++++++++ deployment/app/prod.yaml | 2 + deployment/dev/.gitignore | 1 + deployment/dev/.terraform-version | 1 + deployment/dev/.terraform.lock.hcl | 102 +++++++++++++++++ deployment/dev/.tflint.hcl | 0 deployment/dev/FOOTER.md | 0 deployment/dev/HEADER.md | 38 +++++++ deployment/dev/README-FEATURE-WORK.md | 11 ++ deployment/dev/backends.tf | 6 + deployment/dev/buckets.tf | 38 +++++++ deployment/dev/database.tf | 34 ++++++ deployment/dev/locals.tf | 5 + deployment/dev/main.tf | 20 ++++ deployment/dev/outputs.tf | 24 ++++ deployment/dev/providers.tf | 9 ++ deployment/dev/secrets.tf | 25 +++++ deployment/dev/service-account.tf | 25 +++++ deployment/dev/team-access.tf | 11 ++ deployment/dev/terraform.tf | 22 ++++ deployment/dev/terraform.tfvars | 11 ++ deployment/dev/variables.tf | 69 ++++++++++++ 32 files changed, 797 insertions(+) create mode 100644 .github/workflows/dev-checks.yaml create mode 100644 .github/workflows/prod-checks.yaml create mode 100644 .terraform-docs.yml create mode 100644 atlantis.yaml create mode 100644 deployment/app/.gitignore create mode 100644 deployment/app/NOTE.md create mode 100644 deployment/app/default.yaml create mode 100644 deployment/app/dev.yaml create mode 100644 deployment/app/helmfile.yaml.gotmpl create mode 100644 deployment/app/prod.yaml create mode 100644 deployment/dev/.gitignore create mode 100644 deployment/dev/.terraform-version create mode 100644 deployment/dev/.terraform.lock.hcl create mode 100644 deployment/dev/.tflint.hcl create mode 100644 deployment/dev/FOOTER.md create mode 100644 deployment/dev/HEADER.md create mode 100644 deployment/dev/README-FEATURE-WORK.md create mode 100644 deployment/dev/backends.tf create mode 100644 deployment/dev/buckets.tf create mode 100644 deployment/dev/database.tf create mode 100644 deployment/dev/locals.tf create mode 100644 deployment/dev/main.tf create mode 100644 deployment/dev/outputs.tf create mode 100644 deployment/dev/providers.tf create mode 100644 deployment/dev/secrets.tf create mode 100644 deployment/dev/service-account.tf create mode 100644 deployment/dev/team-access.tf create mode 100644 deployment/dev/terraform.tf create mode 100644 deployment/dev/terraform.tfvars create mode 100644 deployment/dev/variables.tf diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml index 20e7764..55eb16e 100644 --- a/.github/dependabot.yaml +++ b/.github/dependabot.yaml @@ -10,3 +10,21 @@ updates: schedule: # Check for updates to GitHub Actions every month interval: monthly + - package-ecosystem: terraform + directory: /deployment/dev + groups: + terraform_dev: + patterns: + - "*" + schedule: + # Check for updates to Terraform every month + interval: monthly + - package-ecosystem: terraform + directory: /deployment/prod + groups: + terraform_prod: + patterns: + - "*" + schedule: + # Check for updates to Terraform every month + interval: monthly diff --git a/.github/workflows/dev-checks.yaml b/.github/workflows/dev-checks.yaml new file mode 100644 index 0000000..6ed527c --- /dev/null +++ b/.github/workflows/dev-checks.yaml @@ -0,0 +1,29 @@ +--- +name: dev-checks + +"on": + pull_request: + branches: + - main + paths: + - "deployment/dev/**" + +jobs: + linting: + uses: broadinstitute/shared-workflows/.github/workflows/terraform-lint.yaml@v5.0.1 + with: + working_directory: "./deployment/dev" + validation: + uses: broadinstitute/shared-workflows/.github/workflows/terraform-validate.yaml@hf_use_tfenv + with: + working_directory: "./deployment/dev" +# NOTE: using tfsec because trivy tries to scan remote terraform modules and trivy-ignores +# at root level do not work for remote terraform modules + static_analysis: + uses: broadinstitute/shared-workflows/.github/workflows/terraform-static-analyze.yaml@v5.0.1 + secrets: + wf_github_token: ${{ secrets.github_token }} + with: + working_directory: "./deployment/dev" + run_tfsec: true + run_trivy: false diff --git a/.github/workflows/prod-checks.yaml b/.github/workflows/prod-checks.yaml new file mode 100644 index 0000000..302034a --- /dev/null +++ b/.github/workflows/prod-checks.yaml @@ -0,0 +1,37 @@ +--- +name: prod-checks + +"on": + pull_request: + branches: + - main + paths: + - "deployment/prod/**" + +defaults: + run: + working-directory: "./deployment/prod/" + +jobs: + terraform-docs: + uses: broadinstitute/shared-workflows/.github/workflows/terraform-docs.yaml@v5.0.1 + with: + working_directory: "./deployment/prod" + linting: + uses: broadinstitute/shared-workflows/.github/workflows/terraform-lint.yaml@v5.0.1 + with: + working_directory: "./deployment/prod" + validation: + uses: broadinstitute/shared-workflows/.github/workflows/terraform-validate.yaml@hf_use_tfenv + with: + working_directory: "./deployment/prod" +# NOTE: using tfsec because trivy tries to scan remote terraform modules and trivy-ignores +# at root level do not work for remote terraform modules + static_analysis: + uses: broadinstitute/shared-workflows/.github/workflows/terraform-static-analyze.yaml@v5.0.1 + secrets: + wf_github_token: ${{ secrets.github_token }} + with: + working_directory: "./deployment/prod" + run_tfsec: true + run_trivy: false diff --git a/.gitignore b/.gitignore index a959afc..f03b352 100644 --- a/.gitignore +++ b/.gitignore @@ -51,3 +51,48 @@ $RECYCLE.BIN/ # Windows shortcuts *.lnk + +# Local .terraform directories +.terraform/ + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore transient lock info files created by terraform apply +.terraform.tfstate.lock.info + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Optional: ignore graph output files generated by `terraform graph` +# *.dot + +# Optional: ignore plan files saved before destroying Terraform configuration +# Uncomment the line below if you want to ignore planout files. +# planout diff --git a/.terraform-docs.yml b/.terraform-docs.yml new file mode 100644 index 0000000..256caf6 --- /dev/null +++ b/.terraform-docs.yml @@ -0,0 +1,44 @@ +--- +formatter: "markdown" # this is required + +version: "" + +header-from: HEADER.md +footer-from: FOOTER.md + +recursive: + enabled: false + path: modules + +content: "" + +output: + file: "" + mode: inject + template: |- + + {{ .Content }} + + +output-values: + enabled: false + from: "" + +sort: + enabled: true + by: required + +settings: + anchor: true + color: true + default: true + description: false + escape: true + hide-empty: false + html: true + indent: 2 + lockfile: true + read-comments: true + required: true + sensitive: true + type: true diff --git a/atlantis.yaml b/atlantis.yaml new file mode 100644 index 0000000..ee81300 --- /dev/null +++ b/atlantis.yaml @@ -0,0 +1,8 @@ +--- +version: 3 +projects: + - name: prod + dir: deployments/prod + apply_requirements: [approved] + - name: dev + dir: deployments/dev diff --git a/deployment/app/.gitignore b/deployment/app/.gitignore new file mode 100644 index 0000000..e367fa6 --- /dev/null +++ b/deployment/app/.gitignore @@ -0,0 +1 @@ +terraform-output-*.json diff --git a/deployment/app/NOTE.md b/deployment/app/NOTE.md new file mode 100644 index 0000000..b3549df --- /dev/null +++ b/deployment/app/NOTE.md @@ -0,0 +1,6 @@ + export VALKEY_PASSWORD=$(kubectl get secret --namespace "broad-netbox-dev" netbox-hjf-valkey -o jsonpath="{.data.valkey-password}" | base64 -d) + + export PASSWORD=$(kubectl get secret --namespace "broad-netbox-dev" netbox-hjf-superuser -o jsonpath="{.data.password}" | base64 -d) + +--set global.valkey.password=$VALKEY_PASSWORD +--set superuser.password=$PASSWORD diff --git a/deployment/app/default.yaml b/deployment/app/default.yaml new file mode 100644 index 0000000..e69de29 diff --git a/deployment/app/dev.yaml b/deployment/app/dev.yaml new file mode 100644 index 0000000..47612e9 --- /dev/null +++ b/deployment/app/dev.yaml @@ -0,0 +1,2 @@ +Google_Project: "broad-netbox-dev" +Namespace: "broad-netbox-dev" diff --git a/deployment/app/helmfile.yaml.gotmpl b/deployment/app/helmfile.yaml.gotmpl new file mode 100644 index 0000000..e097864 --- /dev/null +++ b/deployment/app/helmfile.yaml.gotmpl @@ -0,0 +1,153 @@ +repositories: + - name: netbox + url: https://charts.netbox.oss.netboxlabs.com/ + +helmDefaults: + kubeContext: gke_bits-gke-clusters_us-east4_gke-autopilot-internal-01-prod + +environments: + dev: + values: + - default.yaml + - dev.yaml + - terraform-output-dev.json + prod: + values: + - default.yaml + - prod.yaml + - terraform-output-prod.json + +--- + +# kubeContext: gke_bits-gke-clusters_us-east4_gke-autopilot-01-prod +# annotations: +# kubernetes.io/ingress.class: "gce" +# networking.gke.io/managed-certificates: netbox-hjf +# tls: +# - hosts: +# - "netbox-hjf.broadinstitute.org" +# - allowedHosts: +# - "netbox{{ if ne .Values.instance.value "prod" }}-{{ .Values.instance.value }}{{ end }}.broadinstitute.org" +# - debug: true + +releases: + - name: netbox-{{ .Values.instance.value }} + namespace: {{ .Values.Namespace }} + chart: netbox/netbox + version: 7.1.18 + values: + # to debug database connections + - extraEnvs: + - name: DB_WAIT_DEBUG + value: "1" + - commonLabels: + environment: {{ .Values.instance.value }} + team: "science-and-technology" + app: "netbox" + - ingress: + enabled: false + className: "gce" + hostname: "netbox{{ if ne .Values.instance.value "prod" }}-{{ .Values.instance.value }}{{ end }}.broadinstitute.org" + hosts: + - host: "netbox{{ if ne .Values.instance.value "prod" }}-{{ .Values.instance.value }}{{ end }}.broadinstitute.org" + paths: + - "/" + - resources: + limits: + cpu: "1" + memory: "2Gi" + requests: + cpu: "1" + memory: "2Gi" + - serviceAccount: + annotations: + iam.gke.io/gcp-service-account: netbox-{{ .Values.instance.value }}@{{ .Values.Google_Project }}.iam.gserviceaccount.com + - superuser: + password: "TempPW4Now!" + - updateStrategy: + type: Recreate + - worker: + sidecars: + - name: cloud-sql-proxy + image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.18.3 + imagePullPolicy: Always + ports: + - name: database + containerPort: 5432 + args: + # Enable structured logging with LogEntry format: + - "--structured-logs" + # Replace DB_PORT with the port the proxy should listen on + - "--port=5432" + # Use auto iam authn to authenticate with the Cloud SQL instance + - "--auto-iam-authn" + - {{ .Values.application_database.value | quote }} + # You should use resource requests/limits as a best practice to prevent + # pods from consuming too many resources and affecting the execution of + # other pods. You should adjust the following values based on what your + # application needs. For details, see + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: "1" + memory: "1Gi" + requests: + # The proxy's CPU use scales linearly with the amount of IO between + # the database and the application. Adjust this value based on your + # application's requirements. + cpu: "1" + # The proxy's memory use scales linearly with the number of active + # connections. Fewer open connections will use less memory. Adjust + # this value based on your application's requirements. + memory: "1Gi" + securityContext: + # The default Cloud SQL Auth Proxy image runs as the + # "nonroot" user and group (uid: 65532) by default. + runAsNonRoot: true + updateStrategy: + type: Recreate + - sidecars: + - name: cloud-sql-proxy + image: gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.18.3 + imagePullPolicy: Always + ports: + - name: database + containerPort: 5432 + args: + # Enable structured logging with LogEntry format: + - "--structured-logs" + # Replace DB_PORT with the port the proxy should listen on + - "--port=5432" + # Use auto iam authn to authenticate with the Cloud SQL instance + - "--auto-iam-authn" + - {{ .Values.application_database.value | quote }} + # You should use resource requests/limits as a best practice to prevent + # pods from consuming too many resources and affecting the execution of + # other pods. You should adjust the following values based on what your + # application needs. For details, see + # https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: "1" + memory: "1Gi" + requests: + # The proxy's CPU use scales linearly with the amount of IO between + # the database and the application. Adjust this value based on your + # application's requirements. + cpu: "1" + # The proxy's memory use scales linearly with the number of active + # connections. Fewer open connections will use less memory. Adjust + # this value based on your application's requirements. + memory: "1Gi" + securityContext: + # The default Cloud SQL Auth Proxy image runs as the + # "nonroot" user and group (uid: 65532) by default. + runAsNonRoot: true + - postgresql: + enabled: false + - externalDatabase: + host: localhost + port: 5432 + database: netbox + username: netbox + password: ref+gcpsecrets://{{ .Values.Google_Project }}/{{ .Values.application_database_password_secret.value }}?version=1 diff --git a/deployment/app/prod.yaml b/deployment/app/prod.yaml new file mode 100644 index 0000000..7367cff --- /dev/null +++ b/deployment/app/prod.yaml @@ -0,0 +1,2 @@ +Google_Project: "broad-netbox-prod" +Namespace: "broad-netbox-prod" diff --git a/deployment/dev/.gitignore b/deployment/dev/.gitignore new file mode 100644 index 0000000..741e546 --- /dev/null +++ b/deployment/dev/.gitignore @@ -0,0 +1 @@ +!terraform.tfvars diff --git a/deployment/dev/.terraform-version b/deployment/dev/.terraform-version new file mode 100644 index 0000000..43ded90 --- /dev/null +++ b/deployment/dev/.terraform-version @@ -0,0 +1 @@ +1.13.5 diff --git a/deployment/dev/.terraform.lock.hcl b/deployment/dev/.terraform.lock.hcl new file mode 100644 index 0000000..e6a555e --- /dev/null +++ b/deployment/dev/.terraform.lock.hcl @@ -0,0 +1,102 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "7.12.0" + constraints = ">= 3.53.0, >= 5.31.0, >= 6.31.0, >= 6.37.0, 7.12.0, < 8.0.0" + hashes = [ + "h1:kBKvDUp6GLwHAsoM6CIj9ZTxVBzSnQjyxaVSP8SfqHQ=", + "zh:38722ec7777543c23e22e02695e53dd5c94644022647c3c79e11e587063d4d2b", + "zh:417b12b69c91c12e3fcefee38744b7a37bae73b706e3071c714151a623a6b0e9", + "zh:4902cea92c78b462beaf053de03d0d55fb2241d41ca3379b4568ba247f667fa9", + "zh:50ccce39d403ba477943e6652ccb6913092d9dcce1d55533b00b66062888db3d", + "zh:56dccfe5df28cfe368d93c37ad6c46a16e76da61482fd0bfc83676b1423cecf5", + "zh:7265fca2921e5e300da5d8de7e28b658c0863fdda9da696c5b97dbd3122c17c2", + "zh:8317467e828178a6db9ddabe431bb13935c00bfb5e4b4d9760bd56f7ae596eca", + "zh:84cc9d9277422a0d6c80d2bd204642d8776ddbba23feb94cf2760bb5f15410bc", + "zh:8f79d72e7ed4e36d01560ce5fc944dc7e0387fa0f8272a4345fc6ae896e8f575", + "zh:98c3d756beca036f84e7840e2099ff7359e9a246cd9a35386e03ce65032b3f5f", + "zh:a07e3ca19673d28da9289ca28dfb83204fa6636f642b8cf46de8caaf526b7dde", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + ] +} + +provider "registry.terraform.io/hashicorp/google-beta" { + version = "7.12.0" + constraints = ">= 6.31.0, 7.12.0, < 8.0.0" + hashes = [ + "h1:vp26U2dNza+E+o45vmEvRszQJZfws9qUC2Z8I2ePO0Q=", + "zh:297f689ccd89d3b3ca04ae175676bea988abcd3aececa6205303bc3f3bfdc91d", + "zh:631b7d6b12ee382ab13f56cdf500122e7cf80af8a80c6a5ce32a6f4f3693567c", + "zh:87aa6ac3f2aac978af25f07ea9e594423d9b18dc1a9831a9f92650f35bf0aea9", + "zh:a6437ac426065c51ccff6d71f6bea4af7d027144f79c24a93b549d49fcad3092", + "zh:ba56d7d5712404d5bb813eeaf47c4efb08520bc76c908fae02a73bdd0fb4f420", + "zh:bb271c97b80393c1eb9ae1d132e7839451c3f0ab804a2880efa7af218586107e", + "zh:c68379d252c2577df956187133c240f046bf9b8205a57715ed601cc922cd693f", + "zh:c721de9327a10f3932f67b041d7edafdf8e25804278bc344f74f6654c4ac0a05", + "zh:e4e8106261a0d3564a6315fdfae05b802948a06c30acbff7f425af11e5ca921f", + "zh:ed92ef287c8570ce48f0bcafbd50f2cc865012a39e92389a51e7b009eb30a316", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:f6ed8f74eeb03c3651f205cd892cd20bf015fe93d814b1995a499afc26a84a06", + ] +} + +provider "registry.terraform.io/hashicorp/null" { + version = "3.2.4" + constraints = "~> 3.1" + hashes = [ + "h1:L5V05xwp/Gto1leRryuesxjMfgZwjb7oool4WS1UEFQ=", + "zh:59f6b52ab4ff35739647f9509ee6d93d7c032985d9f8c6237d1f8a59471bbbe2", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:795c897119ff082133150121d39ff26cb5f89a730a2c8c26f3a9c1abf81a9c43", + "zh:7b9c7b16f118fbc2b05a983817b8ce2f86df125857966ad356353baf4bff5c0a", + "zh:85e33ab43e0e1726e5f97a874b8e24820b6565ff8076523cc2922ba671492991", + "zh:9d32ac3619cfc93eb3c4f423492a8e0f79db05fec58e449dee9b2d5873d5f69f", + "zh:9e15c3c9dd8e0d1e3731841d44c34571b6c97f5b95e8296a45318b94e5287a6e", + "zh:b4c2ab35d1b7696c30b64bf2c0f3a62329107bd1a9121ce70683dec58af19615", + "zh:c43723e8cc65bcdf5e0c92581dcbbdcbdcf18b8d2037406a5f2033b1e22de442", + "zh:ceb5495d9c31bfb299d246ab333f08c7fb0d67a4f82681fbf47f2a21c3e11ab5", + "zh:e171026b3659305c558d9804062762d168f50ba02b88b231d20ec99578a6233f", + "zh:ed0fe2acdb61330b01841fa790be00ec6beaac91d41f311fb8254f74eb6a711f", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.7.2" + constraints = "~> 3.1" + hashes = [ + "h1:KG4NuIBl1mRWU0KD/BGfCi1YN/j3F7H4YgeeM7iSdNs=", + "zh:14829603a32e4bc4d05062f059e545a91e27ff033756b48afbae6b3c835f508f", + "zh:1527fb07d9fea400d70e9e6eb4a2b918d5060d604749b6f1c361518e7da546dc", + "zh:1e86bcd7ebec85ba336b423ba1db046aeaa3c0e5f921039b3f1a6fc2f978feab", + "zh:24536dec8bde66753f4b4030b8f3ef43c196d69cccbea1c382d01b222478c7a3", + "zh:29f1786486759fad9b0ce4fdfbbfece9343ad47cd50119045075e05afe49d212", + "zh:4d701e978c2dd8604ba1ce962b047607701e65c078cb22e97171513e9e57491f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:7b8434212eef0f8c83f5a90c6d76feaf850f6502b61b53c329e85b3b281cba34", + "zh:ac8a23c212258b7976e1621275e3af7099e7e4a3d4478cf8d5d2a27f3bc3e967", + "zh:b516ca74431f3df4c6cf90ddcdb4042c626e026317a33c53f0b445a3d93b720d", + "zh:dc76e4326aec2490c1600d6871a95e78f9050f9ce427c71707ea412a2f2f1a62", + "zh:eac7b63e86c749c7d48f527671c7aee5b4e26c10be6ad7232d6860167f99dbb0", + ] +} + +provider "registry.terraform.io/hashicorp/time" { + version = "0.13.1" + constraints = "0.13.1" + hashes = [ + "h1:ZT5ppCNIModqk3iOkVt5my8b8yBHmDpl663JtXAIRqM=", + "zh:02cb9aab1002f0f2a94a4f85acec8893297dc75915f7404c165983f720a54b74", + "zh:04429b2b31a492d19e5ecf999b116d396dac0b24bba0d0fb19ecaefe193fdb8f", + "zh:26f8e51bb7c275c404ba6028c1b530312066009194db721a8427a7bc5cdbc83a", + "zh:772ff8dbdbef968651ab3ae76d04afd355c32f8a868d03244db3f8496e462690", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:898db5d2b6bd6ca5457dccb52eedbc7c5b1a71e4a4658381bcbb38cedbbda328", + "zh:8de913bf09a3fa7bedc29fec18c47c571d0c7a3d0644322c46f3aa648cf30cd8", + "zh:9402102c86a87bdfe7e501ffbb9c685c32bbcefcfcf897fd7d53df414c36877b", + "zh:b18b9bb1726bb8cfbefc0a29cf3657c82578001f514bcf4c079839b6776c47f0", + "zh:b9d31fdc4faecb909d7c5ce41d2479dd0536862a963df434be4b16e8e4edc94d", + "zh:c951e9f39cca3446c060bd63933ebb89cedde9523904813973fbc3d11863ba75", + "zh:e5b773c0d07e962291be0e9b413c7a22c044b8c7b58c76e8aa91d1659990dfb5", + ] +} diff --git a/deployment/dev/.tflint.hcl b/deployment/dev/.tflint.hcl new file mode 100644 index 0000000..e69de29 diff --git a/deployment/dev/FOOTER.md b/deployment/dev/FOOTER.md new file mode 100644 index 0000000..e69de29 diff --git a/deployment/dev/HEADER.md b/deployment/dev/HEADER.md new file mode 100644 index 0000000..dfd9c27 --- /dev/null +++ b/deployment/dev/HEADER.md @@ -0,0 +1,38 @@ + +[Terraform Docs](https://terraform-docs.io/) created by running: + +```Shell +podman run --rm \ + --volume "$(pwd):/terraform-docs" \ + -u $(id -u) \ + -w /terraform-docs \ + quay.io/terraform-docs/terraform-docs:latest \ + --output-file /terraform-docs/docs/prod.md \ + --output-mode inject /terraform-docs/deployment/prod +``` + +Remember update the dependency lock file for different architectures: + +```Shell +terraform providers lock \ + -platform=linux_amd64 \ + -platform=linux_arm64 \ + -platform=darwin_amd64 \ + -platform=darwin_arm64 \ + -platform=windows_amd64 +``` + +When applying on a brand new google project that does not have any of the Google APIs enabled, you must first enable the compute.googleapi.com before terraform is able to successfully apply the first time. This is due to how the [Google SqlDB Terrform module](https://github.com/terraform-google-modules/terraform-google-sql-db/tree/main) is implemented. The module has a data resource that queries the google project for list of available compute zones, which requires compute.googleapi.com to be enabled. + +NOTE: as of terraform provider version 6.38.0 and cloudsql module version 25.2.2 running terraform apply or plan will cause the following Warning that is safe to ignore +`Warning: Available Write-only Attribute Alternative` + +```Shell +terraform init +terraform apply --target='google_project_service.api_services["compute.googleapis.com"]' +``` + +An alternative method is to use the Google SDK CLI. +```Shell +gcloud services enable compute.googleapis.com --project=REPLACE_WITH_GOOGLE_PROJECT_NAME +``` diff --git a/deployment/dev/README-FEATURE-WORK.md b/deployment/dev/README-FEATURE-WORK.md new file mode 100644 index 0000000..4cd4ea6 --- /dev/null +++ b/deployment/dev/README-FEATURE-WORK.md @@ -0,0 +1,11 @@ +This directory is intended to be used for two purposes. + 1. PR testing prior to merging into main + 2. Engineer feature work for infrastructure changes + +In order to accomplish both of these tasks without causing collisions with the above stated purposes, there is one important thing to be aware of. The terraform variable "instance" must be set accordingly prior to performing any Engineering feature work. + +The "instance" variable is curently set via terraform.tfvars to "dev", since that is the value needed for PR testing. An engineer performing any feature work should follow the following procedures: + 1. Create their own branch from "main" + 2. Edit the file "feature.auto.tfvars" and set "instance" to some identifier for your feature work. (Ex: instance = "update_version") + +NOTE: the file "feature.auto.tfvars" is set to be ignored in the .gitignore file in order to ensure feature overrides are not checked into git. diff --git a/deployment/dev/backends.tf b/deployment/dev/backends.tf new file mode 100644 index 0000000..7e3fba9 --- /dev/null +++ b/deployment/dev/backends.tf @@ -0,0 +1,6 @@ +terraform { + backend "gcs" { + bucket = "broad-atlantis-terraform-prod" + prefix = "netbox/dev" + } +} diff --git a/deployment/dev/buckets.tf b/deployment/dev/buckets.tf new file mode 100644 index 0000000..fca374c --- /dev/null +++ b/deployment/dev/buckets.tf @@ -0,0 +1,38 @@ +module "backup-bucket" { + source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" + version = "~> 12.0.0" + + name = "${local.application_instance}-backups" + project_id = var.core_project + location = var.region + iam_members = [{ + role = "roles/storage.admin" + member = "serviceAccount:${module.application_service_accounts.service_accounts_map[local.application_instance]["email"]}" + }] +} + +module "media-bucket" { + source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" + version = "~> 12.0.0" + + name = "${local.application_instance}-media" + project_id = var.core_project + location = var.region + iam_members = [{ + role = "roles/storage.admin" + member = "serviceAccount:${module.application_service_accounts.service_accounts_map[local.application_instance]["email"]}" + }] +} + +module "reports-bucket" { + source = "terraform-google-modules/cloud-storage/google//modules/simple_bucket" + version = "~> 12.0.0" + + name = "${local.application_instance}-reports" + project_id = var.core_project + location = var.region + iam_members = [{ + role = "roles/storage.admin" + member = "serviceAccount:${module.application_service_accounts.service_accounts_map[local.application_instance]["email"]}" + }] +} diff --git a/deployment/dev/database.tf b/deployment/dev/database.tf new file mode 100644 index 0000000..2eb7a3a --- /dev/null +++ b/deployment/dev/database.tf @@ -0,0 +1,34 @@ +# Create a postgres cloudsql instance for application +module "postgres" { + source = "GoogleCloudPlatform/sql-db/google//modules/postgresql" + version = "26.2.2" + database_version = var.application_cloudsql_version + name = local.application_instance + project_id = var.core_project + user_name = var.application_name + user_password = data.google_secret_manager_secret_version.netbox_db_password.secret_data + db_name = var.application_name + deletion_protection = false # todo: Used to block Terraform from deleting a SQL Instance. - Set to false for now + deletion_protection_enabled = false # todo: Enables protection of an instance from accidental deletion across all surfaces (API, gcloud, Cloud Console and Terraform). - Set to false for now + enable_default_db = true + database_flags = [ + { + name = "cloudsql.iam_authentication", + value = "on", + }, + ] + region = var.region + edition = "ENTERPRISE" + tier = var.application_cloudsql_tier + + ip_configuration = { + ssl_mode = "ENCRYPTED_ONLY" + } + + iam_users = [ + { + id = var.application_name, + email = module.application_service_accounts.service_accounts_map[local.application_instance]["email"], + }, + ] +} diff --git a/deployment/dev/locals.tf b/deployment/dev/locals.tf new file mode 100644 index 0000000..9f6eb3e --- /dev/null +++ b/deployment/dev/locals.tf @@ -0,0 +1,5 @@ +# local variables +locals { + application_instance = "${var.application_name}-${var.instance}" + application_instance_resource = replace("${var.application_name}-${var.instance}", "-", "_") +} diff --git a/deployment/dev/main.tf b/deployment/dev/main.tf new file mode 100644 index 0000000..44943c0 --- /dev/null +++ b/deployment/dev/main.tf @@ -0,0 +1,20 @@ +# Enable APIs +resource "google_project_service" "api_services" { + for_each = toset(var.api_services) + project = var.core_project + service = each.key + disable_dependent_services = false + disable_on_destroy = false +} + +# Some API enablement takes time to complete its background tasks. However +# from a terraform perspecitive the resources so complete. This is true for +# the first time the compute API is enabled. This time_sleep will sleep +# for some seconds once the listed APIs have all been enabled. +resource "time_sleep" "wait_for_api" { + create_duration = "60s" + + depends_on = [ + google_project_service.api_services["compute.googleapis.com"], + ] +} diff --git a/deployment/dev/outputs.tf b/deployment/dev/outputs.tf new file mode 100644 index 0000000..84c4a0a --- /dev/null +++ b/deployment/dev/outputs.tf @@ -0,0 +1,24 @@ +# applicaion instance ID +output "instance" { + value = var.instance +} +# applicaion instance ID +output "application_instance" { + value = local.application_instance +} +# instance database +output "application_database" { + value = module.postgres.instance_connection_name +} +# instance database password secret +output "application_database_password_secret" { + value = google_secret_manager_secret.netbox_db_password.secret_id +} +# instance service account +output "application_service_account" { + value = module.application_service_accounts.email +} +# google project +output "application_google_project" { + value = var.core_project +} diff --git a/deployment/dev/providers.tf b/deployment/dev/providers.tf new file mode 100644 index 0000000..3619503 --- /dev/null +++ b/deployment/dev/providers.tf @@ -0,0 +1,9 @@ +provider "google" { + project = var.core_project + region = var.region +} + +provider "google-beta" { + project = var.core_project + region = var.region +} diff --git a/deployment/dev/secrets.tf b/deployment/dev/secrets.tf new file mode 100644 index 0000000..0b3019d --- /dev/null +++ b/deployment/dev/secrets.tf @@ -0,0 +1,25 @@ +# +# database password +# +resource "random_password" "netbox_db_password" { + length = 16 + special = true + override_special = "!@#$%^&*" # Optional: Specify custom special characters +} + +resource "google_secret_manager_secret" "netbox_db_password" { + secret_id = "${local.application_instance_resource}_netbox_db_password" + replication { + auto {} + } +} + +resource "google_secret_manager_secret_version" "netbox_db_password" { + secret = google_secret_manager_secret.netbox_db_password.name + secret_data_wo = random_password.netbox_db_password.result +} + +data "google_secret_manager_secret_version" "netbox_db_password" { + secret = google_secret_manager_secret.netbox_db_password.name + version = "1" +} diff --git a/deployment/dev/service-account.tf b/deployment/dev/service-account.tf new file mode 100644 index 0000000..32b2342 --- /dev/null +++ b/deployment/dev/service-account.tf @@ -0,0 +1,25 @@ +# Create a service account for Netbox +module "application_service_accounts" { + source = "terraform-google-modules/service-accounts/google" + version = "4.6.0" + project_id = var.core_project + names = [local.application_instance] + description = "service account for Application Pods" + project_roles = [ + "${var.core_project}=>roles/cloudsql.instanceUser", + "${var.core_project}=>roles/cloudsql.client", + "${var.core_project}=>roles/monitoring.viewer", + "${var.core_project}=>roles/monitoring.metricWriter", + "${var.core_project}=>roles/iam.serviceAccountTokenCreator", + "${var.core_project}=>roles/secretmanager.secretAccessor", + ] +} + +resource "google_service_account_iam_member" "application_workload_identity" { + service_account_id = module.application_service_accounts.service_accounts_map[local.application_instance]["name"] + role = "roles/iam.workloadIdentityUser" + member = "serviceAccount:${var.gke_project}.svc.id.goog[${var.namespace}/${local.application_instance}]" + depends_on = [ + time_sleep.wait_for_api, + ] +} diff --git a/deployment/dev/team-access.tf b/deployment/dev/team-access.tf new file mode 100644 index 0000000..b7b5d2c --- /dev/null +++ b/deployment/dev/team-access.tf @@ -0,0 +1,11 @@ +resource "google_project_iam_member" "team_access" { + project = var.core_project + role = "roles/editor" + member = "group:devnull@broadinstitute.org" +} + +resource "google_project_iam_member" "sa_access" { + project = var.core_project + role = "roles/owner" + member = "group:devnull-sa@broadinstitute.org" +} diff --git a/deployment/dev/terraform.tf b/deployment/dev/terraform.tf new file mode 100644 index 0000000..1c1f983 --- /dev/null +++ b/deployment/dev/terraform.tf @@ -0,0 +1,22 @@ +terraform { + required_version = "1.13.5" + + required_providers { + google = { + source = "hashicorp/google" + version = "7.12.0" + } + google-beta = { + source = "hashicorp/google-beta" + version = "7.12.0" + } + random = { + source = "hashicorp/random" + version = "3.7.2" + } + time = { + source = "hashicorp/time" + version = "0.13.1" + } + } +} diff --git a/deployment/dev/terraform.tfvars b/deployment/dev/terraform.tfvars new file mode 100644 index 0000000..7a07e7b --- /dev/null +++ b/deployment/dev/terraform.tfvars @@ -0,0 +1,11 @@ +# https://www.terraform.io/docs/configuration/variables.html#variable-definitions-tfvars-files +# Terraform automatically loads variable definition from this file. +# Use this file to override variable defaults set in variables.tf +# ex. foo = "bar" + +core_project = "broad-netbox-dev" +instance = "dev" +namespace = "broad-netbox-dev" +gke_project = "bits-gke-clusters" +application_cloudsql_version = "POSTGRES_14" +application_name = "netbox" diff --git a/deployment/dev/variables.tf b/deployment/dev/variables.tf new file mode 100644 index 0000000..c8585f7 --- /dev/null +++ b/deployment/dev/variables.tf @@ -0,0 +1,69 @@ +# Input variables to accept values from the calling module +# https://www.terraform.io/docs/modules/#standard-module-structure +variable "api_services" { + description = "List of API services to enable" + type = list(string) + default = [ + "bigquery.googleapis.com", + "cloudidentity.googleapis.com", + "cloudresourcemanager.googleapis.com", + "compute.googleapis.com", + "iam.googleapis.com", + "iap.googleapis.com", + "iamcredentials.googleapis.com", + "monitoring.googleapis.com", + "networkconnectivity.googleapis.com", + "secretmanager.googleapis.com", + "sqladmin.googleapis.com", + "servicenetworking.googleapis.com", + "serviceconsumermanagement.googleapis.com", + "storage.googleapis.com", + "sts.googleapis.com", + ] +} + +variable "core_project" { + description = "GCP project to use for the default/primary provider" + type = string +} + +variable "gke_project" { + description = "google project where GKE cluster resides" + type = string +} + +variable "instance" { + description = "application instance identifier" + type = string +} + +variable "namespace" { + description = "GKE namespace where application will be deployed" + type = string +} + +variable "region" { + default = "us-east4" + description = "The default region in which resources will be created" + type = string +} + +# Below are application specific variables and defaults + +variable "application_name" { + default = "" + description = "application name" + type = string +} + +variable "application_cloudsql_tier" { + default = "db-f1-micro" + description = "Cloudsql instance size for application database" + type = string +} + +variable "application_cloudsql_version" { + default = "" + description = "Cloudsql instance version for application database" + type = string +} From ae85c7bf825de81831eb84865c76bdce89e40032 Mon Sep 17 00:00:00 2001 From: Henry Ferrara Date: Thu, 4 Dec 2025 16:45:29 -0500 Subject: [PATCH 2/3] fix: correct path for terraform for atlantis --- atlantis.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atlantis.yaml b/atlantis.yaml index ee81300..92342f6 100644 --- a/atlantis.yaml +++ b/atlantis.yaml @@ -2,7 +2,7 @@ version: 3 projects: - name: prod - dir: deployments/prod + dir: deployment/prod apply_requirements: [approved] - name: dev - dir: deployments/dev + dir: deployment/dev From d0890642276d5a91816f15ed54a5add41993fb2b Mon Sep 17 00:00:00 2001 From: Henry Ferrara Date: Fri, 5 Dec 2025 10:31:04 -0500 Subject: [PATCH 3/3] fix: update documentations --- deployment/app/NOTE.md | 6 ------ deployment/app/README.md | 10 ++++++++++ deployment/app/helmfile.yaml.gotmpl | 5 +++++ 3 files changed, 15 insertions(+), 6 deletions(-) delete mode 100644 deployment/app/NOTE.md create mode 100644 deployment/app/README.md diff --git a/deployment/app/NOTE.md b/deployment/app/NOTE.md deleted file mode 100644 index b3549df..0000000 --- a/deployment/app/NOTE.md +++ /dev/null @@ -1,6 +0,0 @@ - export VALKEY_PASSWORD=$(kubectl get secret --namespace "broad-netbox-dev" netbox-hjf-valkey -o jsonpath="{.data.valkey-password}" | base64 -d) - - export PASSWORD=$(kubectl get secret --namespace "broad-netbox-dev" netbox-hjf-superuser -o jsonpath="{.data.password}" | base64 -d) - ---set global.valkey.password=$VALKEY_PASSWORD ---set superuser.password=$PASSWORD diff --git a/deployment/app/README.md b/deployment/app/README.md new file mode 100644 index 0000000..c84b172 --- /dev/null +++ b/deployment/app/README.md @@ -0,0 +1,10 @@ + +Updating the application after the initial deploy requires passing some additional password values to the `helmfile apply` command. + +Below are some shell commands that can be used for manual deploys + + export VALKEY_PASSWORD=$(kubectl get secret --namespace netbox--valkey -o jsonpath="{.data.valkey-password}" | base64 -d) + + export PASSWORD=$(kubectl get secret --namespace netbox--superuser -o jsonpath="{.data.password}" | base64 -d) + +helmfile --set global.valkey.password=$VALKEY_PASSWORD --set superuser.password=$PASSWORD apply diff --git a/deployment/app/helmfile.yaml.gotmpl b/deployment/app/helmfile.yaml.gotmpl index e097864..bd3c684 100644 --- a/deployment/app/helmfile.yaml.gotmpl +++ b/deployment/app/helmfile.yaml.gotmpl @@ -62,8 +62,11 @@ releases: - serviceAccount: annotations: iam.gke.io/gcp-service-account: netbox-{{ .Values.instance.value }}@{{ .Values.Google_Project }}.iam.gserviceaccount.com + # Superuser password is stored in database and can be changed via UI - superuser: password: "TempPW4Now!" + # As long as we use PVCs for media, reports,... can not do rolling update due to + # PVCs only allowing single pod write - updateStrategy: type: Recreate - worker: @@ -104,6 +107,8 @@ releases: # The default Cloud SQL Auth Proxy image runs as the # "nonroot" user and group (uid: 65532) by default. runAsNonRoot: true + # As long as we use PVCs for media, reports,... can not do rolling update due to + # PVCs only allowing single pod write updateStrategy: type: Recreate - sidecars: