Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[terraform/utils] Lint terraform variables generation #1102

Merged
merged 18 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ go-lint:
.PHONY: terraform-lint
terraform-lint:
docker run --rm -w /opt/dss -v ./deploy:/opt/dss/deploy -e TF_LOG=TRACE hashicorp/terraform fmt -recursive -check
docker run --rm -v $(PWD):/app -w /app python:3.9-slim /bin/bash -c "\
pip install python-hcl2 && \
cd /app/deploy/infrastructure/utils && \
python variables.py --diff \
"
@if [ $$? -eq 0 ]; then \
echo "Generated code lint succeeded."; \
barroco marked this conversation as resolved.
Show resolved Hide resolved
else \
echo "Generated code lint failed!"; \
exit 1; \
fi

# This mirrors the hygiene-tests continuous integration workflow job (.github/workflows/ci.yml)
.PHONY: hygiene-tests
Expand Down
96 changes: 81 additions & 15 deletions deploy/infrastructure/utils/variables.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
from os.path import isfile, join, abspath, dirname, exists
from typing import Dict, List, Tuple
import hcl2
import argparse
import sys

DEFINITIONS_PATH = join(abspath(dirname(__file__)), "definitions")
GENERATED_TFVARS_MD_FILENAME = "TFVARS.gen.md"
Expand All @@ -20,10 +22,7 @@

# Variables per project
# For all */terraform-*
GLOBAL_VARIABLES = [
"app_hostname",
"crdb_hostname_suffix"
]
GLOBAL_VARIABLES = ["app_hostname", "crdb_hostname_suffix"]

# dependencies/terraform-commons-dss
COMMONS_DSS_VARIABLES = GLOBAL_VARIABLES + [
Expand All @@ -38,14 +37,14 @@
"crdb_cluster_name",
"crdb_locality",
"crdb_external_nodes",
"kubernetes_namespace"
"kubernetes_namespace",
]

# dependencies/terraform-*-kubernetes
COMMON_KUBERNETES_VARIABLES = GLOBAL_VARIABLES + [
"cluster_name",
"node_count",
"kubernetes_version"
"kubernetes_version",
]

# dependencies/terraform-google-kubernetes
Expand All @@ -70,7 +69,7 @@
"aws_region",
"aws_instance_type",
"aws_route53_zone_id",
"aws_iam_permissions_boundary"
"aws_iam_permissions_boundary",
] + COMMON_KUBERNETES_VARIABLES

# modules/terraform-aws-dss
Expand All @@ -88,9 +87,7 @@
"../dependencies/terraform-aws-kubernetes": AWS_KUBERNETES_VARIABLES,
"../dependencies/terraform-google-kubernetes": GOOGLE_KUBERNETES_VARIABLES,
"../dependencies/terraform-commons-dss": COMMONS_DSS_VARIABLES,
"../../operations/ci/aws-1": list(
dict.fromkeys(AWS_MODULE_VARIABLES)
)
"../../operations/ci/aws-1": list(dict.fromkeys(AWS_MODULE_VARIABLES)),
}


Expand Down Expand Up @@ -175,9 +172,11 @@ def comment(content: str) -> str:
return commented_lines


def get_variables_tf_content(variables: List[str], definitions: Dict[str, str]) -> str:
def get_variables_gen_tf_content(
variables: List[str], definitions: Dict[str, str]
) -> str:
"""
Generate the content of variables.tf (Terraform definitions) based
Generate the content of variables.gen.tf (Terraform definitions) based
on the `variables` list. `variables` contains the variables names to
include in the content. `definitions` contains the definitions of all
available variables.
Expand Down Expand Up @@ -234,9 +233,9 @@ def write_files(definitions: Dict[str, str]):
for path, variables in PROJECT_VARIABLES.items():
project_name = path.split("/")[-1]

# Generate variables.tf definition
# Generate variables.gen.tf definition
var_filename = join(path, GENERATED_VARIABLES_FILENAME)
content = get_variables_tf_content(variables, definitions)
content = get_variables_gen_tf_content(variables, definitions)
write_file(var_filename, content)

if is_example_project(path):
Expand All @@ -249,6 +248,73 @@ def write_files(definitions: Dict[str, str]):
write_file(tfvars_md_filename, content)


def read_file(file_path: str) -> str:
"""
Reads a file and returns its content

Arguments:
file_path: the relative path of the file to be read from
Returns:
the content of the file as a str
"""
with open(file_path, "r") as file:
content = file.read()
return content


def diff_files(definitions: Dict[str, str]) -> bool:
"""
Generates the `variables.gen.tf` file content in memory and diffs it
against the `variables.get.tf` files found on disk

Arguments:
definitions: a dict where the keys are the terraform variable names,
and the valuies are the content of that file.
Returns:
true iff the generated content and locally stored content are equal
string values
"""
ethangraham2001 marked this conversation as resolved.
Show resolved Hide resolved
for path, variables in PROJECT_VARIABLES.items():
# Generate variables.gen.tf definition
var_filename = join(path, GENERATED_VARIABLES_FILENAME)
generated_content = get_variables_gen_tf_content(variables, definitions)

try:
actual_content = read_file(var_filename)
except Exception as e:
print(f"Error reading {var_filename}: {e}")
return False

if generated_content != actual_content:
return False

return True


if __name__ == "__main__":
parser = argparse.ArgumentParser(usage="python variables")
parser.add_argument(
"--diff",
barroco marked this conversation as resolved.
Show resolved Hide resolved
action="store_true",
help="diffs the generated `variables.gen.tf` files against the ones\
barroco marked this conversation as resolved.
Show resolved Hide resolved
found locally without modifiying existing files or writing any\
results out to disk.\
Exits with code 0 if there are no diffs, else exits with code 1",
barroco marked this conversation as resolved.
Show resolved Hide resolved
)
args = parser.parse_args()

definitions = load_tf_definitions()
write_files(definitions)

if args.diff:
if not diff_files(definitions):
print(
"variables.py: generated content was NOT equal to local variables.gen.tf content"
)
sys.exit(1)
barroco marked this conversation as resolved.
Show resolved Hide resolved
else:
print(
"variables.py: generated content was equal to local variables.gen.tf content"
)
sys.exit(0)
else:
write_files(definitions)