/ main.tf
main.tf
  1  /* DERIVED --------------------------------------*/
  2  locals {
  3    stage = var.stage != "" ? var.stage : terraform.workspace
  4    dc    = "${var.provider_name}-${var.region}"
  5    /* always add SSH, WireGuard, and Consul to allowed ports */
  6    open_tcp_ports = concat(["22", "8301"], var.open_tcp_ports)
  7    open_udp_ports = concat(["51820", "8301"], var.open_udp_ports)
  8    /* tags for the dropplet */
  9    tags        = [local.stage, var.group, var.env]
 10    tags_sorted = sort(distinct(local.tags))
 11    /* pre-generated list of hostnames */
 12    hostnames = [for i in range(1, var.host_count + 1) :
 13      "${var.name}-${format("%02d", i)}.${local.dc}.${var.env}.${local.stage}"
 14    ]
 15  }
 16  /* RESOURCES ------------------------------------*/
 17  
 18  resource "digitalocean_tag" "host" {
 19    for_each = toset(local.tags_sorted)
 20  
 21    name  = each.key
 22  }
 23  
 24  /* Optional resource when vol_size is set */
 25  resource "digitalocean_volume" "host" {
 26    for_each = toset([ for h in local.hostnames : h if var.data_vol_size > 0 ])
 27  
 28    name   = "data-${replace(each.key, ".", "-")}"
 29    region = var.region
 30    size   = var.data_vol_size
 31  
 32    lifecycle {
 33      prevent_destroy = true
 34      /* We do this to avoid destrying a volume unnecesarily */
 35      ignore_changes = [name]
 36    }
 37  }
 38  
 39  resource "digitalocean_droplet" "host" {
 40    for_each = toset(local.hostnames)
 41  
 42    name     = each.key
 43    image    = var.image
 44    region   = var.region
 45    size     = var.type
 46    ssh_keys = var.ssh_keys
 47    ipv6     = true
 48  
 49    tags = [for tag in digitalocean_tag.host : tag.id]
 50  
 51    /* This can be optional, ugly as hell but it works */
 52    volume_ids = var.data_vol_size > 0 ? [digitalocean_volume.host[each.key].id] : null
 53  
 54    /* Ignore changes in attributes like image */
 55    lifecycle {
 56      ignore_changes = [image, ssh_keys]
 57    }
 58  }
 59  
 60  resource "digitalocean_floating_ip" "host" {
 61    for_each = digitalocean_droplet.host
 62  
 63    droplet_id = each.value.id
 64    region     = each.value.region
 65  
 66    lifecycle {
 67      prevent_destroy = false
 68    }
 69  }
 70  
 71  resource "digitalocean_firewall" "host" {
 72    name        = "${var.name}.${local.dc}.${var.env}.${local.stage}"
 73    droplet_ids = [for name, droplet in digitalocean_droplet.host : droplet.id]
 74  
 75    /* Allow ICMP pings */
 76    inbound_rule {
 77      protocol         = "icmp"
 78      source_addresses = ["0.0.0.0/0", "::/0"]
 79    }
 80    outbound_rule {
 81      protocol              = "icmp"
 82      destination_addresses = ["0.0.0.0/0", "::/0"]
 83    }
 84  
 85    /* TCP */
 86    dynamic "inbound_rule" {
 87      iterator = port
 88      for_each = local.open_tcp_ports
 89      content {
 90        protocol         = "tcp"
 91        port_range       = port.value
 92        source_addresses = ["0.0.0.0/0", "::/0"]
 93      }
 94    }
 95  
 96    /* UDP */
 97    dynamic "inbound_rule" {
 98      iterator = port
 99      for_each = local.open_udp_ports
100      content {
101        protocol         = "udp"
102        port_range       = port.value
103        source_addresses = ["0.0.0.0/0", "::/0"]
104      }
105    }
106  
107    /* Open for all outgoing connections */
108    dynamic "outbound_rule" {
109      iterator = protocol
110      for_each = ["tcp", "udp"]
111      content {
112        protocol              = protocol.value
113        port_range            = "all"
114        destination_addresses = ["0.0.0.0/0", "::/0"]
115      }
116    }
117  }
118  
119  resource "null_resource" "host" {
120    for_each = digitalocean_droplet.host
121  
122    /* Trigger bootstrapping on host or public IP change. */
123    triggers = {
124      droplet_id = each.value.id
125      #floatin_ip_id  = digitalocean_floating_ip.host[count.index].id
126    }
127  
128    /* Make sure everything is in place before bootstrapping. */
129    depends_on = [
130      digitalocean_volume.host,
131      digitalocean_droplet.host,
132      digitalocean_floating_ip.host,
133      digitalocean_firewall.host,
134    ]
135  
136    /* bootstraping access for later Ansible use */
137    provisioner "ansible" {
138      plays {
139        playbook {
140          file_path = "${path.cwd}/ansible/bootstrap.yml"
141        }
142  
143        hosts  = [each.value.ipv4_address]
144        groups = [var.group]
145  
146        extra_vars = {
147          hostname     = each.key
148          ansible_user = var.ssh_user
149          data_center  = local.dc
150          stage        = local.stage
151          env          = var.env
152        }
153      }
154    }
155  }
156  
157  resource "cloudflare_record" "host_ipv4" {
158    for_each = digitalocean_droplet.host
159  
160    zone_id = var.cf_zone_id
161    name    = each.key
162    value   = digitalocean_floating_ip.host[each.key].ip_address
163    type    = "A"
164    ttl     = 3600
165  }
166  
167  resource "cloudflare_record" "host_ipv6" {
168    for_each = digitalocean_droplet.host
169  
170    zone_id = var.cf_zone_id
171    name    = each.key
172    value   = digitalocean_droplet.host[each.key].ipv6_address
173    type    = "AAAA"
174    ttl     = 3600
175  }
176  
177  resource "ansible_host" "host" {
178    for_each = digitalocean_droplet.host
179  
180    inventory_hostname = each.key
181  
182    groups = ["${var.env}.${local.stage}", var.group, local.dc]
183  
184    vars = {
185      ansible_host = digitalocean_floating_ip.host[each.key].ip_address
186      hostname     = each.key
187      region       = each.value.region
188      dns_entry    = "${each.key}.${var.domain}"
189      dns_domain   = var.domain
190      data_center  = local.dc
191      stage        = local.stage
192      env          = var.env
193    }
194  }