-
Notifications
You must be signed in to change notification settings - Fork 10.3k
Terraform testing framework: Dynamically set object properties from variables when testing modules #34534
Description
Terraform Version
Terraform v1.6.6
on linux_amd64Use Cases
Background
My team and I are working on a Terraform module (https://github.com/blinqas/station) that facilitates the setup of projects and workspaces in Terraform Cloud, in conjunction with managing associated GitHub repositories (holds TF code for each workspace) and Azure resources associated with each workload.
Example use case:
If someone needs to deploy a new application in azure, the platform team can just call the module and it will create:
- Everything we need in TFC
- Github repo and link this to the workspace
- Resource group in Azure
- Managed identity with owner at the resource group level and potential other permissions provided by the platform team.
The application team can then create add their own Terraform resources in the new repository to deploy the infrastructure needed for their application. This makes it much faster to deploy new resources and the platform team has full control over the permissions given to each project.
Current issue
One of the core components of our module is the tfe block that creates a new workspace for each new workload. This is where we define essential attributes like organization, project name, workspace name, TF/ENV variables, etc. In the current implementation, these details are provided as variables to the module.
Example
module "web_app_1" {
source = "git::https://github.com/blinqas/station.git?ref=trunk"
environment_name = "prod"
resource_group_name = "web-app-1"
tags = local.tags.common
tfe = {
organization_name = "myTFCorg"
project_name = "Station"
workspace_name = "web-application-1"
workspace_description = "Web application for x managed by y"
vcs_repo = {
identifier = github_repository.repos["web-application-1-infrastructure"].full_name
branch = "trunk"
oauth_token_id = var.vcs_repo_oauth_token_id
}
}
}To increase flexibility and avoid hard-coding sensitive information (like the TFC org name) when writing the tests, I am exploring the possibility of dynamically setting the organization_name within the tfe block, ideally through an environment variable. However, I've encountered a limitation: it seems that the Terraform testing framework does not currently allow for referencing a variable within the variable block. This limitation is a significant hurdle, as it restricts our ability to write flexible and reusable tests.
A solution to this is to set the whole tfe block as an environment variable but this is kinda pointless as the test will be much less readable and we would have to wrap the testing in a bash/python script...
Current test for the TFE block
provider "tfe" {}
provider "azurerm" {
features {}
}
provider "azuread" {
}
variables {
tfe = {
project_name = "tests_tfe"
organization_name = "blinq-lab-tfc-org"
workspace_name = "tfe_test"
workspace_description = "Workspace description"
create_federated_identity_credential = true # Configures Federated Credentials on the workload identity for plan and apply phases.
module_outputs_to_workspace_var = {
applications = true
groups = true
role_definitions = true
user_assigned_identities = true
resource_groups = true
}
workspace_env_vars = {
tfe_test_env_var_1 = {
value = "test_env_var"
category = "env"
description = "Test non sensitive env var"
}
tfe_test_env_var_2 = {
value = "test_env_var"
category = "env"
description = "Test sensitive env var"
sensitive = true
}
}
workspace_vars = {
tfe_test_var_1 = {
value = "test"
category = "terraform"
description = "Test workspace var from station tests"
hcl = false
sensitive = false
},
tfe_test_var_2 = {
value = "tfe_test_var_2_test_value"
category = "terraform"
description = "Test workspace var from station tests. This should be sensitive"
hcl = false
sensitive = true
},
tfe_test_var_3 = {
value = "{\"key\": \"value\", \"another_key\": \"another_value\"}"
category = "terraform"
description = "Test workspace var from station tests. Testing hcl format"
hcl = true
sensitive = false
}
}
}
}
run "setup_create_tfc_test_project" {
variables {
tfc_project_name = "tests_tfe"
}
module {
source = "./tests/setup-tfe-project"
}
}
run "tfe_create_workspace" {
module {
source = "./"
}
assert {
condition = module.station-tfe.workspace.name == "tfe_test"
error_message = "The workspace name does NOT match the input"
}
assert {
condition = module.station-tfe.workspace.description == "Workspace description"
error_message = "The workspace description does NOT match the input"
}
}
run "tfe_workspace_varaibles" {
module {
source = "./"
}
# Assertions for tfe_test_env_var_1
assert {
condition = module.station-tfe.workspace_variables.tfe_test_env_var_1.value == "test_env_var"
error_message = "The workspace_env_vars.tfe_test_env_var_1 had not the expected value"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_env_var_1.sensitive == false
error_message = "The workspace_env_vars.tfe_test_env_var_1 was set as sensitive"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_env_var_1.category == "env"
error_message = "The workspace_env_vars.tfe_test_env_var_1 was NOT set as type env"
}
# Assertions for tfe_test_env_var_2
assert {
condition = module.station-tfe.workspace_variables.tfe_test_env_var_2.value == ""
error_message = "We could read the variable and this should not work when it's marked as sensitive"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_env_var_2.sensitive == true
error_message = "The workspace_env_vars.tfe_test_env_var_2 was not set as sensitive"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_env_var_2.category == "env"
error_message = "The workspace_env_vars.tfe_test_env_var_2 was not of type env"
}
# Assertions for tfe_test_var_1
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_1.value == "test"
error_message = "The workspace_vars.tfe_test_var_1 had not the expected value"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_1.sensitive == false
error_message = "The workspace_vars.tfe_test_var_1 was set as sensitive"
}
# Assertions for tfe_test_var_2
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_2.value == ""
error_message = "The workspace_vars.tfe_test_var_2 had not the expected value when sensitive"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_2.sensitive == true
error_message = "The workspace_vars.tfe_test_var_2 was not set as sensitive"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_2.hcl == false
error_message = "The workspace_vars.tfe_test_var_2 was set as hcl, but expected hcl == false"
}
# Assertions for tfe_test_var_3
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_3.value == "{\"key\": \"value\", \"another_key\": \"another_value\"}"
error_message = "The workspace_vars.tfe_test_var_3 had not the expected value"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_3.sensitive == false
error_message = "The workspace_vars.tfe_test_var_3 was set as sensitive"
}
assert {
condition = module.station-tfe.workspace_variables.tfe_test_var_3.hcl == true
error_message = "The workspace_vars.tfe_test_var_3 was NOT set as hcl"
}
}Refactoring the module would be our last resort because of how time-consuming it would be to update it for all of our clients.
Since the module also is open source and other people can contribute, it would be a hassle for others who want to perform tests against they're own TFC org. They would then have to update all the test files with their org name, test and revert the changes before making a PR.
I have already been in contact with Omar Ismail at Hasicorp regarding this issue and he suggested that we created an issue on this.
Attempted Solutions
It is not possible to reference a variable inside a variable. I have also tried to reference a local block where I pass in the value from a variable to the local block.
variables {
tfe = {
project_name = "tests_tfe"
organization_name = var.TFC_org_name
workspace_name = "web_app_n"
workspace_description = "Web application n managed by x"
}
}Proposal
It would be very helpful for us and other who have structured their module to use blocks to be able to update parts of the block using variables like so:
provider "tfe" {}
provider "azurerm" {
features {}
}
provider "azuread" {
}
variables {
tfe = {
project_name = "tests_tfe"
organization_name = var.TFC_org_name #This is currently not possible
workspace_name = "web_app_n"
workspace_description = "Web application n managed by x"
}
}
run "setup_create_tfc_test_project" {
variables {
tfc_project_name = "tests_tfe"
}
module {
source = "./tests/setup-tfe-project"
}
}
run "tfe_create_workspace" {
module {
source = "./"
}
assert {
condition = module.station-tfe.workspace.name == "web_app_n"
error_message = "The workspace name does NOT match the input"
}
}References
No response