1

Background

I am using the digitalocean_droplet resource. I would like to create multiple digitalocean droplets, each with their own SSH key.

The droplets will be created by reading locals variable named droplets at the top of the script for ease of modification, and each droplet must have its own SSH key. I am using the cloudposse/terraform-tls-ssh-key-pair module to create the key pair.

Problem

The digitalocean_droplet resource requires an array of IDs under the ssh_keys key. I know how to provide a single key here, but I am using for_each for both the module and for the digitalocean_ssh_key resource.

I don't know how to tell the digitalocean_droplet resource to, for ssh_keys, use multiple values from the digitalocean_ssh_key resource.

To use a single key, ssh_keys would look like this:

ssh_keys = [digitalocean_ssh_key.ssh_key.id]

To use multiple, I think that I need to tell ssh_keys to "use each of the values generated by the digitalocean_ssh_key resource". But I don't know how to do this.

I expect (and hope) this to simply be a lack of knowledge on HCL.

What I have tried

I have tried what I think should be telling ssh_keys to use each .id from the digitalocean_ssh_key resource:

ssh_keys = {
  for index, v in digitalocean_ssh_key.ssh_key:
    v => v.id
}

However this results in a cycle error on running terraform plan:

Error: Cycle: module.ssh_key_pair.output.private_key (expand), module.ssh_key_pair.var.chmod_command (expand), module.ssh_key_pair.null_resource.chmod, module.ssh_key_pair.output.public_key (expand), module.ssh_key_pair.var.private_key_extension (expand) [etc etc]

I have also tried using module.ssh_key_pair, but this does not provide me with an id, which is what ssh_keys requires.

Conclusion

What do I need to do to tell digitalocean_droplet to, for each of the configuration in my locals variable named "droplets", generate a new SSH key using my module and to assign that SSH key to each individual droplet?

Code

locals {
  // An example of a single droplet. I will add multiple here in the future.
  droplets = {
    "DROPLET_NAME_AS_KEY" : {
      image = "distro image here"
      size  = "droplet size here"
    }
  }
}

// For each droplet above, create a new digitalocean droplet
resource "digitalocean_droplet" "droplets" {
  for_each = local.droplets
  name     = each.key
  region   = "fra1"
  image    = each.value.image
  size     = each.value.size
  tags     = [each.key]
  // Next is my problem, what goes for ssh_keys??
  ssh_keys = ???
}

// Create a new key pair for each droplet.
module "ssh_key_pair" {
  for_each = digitalocean_droplet.droplets

  source              = "git::https://github.com/cloudposse/terraform-tls-ssh-key-pair.git?ref=master"
  ssh_public_key_path = "/users/me/.ssh"
  name                = "${each.value.name}"
}

// The link between the key pair resource and the ssh_key for digital ocean.
resource "digitalocean_ssh_key" "ssh_key" {
  for_each = module.ssh_key_pair

  name       = each.value.key_name
  public_key = each.value.public_key
}

Bonus question if the above gets solved:

I actually use variables for all the values in the locals array. These come from environment variables from docker-compose, as I'm running terraform in a container. Is there a simple way to provide an array in docker-compose which can become this array here without me having to update both the environment key in docker-compose.yml and this droplets array, and instead just update an array in docker-compose.yml?

Jimbo
  • 25,790
  • 15
  • 86
  • 131
  • 1
    I'd probably extend the local variable to have e.g., a name for the SSH key under the `DROPLET_NAME_AS_KEY` and then use the local variable in the SSH resource instead of using resource chaining as that will cause cycle errors you already experienced. – Marko E Feb 25 '23 at 12:45
  • 1
    Thanks @MarkoE, I've actually just solved this in a different way. I made the `ssh_key_pair` module also loop `local.droplets`, and in the `ssh_keys` used a for loop to filter `ssh_key` where the key name is the same as the one I'm looping through in the `for_each` in `digitalocean_droplet`. – Jimbo Feb 25 '23 at 12:50
  • 1
    But if storing this in the local variable is easier (it is dynamic, and the name for the key is based on the name in locals) - I'll give this a try too. – Jimbo Feb 25 '23 at 12:50

1 Answers1

1

You should use local.droplets in all three for_each loops to avoid cycles.

You can then access module.ssh_key_pair module in digitalocean_ssh_key resource with module.ssh_key_pair[each.key].

The ssh_keys field in digitalocean_droplet resource would be ssh_keys = [digitalocean_ssh_key.ssh_key[each.key].fingerprint].


Regarding your bonus question - you could declare this variable:

variable "droplets" {
  type = map(object({
    image = string
    size = string
  }))
}

And then in for_each use var.droplets instead of locals.droplets.

You can use env variables to set Terraform variables, however:

For readability, and to avoid the need to worry about shell escaping, we recommend always setting complex variable values via variable definitions files.

See details here - https://developer.hashicorp.com/terraform/language/values/variables#assigning-values-to-root-module-variables.

Anton
  • 1,793
  • 10
  • 20
  • Hey Anton, actually I used `local.droplets` in the `ssh_key_pair` module, and this removed the cycle error I was receiving. Why do I need to also do it in the `digitalocean_ssh_key` resource? For the bonus question, it isn't clear what the key I would provide in docker-compose under `environment` would look like that would work with this variable object map you are suggesting - how would that look? – Jimbo Feb 25 '23 at 13:58
  • @Jimbo it's not a must to use `local.droplets` in `digitalocean_ssh_key` for_each, it's a personal preference, this way it's easier to see that all 3 resources/modules are a part of one thing. About vars - just collapse the map variable into a one-line string. The link I added in the answer has this example for a list var: `export TF_VAR_availability_zone_names='["us-west-1b","us-west-1d"]'`. – Anton Feb 25 '23 at 14:31