From 05c7c2c423ca282b847b976cbe23a55f6e414444 Mon Sep 17 00:00:00 2001 From: Dan Buch Date: Tue, 11 Feb 2020 20:02:22 -0500 Subject: [PATCH] Sprinkle on some dynamic blocks --- main.tf | 219 ++++++++++++++++++++--------------- nginx-sites-default.conf.tpl | 23 ++++ outputs.tf | 19 --- spoke-app-provision | 53 ++++++--- variables.tf | 61 ---------- 5 files changed, 185 insertions(+), 190 deletions(-) create mode 100644 nginx-sites-default.conf.tpl delete mode 100644 outputs.tf delete mode 100644 variables.tf diff --git a/main.tf b/main.tf index 3584f49..6e077a9 100644 --- a/main.tf +++ b/main.tf @@ -1,84 +1,88 @@ -resource "digitalocean_ssh_key" "app" { - count = length(var.ssh_keys) - name = "${var.resource_prefix}app-${count.index}" - public_key = element(var.ssh_keys, count.index) +variable "base_url" { + description = "Fully qualified https URL of the app" } -resource "digitalocean_database_cluster" "pg" { - name = "${var.resource_prefix}pg" - engine = "pg" - version = "11" - size = var.database_cluster_size - region = var.region - node_count = 1 +variable "resource_prefix" { + description = "Prefix prepended to resource names" + default = "spoke-" } -resource "digitalocean_droplet" "app" { - image = "ubuntu-18-04-x64" - name = "${var.resource_prefix}app" - region = var.region - size = var.droplet_size +variable "node_options" { + description = "Value defined at build time and run time as NODE_OPTIONS" + default = "--max_old_space_size=8192" +} - ssh_keys = [digitalocean_ssh_key.app.*.id] +variable "node_env" { + description = "Value defined at build time and run time as NODE_ENV" + default = "production" } -resource "digitalocean_certificate" "app" { - name = "${var.resource_prefix}app" - private_key = var.cert_private_key - leaf_certificate = var.cert_leaf_certificate +variable "port" { + description = "TCP port used to communicate between droplet and nginx" + default = "3000" +} - lifecycle { - create_before_destroy = true - } +variable "droplet_size" { + description = "Size value passed when provisioning app droplet" + default = "s-1vcpu-1gb" } -resource "digitalocean_loadbalancer" "app" { - name = "${var.resource_prefix}lb-app" - region = var.region - droplet_ids = [digitalocean_droplet.app.id] - redirect_http_to_https = true +variable "region" { + description = "Region in which all resources will be provisioned" + default = "nyc1" +} - forwarding_rule { - entry_port = 80 - entry_protocol = "http" +variable "ssh_keys" { + type = "list" + description = "List of ssh public keys to pass to droplet provisioning" +} - target_port = var.port - target_protocol = "http" - } +variable "cert_private_key" { + description = "Certificate key to pass to nginx" +} - forwarding_rule { - entry_port = 443 - entry_protocol = "https" +variable "cert_certificate" { + description = "Certificate with leaf and intermediates to pass to nginx" +} - target_port = var.port - target_protocol = "http" +variable "env" { + type = "map" + description = "Arbitrary *additional* environment variables passed at build time and run time" + default = {} +} - certificate_id = digitalocean_certificate.app.id - } +resource "digitalocean_ssh_key" "app" { + count = length(var.ssh_keys) + name = "${var.resource_prefix}app-${count.index}" + public_key = element(var.ssh_keys, count.index) +} - healthcheck { - port = var.port - protocol = "tcp" - } +resource "digitalocean_droplet" "app" { + image = "ubuntu-18-04-x64" + name = "${var.resource_prefix}app" + region = var.region + size = var.droplet_size + + ssh_keys = [digitalocean_ssh_key.app[*].id] +} + +resource "digitalocean_floating_ip" "app" { + droplet_id = digitalocean_droplet.app.id + region = digitalocean_droplet.app.region } resource "digitalocean_firewall" "app" { - name = "${var.resource_prefix}app" + name = "pghdsa-spoke-app" droplet_ids = [digitalocean_droplet.app.id] - inbound_rule { - protocol = "tcp" - port_range = "22" - source_addresses = ["0.0.0.0/0", "::/0"] - } - - inbound_rule { - protocol = "tcp" - port_range = "1-65535" - # FIXME: what - #port_range = var.port - source_load_balancer_uids = [digitalocean_loadbalancer.app.id] + dynamic "inbound_rule" { + for_each = ["22", "80", "443"] + content { + protocol = "tcp" + port_range = inbound_rule.value + source_addresses = ["0.0.0.0/0", "::/0"] + } } inbound_rule { @@ -86,16 +90,13 @@ resource "digitalocean_firewall" "app" { source_addresses = ["0.0.0.0/0", "::/0"] } - outbound_rule { - protocol = "tcp" - port_range = "1-65535" - destination_addresses = ["0.0.0.0/0", "::/0"] - } - - outbound_rule { - protocol = "udp" - port_range = "1-65535" - destination_addresses = ["0.0.0.0/0", "::/0"] + dynamic "outbound_rule" { + for_each = ["tcp", "udp"] + content { + protocol = outbound_rule.value + port_range = "1-65535" + destination_addresses = ["0.0.0.0/0", "::/0"] + } } outbound_rule { @@ -109,10 +110,13 @@ resource "random_string" "session_secret" { special = false } +resource "random_string" "pg_password" { + length = 31 +} + resource "null_resource" "app_provision" { triggers = { droplet_id = digitalocean_droplet.app.id - database_cluster_id = digitalocean_database_cluster.pg.id provision_script_sha1 = filesha1("spoke-app-provision") run_script_sha1 = filesha1("spoke-app-run") service_sha1 = filesha1("spoke.service") @@ -141,26 +145,46 @@ resource "null_resource" "app_provision" { } provisioner "file" { - content = templatefile("app.env.tpl", merge({ - ASSETS_MAP_FILE = "assets.json", - ASSETS_DIR = "./build/client/assets", - BASE_URL = var.base_url, - DATABASE_URL = digitalocean_database_cluster.pg.uri, - DB_HOST = digitalocean_database_cluster.pg.host, - DB_NAME = digitalocean_database_cluster.pg.database, - DB_PASSWORD = digitalocean_database_cluster.pg.password, - DB_PORT = digitalocean_database_cluster.pg.port, - DB_TYPE = "pg", - DB_USER = digitalocean_database_cluster.pg.user, - DB_USE_SSL = "true", - JOBS_SAME_PROCESS = "1", - NODE_ENV = var.node_env, - NODE_OPTIONS = var.node_options, - OUTPUT_DIR = "./build", - PORT = var.port, - REDIS_URL = "redis://127.0.0.1:6379/0", - SESSION_SECRET = random_string.session_secret.result, - }, var.env)) + content = templatefile("nginx-sites-default.conf.tpl", { + server_name = var.server_name, + port = var.port, + }) + destination = "/tmp/nginx-sites-default.conf" + } + + provisioner "file" { + source = var.cert_pem_file + destination = "/tmp/spoke.crt" + } + + provisioner "file" { + source = var.key_pem_file + destination = "/tmp/spoke.key" + } + + provisioner "file" { + content = templatefile("app.env.tpl", { + env = merge({ + ASSETS_MAP_FILE = "assets.json", + ASSETS_DIR = "./build/client/assets", + BASE_URL = var.base_url, + DATABASE_URL = "postgres://spoke:${random_string.pg_password.result}@127.0.0.1:5432/spoke", + DB_HOST = "localhost", + DB_NAME = "spoke", + DB_PASSWORD = random_string.pg_password.result, + DB_PORT = "5432", + DB_TYPE = "pg", + DB_USER = "spoke", + DB_USE_SSL = "true", + JOBS_SAME_PROCESS = "1", + NODE_ENV = var.node_env, + NODE_OPTIONS = var.node_options, + OUTPUT_DIR = "./build", + PORT = var.port, + REDIS_URL = "redis://127.0.0.1:6379/0", + SESSION_SECRET = random_string.session_secret.result, + }, var.env) + }) destination = "/tmp/app.env" } @@ -178,11 +202,14 @@ resource "null_resource" "app_provision" { } } -resource "digitalocean_database_firewall" "app_pg" { - cluster_id = digitalocean_database_cluster.pg.id +output "droplet_urn" { + value = digitalocean_droplet.app.urn +} - rule { - type = "droplet" - value = digitalocean_droplet.app.id - } +output "droplet_ipv4_address" { + value = digitalocean_droplet.app.ipv4_address +} + +output "floating_ip_address" { + value = digitalocean_floating_ip.app.ip_address } diff --git a/nginx-sites-default.conf.tpl b/nginx-sites-default.conf.tpl new file mode 100644 index 0000000..606a848 --- /dev/null +++ b/nginx-sites-default.conf.tpl @@ -0,0 +1,23 @@ +# vim:filetype=nginx +server { + listen 80; + listen [::]:80; + listen 443 ssl; + listen [::]:443 ssl; + server_name ${server_name}; + ssl_certificate spoke.crt; + ssl_certificate_key spoke.key; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers HIGH:!aNULL:!MD5; + access_log /var/log/nginx/spoke.access.log combined; + index index.html; + root /home/spoke/app/build/client; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_pass http://127.0.0.1:${port}; + } +} diff --git a/outputs.tf b/outputs.tf deleted file mode 100644 index 4877721..0000000 --- a/outputs.tf +++ /dev/null @@ -1,19 +0,0 @@ -output "loadbalancer_ip" { - value = digitalocean_loadbalancer.app.ip -} - -output "droplet_urn" { - value = digitalocean_droplet.app.urn -} - -output "loadbalancer_urn" { - value = digitalocean_loadbalancer.app.urn -} - -output "database_cluster_urn" { - value = digitalocean_database_cluster.pg.urn -} - -output "droplet_ipv4_address" { - value = digitalocean_droplet.app.ipv4_address -} diff --git a/spoke-app-provision b/spoke-app-provision index 5c8a008..6ab01a9 100755 --- a/spoke-app-provision +++ b/spoke-app-provision @@ -9,6 +9,7 @@ main() { _run_system0() { set -o xtrace + cd /tmp sudo swapon --show | if ! grep -q /swap; then sudo fallocate -l 8G /swap @@ -35,18 +36,44 @@ _run_system0() { curl \ git \ gnupg \ + nginx-full \ redis - sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" >/etc/apt/sources.list.d/pgdg.list' - curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - - sudo apt-get update -y - sudo apt-get install -y postgresql-client-11 - - curl -fsSL https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - - echo "deb https://dl.yarnpkg.com/debian/ stable main" | - sudo tee /etc/apt/sources.list.d/yarn.list - sudo apt-get update -y - sudo apt-get install -y --no-install-recommends yarn + dpkg --get-selections || true | + if ! grep -qE '^postgresql-client-11.+install$'; then + echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" | + sudo tee /etc/apt/sources.list.d/pgdg.list &>/dev/null + curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + sudo apt-get update -y + sudo apt-get install -y postgresql-11 postgresql-client-11 + fi + + pg_ctlcluster 11 main start + sudo -H -u postgres bash </dev/null nvm use 2>/dev/null - if [[ -f /tmp/app.env ]]; then - cp -v /tmp/app.env /home/spoke/app/.env - fi + cp -v /tmp/app.env /home/spoke/app/.env sha1sum /home/spoke/app/.env set -o allexport @@ -117,7 +142,7 @@ _run_spoke0() { yarn run prod-build rm -rf ./node_modules yarn install --production --ignore-scripts - echo "${git_ref}" >/home/spoke/yarn_prod_build_ref + echo "${git_head}" >/home/spoke/yarn_prod_build_ref } main "${@}" diff --git a/variables.tf b/variables.tf deleted file mode 100644 index 0aa9b32..0000000 --- a/variables.tf +++ /dev/null @@ -1,61 +0,0 @@ -variable "base_url" { - description = "Fully qualified https URL of the app" -} - -variable "resource_prefix" { - description = "Prefix prepended to resource names" - default = "spoke-" -} - -variable "node_options" { - description = "Value defined at build time and run time as NODE_OPTIONS" - default = "--max_old_space_size=8192" -} - -variable "node_env" { - description = "Value defined at build time and run time as NODE_ENV" - default = "production" -} - -variable "port" { - description = "TCP port used to communicate between droplet and load balancer" - default = "3000" -} - -variable "droplet_size" { - description = "Size value passed when provisioning app droplet" - default = "s-1vcpu-1gb" -} - -variable "database_cluster_size" { - description = "Size value passed when provisioning database cluster" - default = "db-s-1vcpu-1gb" -} - -variable "database_cluster_node_count" { - default = 1 -} - -variable "region" { - description = "Region at which all resources will be provisioned" - default = "nyc1" -} - -variable "ssh_keys" { - type = "list" - description = "List of ssh public keys to pass to droplet provisioning" -} - -variable "cert_private_key" { - description = "Certificate key to use when defining th cert used with the load balancer" -} - -variable "cert_leaf_certificate" { - description = "Leaf certificate to use when defining the cert used with the load balancer" -} - -variable "env" { - type = "map" - description = "Arbitrary *additional* environment variables passed at build time and run time" - default = {} -}