/ main.tf
main.tf
  1  locals {
  2    stage = var.stage != "" ? var.stage : terraform.workspace
  3  
  4    /* we're prepending "eu-" to the location to keep the same format as our
  5     * other tf provider modules (aws, gcp, do ,...)
  6     * all hetzner cloud data centers are in the EU */
  7    dc = "${var.provider_name}-eu-${var.location}"
  8  
  9    /* Got to add some default groups. */
 10    groups = distinct([local.dc, "${var.env}.${local.stage}", var.group])
 11  
 12    /* example: stable-large-01.he-eu-hel1.nimbus.default */
 13    host_suffix = "${local.dc}.${var.env}.${local.stage}"
 14  
 15    /* always add SSH, WireGuard, and Consul to allowed ports */
 16    open_tcp_ports = concat(["22", "8301"], var.open_tcp_ports)
 17    open_udp_ports = concat(["51820", "8301"], var.open_udp_ports)
 18  
 19    /* pre-generated list of hostnames */
 20    hostnames = toset([for i in range(1, var.host_count + 1) :
 21      "${var.name}-${format("%02d", i)}.${local.host_suffix}"
 22    ])
 23  }
 24  
 25  resource "hcloud_firewall" "host" {
 26    name = "${var.name}.${local.host_suffix}"
 27  
 28    /* TCP */
 29    dynamic "rule" {
 30      for_each = local.open_tcp_ports
 31      iterator = port
 32      content {
 33        direction = "in"
 34        protocol  = "tcp"
 35        port      = port.value
 36        source_ips = [
 37          "0.0.0.0/0",
 38          "::/0"
 39        ]
 40      }
 41    }
 42  
 43    /* UDP */
 44    dynamic "rule" {
 45      for_each = local.open_udp_ports
 46      iterator = port
 47      content {
 48        direction = "in"
 49        protocol  = "udp"
 50        port      = port.value
 51        source_ips = [
 52          "0.0.0.0/0",
 53          "::/0"
 54        ]
 55      }
 56    }
 57  }
 58  
 59  resource "hcloud_server" "host" {
 60    for_each     = local.hostnames
 61    name         = each.key
 62    image        = var.image
 63    server_type  = var.type
 64    location     = var.location
 65    ssh_keys     = var.ssh_keys
 66    firewall_ids = [hcloud_firewall.host.id]
 67  
 68    /* floating IPs need to be assigned manually */
 69    user_data = templatefile("${path.module}/user-data/floating_ip.sh", {
 70      floating_ip = hcloud_floating_ip.host[each.key].ip_address
 71    })
 72  
 73    /* Ignore changes in attributes like image */
 74    lifecycle {
 75      ignore_changes = [image, ssh_keys, user_data]
 76    }
 77  
 78    /* wait for cloud-init (ensures instance is fully booted before moving on)
 79     * if we don't wait the ansible provisioner will fail with connection errors
 80     */
 81    provisioner "remote-exec" {
 82      connection {
 83        user = "root"
 84        host = self.ipv4_address
 85      }
 86      inline = [
 87        "echo 'Waiting for cloud-init to complete...'",
 88        "cloud-init status --wait > /dev/null",
 89        "echo 'Completed cloud-init!'",
 90      ]
 91    }
 92  
 93    /* bootstraping access for later Ansible use */
 94    provisioner "ansible" {
 95      plays {
 96        playbook {
 97          file_path = "${path.cwd}/ansible/bootstrap.yml"
 98        }
 99  
100        hosts  = [self.ipv4_address]
101        groups = [var.group]
102  
103        extra_vars = {
104          hostname         = self.name
105          data_center      = local.dc
106          stage            = local.stage
107          env              = var.env
108          ansible_ssh_user = var.ssh_user
109        }
110      }
111    }
112  }
113  
114  resource "hcloud_floating_ip" "host" {
115    for_each      = local.hostnames
116    type          = "ipv4"
117    home_location = var.location
118  
119    lifecycle {
120      prevent_destroy = true
121    }
122  }
123  
124  resource "hcloud_floating_ip_assignment" "host" {
125    for_each       = local.hostnames
126    floating_ip_id = hcloud_floating_ip.host[each.key].id
127    server_id      = hcloud_server.host[each.key].id
128  }
129  
130  /* Optional resource when data_vol_size is set */
131  resource "hcloud_volume" "host" {
132    for_each  = toset([for h in local.hostnames : h if var.data_vol_size != 0])
133    name      = "data-${replace(each.key, ".", "-")}"
134    server_id = hcloud_server.host[each.key].id
135    size      = var.data_vol_size
136  
137    lifecycle {
138      prevent_destroy = true
139      /* We do this to avoid destrying a volume unnecesarily */
140      ignore_changes = [name]
141    }
142  }
143  
144  resource "cloudflare_record" "host" {
145    for_each = local.hostnames
146    zone_id  = var.cf_zone_id
147    name     = hcloud_server.host[each.key].name
148    value    = hcloud_floating_ip.host[each.key].ip_address
149    type     = "A"
150    ttl      = 3600
151  }
152  
153  resource "ansible_host" "host" {
154    for_each           = local.hostnames
155    inventory_hostname = hcloud_server.host[each.key].name
156  
157    groups = local.groups
158  
159    vars = {
160      ansible_host = hcloud_floating_ip.host[each.key].ip_address
161  
162      hostname = hcloud_server.host[each.key].name
163      region   = hcloud_server.host[each.key].location
164  
165      dns_entry   = "${hcloud_server.host[each.key].name}.${var.domain}"
166      dns_domain  = var.domain
167      data_center = local.dc
168      stage       = local.stage
169      env         = var.env
170    }
171  }