Invalidating caches after deploying
Posted by @pcostesi on 2021-07-22Time to read: 20 minutes
In the last blog post, we set up Cloudflare so we could have TLS on the cheap. I incorrectly stated that we didn't need to modify the build process: we need to tell Cloudflare that the old content should be purged, or that it shouldn't cache html files.
We need to set up an API token with limited access in Cloudflare. This allows us to keep our site updated through the builder without exposing our master key:
|
resource "google_cloudbuild_trigger" "site_trigger" { |
|
provider = google-beta |
|
name = "site-trigger" |
|
project = var.project |
|
|
|
github { |
|
push { |
|
branch = "master" |
|
} |
|
owner = "pcostesi" |
|
name = "pcostesi.dev" |
|
|
|
} |
|
|
|
substitutions = { |
|
"_CF_ZONE" = cloudflare_zone.main_site[0].id |
|
"_CF_USER" = var.email_address |
|
"_CF_API_TOKEN" = cloudflare_api_token.dns_tls_purge_cache[0].value |
|
} |
|
|
|
filename = "cloudbuild.yaml" |
|
} |
|
|
|
data "cloudflare_api_token_permission_groups" "all" {} |
|
|
|
resource "cloudflare_api_token" "dns_tls_purge_cache" { |
|
name = "dns_tls_purge_cache" |
|
|
|
policy { |
|
permission_groups = [ |
|
data.cloudflare_api_token_permission_groups.all.permissions["Cache Purge"], |
|
] |
|
resources = { |
|
"com.cloudflare.api.account.zone.${cloudflare_zone.main_site[0].id}" = "*" |
|
} |
|
} |
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
steps: |
|
- name: node:current |
|
entrypoint: npm |
|
args: ["install"] |
|
- name: node:current |
|
entrypoint: npx |
|
args: ["browserslist@latest", "--update-db"] |
|
- name: node:current |
|
entrypoint: npm |
|
args: ["run", "build"] |
|
- name: gcr.io/cloud-builders/gsutil |
|
args: ["-m", "rsync", "-r", "-c", "-d", "./public", "gs://pcostesi.dev"] |
|
- name: gcr.io/cloud-builders/gcloud |
|
entrypoint: "bash" |
|
args: |
|
- "-c" |
|
- | |
|
curl -X POST "https://api.cloudflare.com/client/v4/zones/${_CF_ZONE}/purge_cache" \ |
|
-H "X-Auth-Email: ${_CF_USER}" \ |
|
-H "Authorization: Bearer ${_CF_API_TOKEN}" \ |
|
-H "Content-Type: application/json" \ |
|
--data '{"purge_everything":true}' |
|
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`) |
|
resource "google_compute_global_address" "static_sites" { |
|
provider = google |
|
name = "static-sites" |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Managed DNS zone |
|
data "google_dns_managed_zone" "main_site" { |
|
provider = google |
|
name = var.project |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Bind IP to the DNS |
|
resource "google_dns_record_set" "static_site" { |
|
provider = google |
|
name = "${var.static_site}." |
|
type = "A" |
|
ttl = 300 |
|
managed_zone = data.google_dns_managed_zone.main_site[0].name |
|
rrdatas = [google_compute_global_address.static_sites[0].address] |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Site verification |
|
resource "google_dns_record_set" "static_site_txt" { |
|
provider = google |
|
name = "${var.static_site}." |
|
type = "TXT" |
|
ttl = 300 |
|
managed_zone = data.google_dns_managed_zone.main_site[0].name |
|
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"] |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
resource "cloudflare_zone" "main_site" { |
|
zone = var.static_site |
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_zone_settings_override" "main_site" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
settings { |
|
ssl = "strict" |
|
always_use_https = "on" |
|
automatic_https_rewrites = "on" |
|
} |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_record" "static_site" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
name = "${var.static_site}." |
|
value = "c.storage.googleapis.com" |
|
type = "CNAME" |
|
ttl = 1 |
|
proxied = true |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_record" "static_site_txt" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
name = "${var.static_site}." |
|
type = "TXT" |
|
ttl = 300 |
|
value = "google-site-verification=${var.google_site_verification}" |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
You can check the token permissions here: Cloudflare API docs.
Next, we need to tell the build step the content of the key:
|
resource "google_cloudbuild_trigger" "site_trigger" { |
|
provider = google-beta |
|
name = "site-trigger" |
|
project = var.project |
|
|
|
github { |
|
push { |
|
branch = "master" |
|
} |
|
owner = "pcostesi" |
|
name = "pcostesi.dev" |
|
|
|
} |
|
|
|
substitutions = { |
|
"_CF_ZONE" = cloudflare_zone.main_site[0].id |
|
"_CF_USER" = var.email_address |
|
"_CF_API_TOKEN" = cloudflare_api_token.dns_tls_purge_cache[0].value |
|
} |
|
|
|
filename = "cloudbuild.yaml" |
|
} |
|
|
|
data "cloudflare_api_token_permission_groups" "all" {} |
|
|
|
resource "cloudflare_api_token" "dns_tls_purge_cache" { |
|
name = "dns_tls_purge_cache" |
|
|
|
policy { |
|
permission_groups = [ |
|
data.cloudflare_api_token_permission_groups.all.permissions["Cache Purge"], |
|
] |
|
resources = { |
|
"com.cloudflare.api.account.zone.${cloudflare_zone.main_site[0].id}" = "*" |
|
} |
|
} |
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
steps: |
|
- name: node:current |
|
entrypoint: npm |
|
args: ["install"] |
|
- name: node:current |
|
entrypoint: npx |
|
args: ["browserslist@latest", "--update-db"] |
|
- name: node:current |
|
entrypoint: npm |
|
args: ["run", "build"] |
|
- name: gcr.io/cloud-builders/gsutil |
|
args: ["-m", "rsync", "-r", "-c", "-d", "./public", "gs://pcostesi.dev"] |
|
- name: gcr.io/cloud-builders/gcloud |
|
entrypoint: "bash" |
|
args: |
|
- "-c" |
|
- | |
|
curl -X POST "https://api.cloudflare.com/client/v4/zones/${_CF_ZONE}/purge_cache" \ |
|
-H "X-Auth-Email: ${_CF_USER}" \ |
|
-H "Authorization: Bearer ${_CF_API_TOKEN}" \ |
|
-H "Content-Type: application/json" \ |
|
--data '{"purge_everything":true}' |
|
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`) |
|
resource "google_compute_global_address" "static_sites" { |
|
provider = google |
|
name = "static-sites" |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Managed DNS zone |
|
data "google_dns_managed_zone" "main_site" { |
|
provider = google |
|
name = var.project |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Bind IP to the DNS |
|
resource "google_dns_record_set" "static_site" { |
|
provider = google |
|
name = "${var.static_site}." |
|
type = "A" |
|
ttl = 300 |
|
managed_zone = data.google_dns_managed_zone.main_site[0].name |
|
rrdatas = [google_compute_global_address.static_sites[0].address] |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Site verification |
|
resource "google_dns_record_set" "static_site_txt" { |
|
provider = google |
|
name = "${var.static_site}." |
|
type = "TXT" |
|
ttl = 300 |
|
managed_zone = data.google_dns_managed_zone.main_site[0].name |
|
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"] |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
resource "cloudflare_zone" "main_site" { |
|
zone = var.static_site |
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_zone_settings_override" "main_site" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
settings { |
|
ssl = "strict" |
|
always_use_https = "on" |
|
automatic_https_rewrites = "on" |
|
} |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_record" "static_site" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
name = "${var.static_site}." |
|
value = "c.storage.googleapis.com" |
|
type = "CNAME" |
|
ttl = 1 |
|
proxied = true |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_record" "static_site_txt" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
name = "${var.static_site}." |
|
type = "TXT" |
|
ttl = 300 |
|
value = "google-site-verification=${var.google_site_verification}" |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
Finally, we need to add the purge step in the cloudbuild.yaml
in our blog:
|
resource "google_cloudbuild_trigger" "site_trigger" { |
|
provider = google-beta |
|
name = "site-trigger" |
|
project = var.project |
|
|
|
github { |
|
push { |
|
branch = "master" |
|
} |
|
owner = "pcostesi" |
|
name = "pcostesi.dev" |
|
|
|
} |
|
|
|
substitutions = { |
|
"_CF_ZONE" = cloudflare_zone.main_site[0].id |
|
"_CF_USER" = var.email_address |
|
"_CF_API_TOKEN" = cloudflare_api_token.dns_tls_purge_cache[0].value |
|
} |
|
|
|
filename = "cloudbuild.yaml" |
|
} |
|
|
|
data "cloudflare_api_token_permission_groups" "all" {} |
|
|
|
resource "cloudflare_api_token" "dns_tls_purge_cache" { |
|
name = "dns_tls_purge_cache" |
|
|
|
policy { |
|
permission_groups = [ |
|
data.cloudflare_api_token_permission_groups.all.permissions["Cache Purge"], |
|
] |
|
resources = { |
|
"com.cloudflare.api.account.zone.${cloudflare_zone.main_site[0].id}" = "*" |
|
} |
|
} |
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
steps: |
|
- name: node:current |
|
entrypoint: npm |
|
args: ["install"] |
|
- name: node:current |
|
entrypoint: npx |
|
args: ["browserslist@latest", "--update-db"] |
|
- name: node:current |
|
entrypoint: npm |
|
args: ["run", "build"] |
|
- name: gcr.io/cloud-builders/gsutil |
|
args: ["-m", "rsync", "-r", "-c", "-d", "./public", "gs://pcostesi.dev"] |
|
- name: gcr.io/cloud-builders/gcloud |
|
entrypoint: "bash" |
|
args: |
|
- "-c" |
|
- | |
|
curl -X POST "https://api.cloudflare.com/client/v4/zones/${_CF_ZONE}/purge_cache" \ |
|
-H "X-Auth-Email: ${_CF_USER}" \ |
|
-H "Authorization: Bearer ${_CF_API_TOKEN}" \ |
|
-H "Content-Type: application/json" \ |
|
--data '{"purge_everything":true}' |
|
# Global IP, needed for the load balancer (maybe this should go to `routing.tf`) |
|
resource "google_compute_global_address" "static_sites" { |
|
provider = google |
|
name = "static-sites" |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Managed DNS zone |
|
data "google_dns_managed_zone" "main_site" { |
|
provider = google |
|
name = var.project |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Bind IP to the DNS |
|
resource "google_dns_record_set" "static_site" { |
|
provider = google |
|
name = "${var.static_site}." |
|
type = "A" |
|
ttl = 300 |
|
managed_zone = data.google_dns_managed_zone.main_site[0].name |
|
rrdatas = [google_compute_global_address.static_sites[0].address] |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
# Site verification |
|
resource "google_dns_record_set" "static_site_txt" { |
|
provider = google |
|
name = "${var.static_site}." |
|
type = "TXT" |
|
ttl = 300 |
|
managed_zone = data.google_dns_managed_zone.main_site[0].name |
|
rrdatas = ["google-site-verification=KNrjsg5IVfr7ivKz94SoZ06U7nTtfOsmuzZRncbXpRI"] |
|
|
|
count = var.use_cloudflare ? 0 : 1 |
|
} |
|
|
|
resource "cloudflare_zone" "main_site" { |
|
zone = var.static_site |
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_zone_settings_override" "main_site" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
settings { |
|
ssl = "strict" |
|
always_use_https = "on" |
|
automatic_https_rewrites = "on" |
|
} |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_record" "static_site" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
name = "${var.static_site}." |
|
value = "c.storage.googleapis.com" |
|
type = "CNAME" |
|
ttl = 1 |
|
proxied = true |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |
|
|
|
resource "cloudflare_record" "static_site_txt" { |
|
zone_id = cloudflare_zone.main_site[0].id |
|
name = "${var.static_site}." |
|
type = "TXT" |
|
ttl = 300 |
|
value = "google-site-verification=${var.google_site_verification}" |
|
|
|
count = var.use_cloudflare ? 1 : 0 |
|
} |