Let’s revisit the requirements I put together for the Cloud Accounts module, and then we can delve into the code and see how each of these requirements was addressed.
- Configure a cloud account with only specified vSphere Datacenters enabled.
- Must be able to handle multiple key/value pairs to implement capability tags (these are a vRA construct).
- Return the ID of the created cloud account so that it can be used for downstream modules.
- Support for multiple cloud accounts. This would have added unnecesary complexity to the user input model, and made assumptions about the capability tags being consistent between multiple cloud accounts.
TLDR: repo link.
main.tf - take 1
To build the Cloud Accounts module, I started off with some Terraform code. It looked a lot like this:
Now, to be honest this is more take 1.5 than take 1. I kept getting HTTP 400 responses when creating the cloud account if I had a space in the name. Hence the use of the replace function on line 11.
Once I got past that, this code got me off to a reasonable start - it created the Cloud Account, and the use of the dynamic block handled the possibility that people could throw any number of key/pair values as capability tags.
Where things started to fall apart was that I couldn’t find any way to constrain the region enumeration to only return the values of specific Datacenters by name.
The schema below is what came back from the vra_region_enumeration resource.
As you can see, there is no way to parse the response by the name of the datacenter because the object doesn’t contain datacenter names at all. The closest thing we have is the regions attribute, which contains the vCenter managed object id (MOID) of the datacenters, but with “Datacenter:” prepended to the value.
Knowing what format I need to provide to the regions attribute, I started to get a little more creative. After all, to create the Cloud Account, you need to provide authentication details for vCenter. If you have these details already, surely you could use them to query vSphere directly?
main.tf - take 2
Time to make a few changes, based on what didn’t work for me before.
What changed from my first take? Firstly, the addition of the vsphere_datacenter data source. This takes a Datacenter name, and returns the MOID. Since you may want to enable more than one datacenter, I’ve used the for_each expression to read in the datacenter name from the values provided. The toset function converts the list of values into a set so that the for_each expression can read from it.
Secondly, the value for the regions attribute for the cloud account resource (line 17). Since we are using for_each we need to parse the data.vsphere_datacenter.this model slightly differently. It returns us a set, and so we need to us the for expression to return the ids that we are after. The final piece of work on that expression is to append “Datacenter:” to the returned value, which is delivered with the format function.
This worked pretty well, and covered off the first requirement. Combined with the dynamic block for tags, all that was left was to work on the outputs.
main.tf - take 3
Or so I thought. Since I don’t have my own vRA environment, I have been using vRA Cloud to build this out, which has a mandatory requirement for a data collector to reverse proxy connectivity into an on-premises environment. This requirement becomes optional in the world of vRA installed in your datacenter, so I needed to update my code with some logic to handle both of these scenarios.
Line 2: Checks the data_collector_name variable, and if it isn’t empty (the default value) then it sets the count to 1. If it is empty it sets the count to 0. Very good. Now to handle the dcid attribute of the cloud_account resource.
Line 17: This also reads the data_collector_name variable, and checks it is not empty. If true, it pulls through the id from our data_collector data source, otherwise it sets it to blank.
Alright, now I can get on with the outputs.
outputs.tf - take 1
By this point in time, I thought the hard work was over. All I needed to do was provide the relevant outputs from the created resources. The cloud account id is used for the creation of other resources, so I need to provide that. To make it easy to reference, I also wanted to provide the cloud account name.
At this point, all that is left is to share the details of enabled datacenters.
outputs.tf - take 2
Now, something to know about vRA is that different constructs will need different references to the datacenter when you are configuring them. Some want the “Datacenter:MOID” format we used above, and some want the vRA assigned unique ID. So we need to present both of those values back as region and region_id resepctively. We also want the name, as something human readable. Let’s take a look inside the state file and see what we can pull from the cloud account resource.
As you can see, regions and region_ids are both populated, but the datacenter name is not.
Conversely, if we look at the contents of one of the vsphere_datacenter resources, we get the MOID and the name.
I guess we are going to need to do some magic. I need to correlate values from a few different resources to get the output I’m looking for.
First, I’m going to share the code, and then we will walk through what is going on.
Line 10: First of all, I’m using the data.vsphere_datacenter.this as my leading resource. Since we are using a for_each function, it will return me a tuple that I need to loop through with a for statement.
Line 11: This needs no explanation.
Line 12: We add “Datacenter:” to the beginning of the ID so it matches the format needed by vRA for subsequent operations.
Line 13: This is where we go hunting for the region_id value. Let’s start at the end of the line and work backwards, because it will make more sense if we do it that way.
The index function returns the position of an element in a list. The first argument is the list you want to inspect, and the second is the element you want to search for. In laymans terms we are saying “go take a look at the list in vra_cloud_account_vsphere.this.regions, and tell me what position Datacenter:foo is in”. This will return us an integer.
This integer is valuable because now I can go an look for the nth element in the region_id attribute, and know that it will be for the same resource. We do that with the element function. Similarly to index function, the first argument is the list we want to query, while the second argument is the position of the element in the index that we want to return.
Note: When you are working on multi-part expressions like these, take a look at the terraform console command. It gives you an interactive shell where you can validate your expressions.
Bringing it together
Here is the code from the examples folder of the module repo.
Modules are a bit of work to get going, but they allow you to make it far easier for people to get up and running with both Terraform, and more importantly the platform that Terraform is exposing.
In the next post, we will run through this same procedure, but with vRA Cloud Zones.