Interview Coding Take-Homes: Part 2.b#
UCLA Health - Terraform Module#
This take-home was for a Senior Cloud DevOps Engineer position in a key IT organization at UCLA Health.
This is part b of a two-part take-home. Read the first part, Interview Coding Take-Homes: Part 2.a.
Prompt#
You are a terraform developer building a new module to help with naming resources deployed within the cloud environment. You will have two inputs to this module identifying a “base_name” and “resource_type” with which you will have to evaluate and generate a “resource_name” as an output. The “resource_name” that you generate should follow these rules:
You should setup the “resource_type” to only allow the values of “virtual_machine”, “key_vault”, and “storage_account”
If the resource_type is “virtual_machine” you should take the “base_name” and append “vm-” to the front of it and “-00” to the back. If the base name and and values you’re appending are greater than 15 characters you should truncate the “base_name” only in order to be 15 characters when combined with your additional characters. Note that the base_name can be greater than 15 characters.
If the resource_type is “key_vault” you should append “kv-” to the front of “base_name” and then set all characters to lower case.
If the resource_type is “storage_account” you should append “sa” to the front of “base_name and then remove all “-” from the name and then set all characters to lower case.
You should also build a secondary module that calls your naming module. This parent module should intake a map which has the “base_name” as the key and the “resource_type” as the value. It should present each key/value pair in this map to your naming module.
The Parent Module should reflect all the outputs of the child module and allow for additional outputs if the child module outputs are expanded in the future without further modification to the parent.
Please write these modules as you would for a production environment following best practices and structure that makes it easy to share with other team members and allows them to use it quickly after receiving it.
You can reference the naming module either through a local path or a github URL as long as the entire package can be run from a computer with public internet access.
My Approach#
Whew - that’s a lot to read.
Although I knew what Terraform is, my experience with it is very limited. So first and foremost, I read the Get Started - AWS tutorial. To avoid unintentionally incurring costs, I used LocalStack instead of AWS for my testing.
After completing the tutorial, I relied heavily on the Terraform documentation to complete the prompt.
Here is the project structure I ended up with.
The specifics of these files is documented in Resource Name Mapper.
resource_type
Constraints#
You should setup the “resource_type” to only allow the values of “virtual_machine”, “key_vault”, and “storage_account”
6variable "resource_type" {
7 description = "Must be one of `virtual_machine`, `key_vault`, or `storage_account`."
8 type = string
9
10 validation {
11 condition = contains(["virtual_machine", "key_vault", "storage_account"], var.resource_type)
12 error_message = "`resource_type` must be one of `virtual_machine`, `key_vault`, or `storage_account`."
13 }
14}
This requirement uses an input variable block that contains a validation block. The contains(list, value) function is used to constrain the input variable to the three acceptable values.
base_name
and resource_name
Alterations#
If the resource_type is “virtual_machine” you should take the “base_name” and append “vm-” to the front of it and “-00” to the back. If the base name and and values you’re appending are greater than 15 characters you should truncate the “base_name” only in order to be 15 characters when combined with your additional characters. Note that the base_name can be greater than 15 characters.
If the resource_type is “key_vault” you should append “kv-” to the front of “base_name” and then set all characters to lower case.
If the resource_type is “storage_account” you should append “sa” to the front of “base_name and then remove all “-” from the name and then set all characters to lower case.
2 base_name = (
3 var.resource_type == "virtual_machine" ? substr(var.base_name, 0, 15 - length("vm-") - length("-00")) :
4 var.resource_type == "key_vault" ? lower(var.base_name) :
5 lower(replace(var.base_name, "-", "")) # storage account
6 )
This requirement is handled by a lengthy conditional expression.
Line 3 is specific to virtual_machine
’s base_name
. As the requirements
state, we truncate the base_name
to 15 characters, minus the length of
the vm-
prefix and -00
suffix we add to the resource_name
.
Both key_vault
and storage_account
are handled here as well.
8 resource_name = (
9 var.resource_type == "virtual_machine" ? "vm-${local.base_name}-00" :
10 var.resource_type == "key_vault" ? "kv-${local.base_name}" :
11 "sa${local.base_name}" # storage account
12 )
And here we use another conditional expression to add the prefix and suffix to the resource names.
Secondary Module#
You should also build a secondary module that calls your naming module. This parent module should intake a map which has the “base_name” as the key and the “resource_type” as the value. It should present each key/value pair in this map to your naming module.
I’ve named this module batch
because it processes a “batch” of resources together.
1variable "resources" {
2 description = "A map of base names and resource types."
3 type = map(string) # {"app":"virtual_machine", "data":"storage_account", ...}
4}
The module takes the input resources
as a string
map.
1module "naming" {
2 for_each = var.resources
3 source = "../naming"
4
5 base_name = each.key
6 resource_type = each.value
7}
The batch
module uses the naming
module. From the resources
input variable, base_name
is set to its key, and resource_type
is set to its value.
The Parent Module should reflect all the outputs of the child module and allow for additional outputs if the child module outputs are expanded in the future without further modification to the parent.
1output "resource_names" {
2 description = "A map of base names to formatted resource names."
3 value = { for k, v in module.naming: k => v.resource_name }
4}
The module sets the resource_names
output
variable as an object
that reflects the outputs from the naming
module.
6output "all_naming_outputs" {
7 value = module.naming
8}
The requirement for “future” outputs wasn’t completely clear to me, so I
opted to just return all possible outputs from the naming
module.
This way, any new outputs added to that module will also output in the
batch
module as the all_naming_outputs
output variable.
Production Quality#
Please write these modules as you would for a production environment following best practices and structure that makes it easy to share with other team members and allows them to use it quickly after receiving it.
To the best of my knowledge, I followed Terraform’s best practices when writing these modules, and the structure (the names of the files and their contents) reflects what I found in their documentation.
Tests#
Of course, any production system needs tests. I added several tests for both modules.
Their contents are truncated here, but you can download the files to see each test.
1run "long_vm_name_is_truncated" {
2 command = plan
3 module {
4 source = "./batch"
5 }
6
7 variables {
8 resources = {
9 "fooBARbaz-FOObarBAZ" = "virtual_machine"
10 }
11 }
12
13 assert {
14 condition = output.resource_names["fooBARbaz-FOObarBAZ"] == "vm-fooBARbaz-00"
15 error_message = "VM name is wrong"
16 }
17}
1run "resource_type_can_be_virtual_machine" {
2 command = plan
3 module {
4 source = "./naming"
5 }
6
7 variables {
8 base_name = "foo"
9 resource_type = "virtual_machine"
10 }
11
12 assert {
13 condition = output.resource_name == "vm-foo-00"
14 error_message = "VM name is wrong"
15 }
16}
Need additional help? Consider contacting me on