feat: preview environments and automated deployments (#32)

This commit is contained in:
Zack Pollard
2024-11-28 16:03:53 +00:00
committed by GitHub
parent 7b6a16b103
commit 0e61c56e23
19 changed files with 405 additions and 0 deletions
+73
View File
@@ -11,6 +11,11 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
tofu_version: '1.7.1'
tg_version: '0.69.0'
ENVIRONMENT: ${{ github.ref == 'refs/heads/main' && 'prod' || 'dev' }}
jobs:
build:
name: Build
@@ -30,3 +35,71 @@ jobs:
- name: Run build
run: npm run build
- name: Upload build output
uses: actions/upload-artifact@v4
with:
name: build-output
path: build
retention-days: 1
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: build
env:
TF_VAR_stage: ${{ github.event_name == 'pull_request' && format('pr-{0}', github.event.number) || '' }}
OP_SERVICE_ACCOUNT_TOKEN: ${{ github.ref == 'refs/heads/main' && secrets.OP_TF_PROD_ENV || secrets.OP_TF_DEV_ENV }}
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: 'Get build artifact'
uses: actions/download-artifact@v4
with:
name: 'build-output'
path: '${{ github.workspace }}/build'
- name: Install 1Password CLI
uses: 1password/install-cli-action@v1
- name: Install Terragrunt
uses: eLco/setup-terragrunt@v1
with:
terragrunt_version: ${{ env.tg_version }}
- name: 'Install OpenTofu'
uses: opentofu/setup-opentofu@v1
with:
tofu_version: ${{ env.tofu_version }}
tofu_wrapper: false
- name: Deploy All
working-directory: ${{ github.workspace }}/deployment
run: op run --env-file=".env" -- terragrunt run-all apply --terragrunt-non-interactive
- name: Cloudflare Deploy Output
id: deploy-output
working-directory: ${{ github.workspace }}/deployment/modules/cloudflare/static-pages
run: |
echo "output=$(op run --no-masking --env-file='../../../.env' -- terragrunt output -json | jq -c .)" >> $GITHUB_OUTPUT
- name: Publish Frontend to Cloudflare Pages
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN_PAGES_UPLOAD }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: ${{ fromJson(steps.deploy-output.outputs.output).pages_project_name.value }}
directory: 'build'
branch: ${{ fromJson(steps.deploy-output.outputs.output).pages_branch.value }}
wranglerVersion: '3'
- name: Comment
uses: actions-cool/maintain-one-comment@v3
if: ${{ github.event_name == 'pull_request' }}
with:
number: ${{ github.event.number }}
body: |
📖 Preview of ui.immich.app deployed to [${{ fromJson(steps.deploy-output.outputs.output).immich_subdomain.value }}](https://${{ fromJson(steps.deploy-output.outputs.output).immich_subdomain.value }})
emojis: 'rocket'
body-include: '<!-- ui PR URL -->'
+50
View File
@@ -0,0 +1,50 @@
name: Destroy
on:
pull_request_target:
types: [closed]
env:
tofu_version: '1.7.1'
tg_version: '0.69.0'
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 1
matrix:
environment: ['dev', 'prod']
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install 1Password CLI
uses: 1password/install-cli-action@v1
- name: Install Terragrunt
uses: eLco/setup-terragrunt@v1
with:
terragrunt_version: ${{ env.tg_version }}
- name: 'Install OpenTofu'
uses: opentofu/setup-opentofu@v1
with:
tofu_version: ${{ env.tofu_version }}
tofu_wrapper: false
- name: Destroy All
working-directory: ${{ env.working_dir }}
env:
ENVIRONMENT: ${{ matrix.environment }}
TF_VAR_stage: pr-${{ github.event.number }}
OP_SERVICE_ACCOUNT_TOKEN: ${{ matrix.environment == 'prod' && secrets.OP_TF_PROD_ENV || secrets.OP_TF_DEV_ENV }}
working_dir: 'deployment'
run: op run --env-file=".env" -- terragrunt run-all destroy -refresh=false --terragrunt-non-interactive --terragrunt-exclude-dir '**/pages-project'
- name: Comment
uses: actions-cool/maintain-one-comment@v3
with:
number: ${{ github.event.number }}
delete: true
body-include: '<!-- web PR URL -->'
+4
View File
@@ -0,0 +1,4 @@
export TF_VAR_cloudflare_account_id="op://tf/cloudflare/account_id"
export TF_VAR_cloudflare_api_token="op://tf/cloudflare/api_token"
export TF_VAR_tf_state_postgres_conn_str="op://tf/tf_state/postgres_conn_str"
export TF_VAR_env=$ENVIRONMENT
+40
View File
@@ -0,0 +1,40 @@
# OpenTofu
!.env
# Local .terraform directories
**/.terraform/*
# .tfstate files
*.tfstate
*.tfstate.*
# Crash log files
crash.log
crash.*.log
# 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
# 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
# Terragrunt
# terragrunt cache directories
**/.terragrunt-cache/*
# Terragrunt debug output file (when using `--terragrunt-debug` option)
# See: https://terragrunt.gruntwork.io/docs/reference/cli-options/#terragrunt-debug
terragrunt-debug.tfvars.json
@@ -0,0 +1,25 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.46.0"
constraints = "4.46.0"
hashes = [
"h1:QFFZshAvwr9L5TQmsNQC6/sDqokk5pjbP8Ae4BQqMLQ=",
"zh:2ee426ef3389022db0026792fdc4f2980dcf2600e31adf5a31b4bddfa8d68343",
"zh:2f993edb23df55dc1c18150fa187d80aa7d87e6439698ee34b6a6aad23ac2dd7",
"zh:3d6601333975e55979b1b454e50ff9a482ce4e0269dd6c72a50202163a8f4463",
"zh:4e5f48dce22f7a6d618018d65d1d443bb718defa23f514d5c6385860541fbe79",
"zh:5ebf5aea960fc30de381ffd6db20876d249673cf938fe67f1dfb6b9caa1db418",
"zh:80ed3fb901141f53b4b56ddb7eea5f2e0c0830d501387539d2c2b8e0cc7e587a",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:9aeae8b3be4a577ced46987fd9159262c5b4c54a510f66592fbcdb40fef55b10",
"zh:a0479ef2d308c4a7894f1fe77467cd07e04c7b40d281088f4f204af1bdf94ac6",
"zh:a2bdc0c25130665af0b9559942b9813a1ba4889513e7185d4abc9c02e9bb99bd",
"zh:b10be9755fe80395ced6f0bbda38b8c8681714cf1eca1d895be239c75c2ffc2a",
"zh:ba3d55e722d9f48646574ce7c448f0084fe21fa884b5f8b6d6146a82a99c4baa",
"zh:ec1fd0ecaedc787a77d5342b51ae8dea8362a67f1e19123f6521a0e8e012d9e8",
"zh:ed49590e69faef14550179f965b4451b31415b8f6be6d33427ad48f65c76b6cf",
"zh:f4baa3a2dac719ad20dcfa525bc3f737ad95650b8d0de0c648dc9a87f993b2c3",
]
}
@@ -0,0 +1,11 @@
terraform {
backend "pg" {}
required_version = "~> 1.7"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.46.0"
}
}
}
@@ -0,0 +1,3 @@
locals {
app_name = "ui"
}
@@ -0,0 +1,14 @@
module "pages_project" {
source = "git::https://github.com/immich-app/devtools.git//tf/shared/modules/cloudflare-pages-project?ref=feat/pages-project-tf-module"
cloudflare_api_token = data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_account
cloudflare_account_id = data.terraform_remote_state.cloudflare_account.outputs.cloudflare_account_id
app_name = local.app_name
env = var.env
}
output "pages_project" {
value = module.pages_project.pages_project
sensitive = true
}
@@ -0,0 +1,17 @@
data "terraform_remote_state" "api_keys_state" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_api_keys"
}
}
data "terraform_remote_state" "cloudflare_account" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_account"
}
}
@@ -0,0 +1,24 @@
terraform {
source = "."
extra_arguments custom_vars {
commands = get_terraform_commands_that_need_vars()
}
}
include {
path = find_in_parent_folders("state.hcl")
}
locals {
env = get_env("TF_VAR_env")
}
remote_state {
backend = "pg"
config = {
conn_str = get_env("TF_VAR_tf_state_postgres_conn_str")
schema_name = "cloudflare_pages_project_immich_app_ui_${local.env}"
}
}
@@ -0,0 +1,2 @@
variable "tf_state_postgres_conn_str" {}
variable "env" {}
@@ -0,0 +1,25 @@
# This file is maintained automatically by "tofu init".
# Manual edits may be lost in future updates.
provider "registry.opentofu.org/cloudflare/cloudflare" {
version = "4.46.0"
constraints = "4.46.0"
hashes = [
"h1:QFFZshAvwr9L5TQmsNQC6/sDqokk5pjbP8Ae4BQqMLQ=",
"zh:2ee426ef3389022db0026792fdc4f2980dcf2600e31adf5a31b4bddfa8d68343",
"zh:2f993edb23df55dc1c18150fa187d80aa7d87e6439698ee34b6a6aad23ac2dd7",
"zh:3d6601333975e55979b1b454e50ff9a482ce4e0269dd6c72a50202163a8f4463",
"zh:4e5f48dce22f7a6d618018d65d1d443bb718defa23f514d5c6385860541fbe79",
"zh:5ebf5aea960fc30de381ffd6db20876d249673cf938fe67f1dfb6b9caa1db418",
"zh:80ed3fb901141f53b4b56ddb7eea5f2e0c0830d501387539d2c2b8e0cc7e587a",
"zh:890df766e9b839623b1f0437355032a3c006226a6c200cd911e15ee1a9014e9f",
"zh:9aeae8b3be4a577ced46987fd9159262c5b4c54a510f66592fbcdb40fef55b10",
"zh:a0479ef2d308c4a7894f1fe77467cd07e04c7b40d281088f4f204af1bdf94ac6",
"zh:a2bdc0c25130665af0b9559942b9813a1ba4889513e7185d4abc9c02e9bb99bd",
"zh:b10be9755fe80395ced6f0bbda38b8c8681714cf1eca1d895be239c75c2ffc2a",
"zh:ba3d55e722d9f48646574ce7c448f0084fe21fa884b5f8b6d6146a82a99c4baa",
"zh:ec1fd0ecaedc787a77d5342b51ae8dea8362a67f1e19123f6521a0e8e012d9e8",
"zh:ed49590e69faef14550179f965b4451b31415b8f6be6d33427ad48f65c76b6cf",
"zh:f4baa3a2dac719ad20dcfa525bc3f737ad95650b8d0de0c648dc9a87f993b2c3",
]
}
@@ -0,0 +1,11 @@
terraform {
backend "pg" {}
required_version = "~> 1.7"
required_providers {
cloudflare = {
source = "cloudflare/cloudflare"
version = "4.46.0"
}
}
}
@@ -0,0 +1,3 @@
locals {
app_name = "ui"
}
@@ -0,0 +1,28 @@
module "static_pages" {
source = "git::https://github.com/immich-app/devtools.git//tf/shared/modules/cloudflare-pages?ref=feat/pages-project-tf-module"
cloudflare_api_token = data.terraform_remote_state.api_keys_state.outputs.terraform_key_cloudflare_docs
cloudflare_account_id = data.terraform_remote_state.cloudflare_account.outputs.cloudflare_account_id
pages_project = data.terraform_remote_state.pages_project.outputs.pages_project
app_name = local.app_name
stage = var.stage
env = var.env
}
output "pages_branch" {
value = module.static_pages.pages_branch
}
output "immich_subdomain" {
value = module.static_pages.branch_subdomain
}
output "pages_branch_subdomain" {
value = module.static_pages.pages_branch_subdomain
}
output "pages_project_name" {
value = module.static_pages.pages_project_name
}
@@ -0,0 +1,26 @@
data "terraform_remote_state" "api_keys_state" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_api_keys"
}
}
data "terraform_remote_state" "cloudflare_account" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "prod_cloudflare_account"
}
}
data "terraform_remote_state" "pages_project" {
backend = "pg"
config = {
conn_str = var.tf_state_postgres_conn_str
schema_name = "cloudflare_pages_project_immich_app_ui_${var.env}"
}
}
@@ -0,0 +1,29 @@
terraform {
source = "."
extra_arguments custom_vars {
commands = get_terraform_commands_that_need_vars()
}
}
include {
path = find_in_parent_folders("state.hcl")
}
locals {
env = get_env("TF_VAR_env")
stage = get_env("TF_VAR_stage", "")
}
remote_state {
backend = "pg"
config = {
conn_str = get_env("TF_VAR_tf_state_postgres_conn_str")
schema_name = "cloudflare_immich_app_ui_${local.env}${local.stage}"
}
}
dependencies {
paths = ["../pages-project"]
}
@@ -0,0 +1,5 @@
variable "tf_state_postgres_conn_str" {}
variable "stage" {
default = ""
}
variable "env" {}
+15
View File
@@ -0,0 +1,15 @@
locals {
tf_state_postgres_conn_str = get_env("TF_VAR_tf_state_postgres_conn_str")
}
remote_state {
backend = "pg"
config = {
conn_str = local.tf_state_postgres_conn_str
}
}
inputs = {
tf_state_postgres_conn_str = local.tf_state_postgres_conn_str
}