nbering / terraform-provider-ansible

"Logical" provider for integrating with an Ansible Dynamic Inventory script.
https://nbering.github.io/terraform-provider-ansible/
Mozilla Public License 2.0
329 stars 64 forks source link

ansible_host vars dictionary can't handle arrays #13

Open nbering opened 6 years ago

nbering commented 6 years ago

This one's an interesting edge case.

I wanted to pass an array of hostnames to a variable...

resource "ansible_host" "example" {
  inventory_hostname = "example.com"
  vars {
    certificate_domains = [ 
      "${dnsimple_record.domain1.hostname}",
      "${dnsimple_record.domain2.hostname}"
    ]
  }
}

This received an error like this:

Error: ansible_host.example: vars (certificate_domains): '' expected type 'string', got unconvertible type '[]interface {}'

This might be one of those cases where the intersection of the Go and Terraform type systems are not going to do what I want.

I'm thinking about adding a resource along the lines of what I proposed in #4, but specifically to handle the array case. Maybe something like:

# This is not a resource that exists now, just an example of what could be added.
resource "ansible_host_array_var" "example" {
    inventory_hostname = "example.com"
    key                = "certificate_domains"
    value              = ["${dnsimple_record.domain1.hostname}", "${dnsimple_record.domain2.hostname}"]
}
nbering commented 6 years ago

After some further thought, it occurs to me that supporting arrays still doesn't allow for complex objects, and lists of complex objects.

It might make more sense to have a mechanism whereby the dynamic inventory script will read the String field as raw YAML.

jtopjian commented 6 years ago

It might make more sense to have a mechanism whereby the dynamic inventory script will read the String field as raw YAML.

Terraform has a native jsonencode function that will convert something into JSON. This might be helpful here, but it pushes complexity to the Inventory script to detect if a value is JSON or not.

I'm thinking about adding a resource along the lines of what I proposed in #4, but specifically to handle the array case. Maybe something like:

This is a good idea as well - doing the opposite of the above and pushing the complexity back to the user. If you go this route, it would make sense to also consider a ansible_host_map_var resource, too. And maybe equivalents for groups, too.

There's a lot of precedence for embedding JSON into Terraform resources. It's an acceptable method, but it's looked as a last resort. The Terraform team is working on HCL 2 which might resolve this issue.

Personally I like the individual resources. Several small resources allow for greater flexibility in the long run. Larger individual resources with lots of functionality always become complicated to manage. Another benefit of the individual resources is that if this issue is natively fixed upstream in the future, the individual resources can be slowly deprecated and removed.

nbering commented 6 years ago

Whatever the solution is, the inventory script/plugin will need to understand that the value it is passing needs to be encoded as json/yaml structures and not a string.

I'm not an expert Go programmer by any means, and there could be tricks to work around Terraform's type contract limitations... but I think embedding JSON/YAML as a string or Heredoc is probably the most flexible way to complete this task.

Validation might be a bit of an issue. It might be worth the time to find a JSON/YAML validator library in Go to ensure the value is valid before adding it to the tfstate file. That would avoid complex downstream errors, especially for anyone running in a operations pipeline configuration.

jtopjian commented 6 years ago

Whatever the solution is, the inventory script/plugin will need to understand that the value it is passing needs to be encoded as json/yaml structures and not a string.

Yes, true. My interpretation of using multiple resources (ansible_host_array_var, ansible_host_map_var, etc) was that while the data would be stored as JSON in the TF state, the inventory script could safely assume what type of data to expect.

there could be tricks to work around Terraform's type contract limitations...

This is a hard limitation with Terraform's current HCL implementation. Having the resource support a JSON-body-as-a-string is the common workaround at this time. I haven't yet seen a YAML implementation, but I think everything discussed about JSON can just as well be applied to YAML.

but I think embedding JSON/YAML as a string or Heredoc is probably the most flexible way to complete this task.

Yes, it is.

From a data-modeling point of view, it's not the best solution since it treats a collection of data as a single unit. What I mean by this is that allowing free-form JSON/YAML would allow someone to also include package_versions bundled with certificate_domains in the same input -- two separate and unrelated pieces of data grouped together as one unit.

This is why I like the multiple resource solution better, but I'm not opposed to the JSON/YAML input, either. I fully appreciate the simplicity of the free-form JSON solution and any other solution is to satisfy data structure nit-pickers :smile:

Validation might be a bit of an issue. It might be worth the time to find a JSON/YAML validator library in Go to ensure the value is valid before adding it to the tfstate file.

This is actually pretty easy. I had to do this a few months ago with the rabbitmq_queue resource and my solution was copied straight from the AWS provider:

https://github.com/terraform-providers/terraform-provider-rabbitmq/blob/master/rabbitmq/resource_queue.go#L62-L67

https://github.com/terraform-providers/terraform-provider-rabbitmq/blob/master/rabbitmq/util.go#L20-L48

Note how the schema definition of arguments_json has a ValidateFunc built in to determine if the input is valid JSON.

If you go this route, I think it would be best to have the argument named something like var_json or var_yaml so that the inventory parsing script knows what to expect rather than, say, trying to automatically determine the type of data by parsing for JSON, upon failure, parse for YAML, upon failure, treat as a string.

nbering commented 6 years ago

Thanks for the input! If I have time during the evening this week, I plan to tackle this.