Skip to content

Commit

Permalink
static site infra; github workflow to auto-deploy
Browse files Browse the repository at this point in the history
  • Loading branch information
rtertiaer committed Jul 3, 2024
1 parent be74e68 commit df3961a
Show file tree
Hide file tree
Showing 13 changed files with 307 additions and 1 deletion.
31 changes: 31 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: deploy prod
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
environment: prod
permissions:
contents: 'read'
id-token: 'write'
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: log in to GCP
id: auth
uses: google-github-actions/auth@v2
with:
token_format: access_token
project_id: ${{ secrets.PROJECT_ID }}
workload_identity_provider: ${{ secrets.WORKLOAD_IDENTITY_PROVIDER }}
service_account: ${{ secrets.DEPLOY_SERVICE_ACCOUNT }}
- name: 'Set up gcloud'
uses: 'google-github-actions/setup-gcloud@v2'
- name: Build & deploy site
steps:
- run: apt install hugo
- run: hugo
- run: hugo deploy
- run: gcloud compute url-maps invalidate-cdn-cache blog-prod --path "/*" --async
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
.env
*.lock
*.swp
public
terraform.tfstate*
.terraform
.terraform*
11 changes: 10 additions & 1 deletion config.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
baseURL = 'http://blog.micro-nova.com/'
baseURL = 'https://blog.micro-nova.com/'
languageCode = 'en-us'
title = 'Micro-Nova Engineering Blog'

Expand All @@ -20,3 +20,12 @@ theme = "pixyll"
# google_analytics_id = "XX-XXXXXXXX-X"
# twitter_username = "username"
paginate = true

[deployment]

[[deployment.targets]]
# An arbitrary name for this target.
name = "production"

# URL specifies the Go Cloud Development Kit URL to deploy to. Examples:
URL = "gs://blog.micro-nova.com"
16 changes: 16 additions & 0 deletions opentofu/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# `opentofu/`

This folder contains [OpenTofu](https://opentofu.org/) code to deploy a basic static site.

## Setup

Install opentofu, then configure a `.env` like so and `source` it:

```
export GOOGLE_CLOUD_PROJECT=$SOME_VALUE_FROM_GCP_CONSOLE
export GOOGLE_REGION=us-central1
export TF_VAR_dns_record="blog"
export TF_VAR_dns_zone_name="micro-nova.com"
export TF_VAR_env="prod"
export CLOUDFLARE_API_TOKEN="your token here" # create a cloudflare token that is IP limited to the office and expires in 2yrs
```
24 changes: 24 additions & 0 deletions opentofu/bucket.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
resource "google_storage_bucket" "static_site" {
name = "${var.dns_name}.${var.dns_zone_name}"
location = "US"
force_destroy = true

uniform_bucket_level_access = false

website {
main_page_suffix = "index.html"
not_found_page = "404.html"
}
cors {
origin = ["http://${var.dns_name}.${var.dns_zone_name}"]
method = ["GET", "HEAD", "PUT", "POST", "DELETE"]
response_header = ["*"]
max_age_seconds = 3600
}
}

resource "google_storage_default_object_access_control" "public_rule" {
bucket = google_storage_bucket.static_site.name
role = "READER"
entity = "allUsers"
}
36 changes: 36 additions & 0 deletions opentofu/cert.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
resource "google_certificate_manager_certificate" "default" {
name = "${var.dns_name}-${var.env}" # the regex for this resource is restrictive
managed {
domains = [
google_certificate_manager_dns_authorization.default.domain,
]
dns_authorizations = [
google_certificate_manager_dns_authorization.default.id,
]
}
}

resource "google_certificate_manager_dns_authorization" "default" {
name = "${var.dns_name}-${var.env}" # the regex for this resource is restrictive
description = "The default dnss"
domain = "${var.dns_name}.${var.dns_zone_name}"
}

resource "google_certificate_manager_certificate_map" "certificate_map" {
name = "${var.dns_name}-${var.env}" # the regex for this resource is restrictive
}

resource "google_certificate_manager_certificate_map_entry" "default" {
name = "${var.dns_name}-${var.env}" # the regex for this resource is restrictive
map = google_certificate_manager_certificate_map.certificate_map.name
certificates = [google_certificate_manager_certificate.default.id]
matcher = "PRIMARY"
}

resource "cloudflare_record" "cert_record" {
zone_id = data.cloudflare_zone.zone.id
name = google_certificate_manager_dns_authorization.default.dns_resource_record[0].name
type = google_certificate_manager_dns_authorization.default.dns_resource_record[0].type
value = google_certificate_manager_dns_authorization.default.dns_resource_record[0].data
ttl = 600
}
111 changes: 111 additions & 0 deletions opentofu/deploy.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
data "github_repository" "repo" {
full_name = var.repo_name
}

resource "google_iam_workload_identity_pool" "github" {
workload_identity_pool_id = "github-${data.github_repository.repo.name}-${var.env}"
}

resource "google_iam_workload_identity_pool_provider" "github" {
workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
workload_identity_pool_provider_id = "github"
display_name = "Github"
description = "OIDC identity pool provider for automated deployments from Github -> Cloud Run"
attribute_condition = "assertion.repository_owner == 'micro-nova' && assertion.repository_id == '${data.github_repository.repo.repo_id}'"
attribute_mapping = {
"google.subject" = "assertion.sub"
"attribute.actor" = "assertion.actor"
"attribute.repository" = "assertion.repository"
"attribute.repository_owner" = "assertion.repository_owner"
"attribute.repository_id" = "assertion.repository_id"
}
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
}

resource "google_service_account" "github" {
account_id = "${data.github_repository.repo.name}-${var.env}"
display_name = "Service account used for deploying new Docker images from github"
}

resource "google_service_account_iam_binding" "github" {
service_account_id = google_service_account.github.name
role = "roles/iam.workloadIdentityUser"
members = [
"principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${data.github_repository.repo.full_name}"
]
}

resource "google_project_iam_custom_role" "static_site_cdn" {
role_id = "${var.dns_name}_${var.env}_cdn"
title = "invalidate CDN for static site, ${data.github_repository.repo.name}-${var.env}"
description = "Limited scope role for github to deploy the static site"
permissions = [
"compute.urlMaps.get",
"compute.urlMaps.invalidateCache",
]
}

resource "google_project_iam_binding" "static_site_cdn" {
project = data.google_project.project.project_id
role = google_project_iam_custom_role.static_site_cdn.name
members = [
"serviceAccount:${google_service_account.github.email}"
]
}

resource "google_project_iam_custom_role" "static_site_bucket" {
role_id = "${var.dns_name}_${var.env}_bucket"
title = "deploy static site to bucket, ${data.github_repository.repo.name}-${var.env}"
description = "Limited scope role for github to deploy the static site"
permissions = [
"storage.objects.create",
"storage.objects.delete",
"storage.objects.get",
"storage.objects.getIamPolicy",
"storage.objects.list",
"storage.objects.update",
]
}


resource "google_project_iam_binding" "static_site_bucket" {
project = data.google_project.project.project_id
role = google_project_iam_custom_role.static_site_bucket.name
members = [
"serviceAccount:${google_service_account.github.email}"
]

condition {
title = "only_static_bucket_contents"
expression = "resource.type == 'storage.googleapis.com/Object' && resource.name.startsWith('projects/_/buckets/${google_storage_bucket.static_site.name}/')"
}
}


resource "github_repository_environment" "repo" {
repository = data.github_repository.repo.name
environment = var.env
}

resource "github_actions_environment_secret" "oidc_workload_provider" {
repository = data.github_repository.repo.name
environment = github_repository_environment.repo.environment
secret_name = "WORKLOAD_IDENTITY_PROVIDER"
plaintext_value = google_iam_workload_identity_pool_provider.github.name
}

resource "github_actions_environment_secret" "project_id" {
repository = data.github_repository.repo.name
environment = github_repository_environment.repo.environment
secret_name = "PROJECT_ID"
plaintext_value = data.google_project.project.project_id
}

resource "github_actions_environment_secret" "deploy_service_account" {
repository = data.github_repository.repo.name
environment = github_repository_environment.repo.environment
secret_name = "DEPLOY_SERVICE_ACCOUNT"
plaintext_value = google_service_account.github.email
}
12 changes: 12 additions & 0 deletions opentofu/dns.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
data "cloudflare_zone" "zone" {
name = var.dns_zone_name
}

resource "cloudflare_record" "record" {
zone_id = data.cloudflare_zone.zone.id
name = var.dns_name
value = google_compute_global_forwarding_rule.rule.ip_address
type = "A"
proxied = false
ttl = 600
}
25 changes: 25 additions & 0 deletions opentofu/lb.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
resource "google_compute_backend_bucket" "backend_bucket" {
name = "${var.dns_name}-${var.env}" # the regex for this is restrictive
bucket_name = "${var.dns_name}.${var.dns_zone_name}"
enable_cdn = true
}

resource "google_compute_target_https_proxy" "lb" {
name = "${var.dns_name}-${var.env}" # the regex for this is restrictive
url_map = google_compute_url_map.map.id
certificate_map = "//certificatemanager.googleapis.com/${google_certificate_manager_certificate_map.certificate_map.id}"
}

resource "google_compute_url_map" "map" {
name = "${var.dns_name}-${var.env}" # the regex for this is restrictive

default_service = google_compute_backend_bucket.backend_bucket.self_link
}

resource "google_compute_global_forwarding_rule" "rule" {
name = "${var.dns_name}-${var.env}"
port_range = "443"
load_balancing_scheme = "EXTERNAL_MANAGED"
ip_protocol = "TCP"
target = google_compute_target_https_proxy.lb.id
}
1 change: 1 addition & 0 deletions opentofu/misc.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data "google_project" "project" {}
3 changes: 3 additions & 0 deletions opentofu/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
provider "github" {
owner = "micro-nova"
}
5 changes: 5 additions & 0 deletions opentofu/remote.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
terraform {
backend "gcs" {
bucket = "engineering-blog-tfstate"
}
}
28 changes: 28 additions & 0 deletions opentofu/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
variable "env" {
description = "The env, ie prod, dev, test"
type = string
}

variable "dns_name" {
description = "The bare dns record used to instantiate various services; does not include the zone."
type = string
default = "blog"
}

variable "dns_zone_name" {
description = "The DNS zone name in which to place the above DNS record"
type = string
default = "micro-nova.com"
}

variable "region" {
description = "The region to deploy services into. Most things default to the provider region, but some resources need it hardcoded"
type = string
default = "us-central1"
}

variable "repo_name" {
description = "The repo name to use for setting up OIDC auth between Github <-> Google Cloud for deploys. ex: micro-nova/support_tunnel"
type = string
default = "micro-nova/engineering-blog"
}

0 comments on commit df3961a

Please sign in to comment.