From a10a0b2b91141794cfacaba2c00ca7c366ecfa80 Mon Sep 17 00:00:00 2001 From: Oscar Utbult Date: Wed, 17 Nov 2021 13:43:54 +0100 Subject: [PATCH] Add tag conversion support --- .gitignore | 4 +- Makefile | 62 ++++++++++++++++++++++++++++ examples/{ => basic}/main.tf | 2 +- examples/{ => basic}/outputs.tf | 0 examples/{ => basic}/variables.tf | 0 examples/formatted_tags/main.tf | 59 ++++++++++++++++++++++++++ examples/formatted_tags/variables.tf | 5 +++ internal/README.md | 2 + internal/modules/aws/main.tf | 29 +++++++++++++ internal/modules/awscc/main.tf | 27 ++++++++++++ locals.tf | 33 ++++++++++----- output.tf | 4 ++ variable.tf | 8 +--- 13 files changed, 216 insertions(+), 19 deletions(-) create mode 100644 Makefile rename examples/{ => basic}/main.tf (94%) rename examples/{ => basic}/outputs.tf (100%) rename examples/{ => basic}/variables.tf (100%) create mode 100644 examples/formatted_tags/main.tf create mode 100644 examples/formatted_tags/variables.tf create mode 100644 internal/README.md create mode 100644 internal/modules/aws/main.tf create mode 100644 internal/modules/awscc/main.tf diff --git a/.gitignore b/.gitignore index ee64670..5c3e369 100644 --- a/.gitignore +++ b/.gitignore @@ -12,8 +12,8 @@ crash.log # Exclude all .tfvars files, which are likely to contain sentitive data, such as -# password, private keys, and other secrets. These should not be part of version -# control as they are data points which are potentially sensitive and subject +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject # to change depending on the environment. # *.tfvars diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..93c15eb --- /dev/null +++ b/Makefile @@ -0,0 +1,62 @@ +.PHONY: static-tests unit-tests integration-tests e2e-tests init + +# OS can be "Linux" or "macOS" +OS ?= Linux +# ARCH can be "x86_64" or "arm64" +ARCH ?= x86_64 + +TERRAFORM_VERSION := 1.0.10 +TFLINT_VERSION := 0.33.1 + + +SHELL := /usr/bin/env bash + +static-tests: setup-env + rm .terraform.lock.hcl plan.out plan.out.json 2> /dev/null || true + # should not require any aws credentials to test against, should be safe to run as github checks on pull requests + terraform init || ( echo 'FAILED: terraform init failed'; exit 1 ) + terraform validate || ( echo 'FAILED: terraform validate failed'; exit 1 ) + terraform fmt -check -recursive ./ || ( echo 'FAILED: all tf files should be formatted using "terraform fmt -recursive ./"'; exit 1 ) + tflint --init && tflint --var='region=us-west-1' --var='profile=default' ./ || ( echo 'FAILED: tflint found issues'; exit 1 ) + +unit-tests: setup-env + # Should test code paths in an individual module. terratest, or `terraform test`, this is where you want to test different regions, use retries to smooth transient errors + # Should not run automatically on PR's from un-trusted contributors + export PATH=$(shell pwd)/build/bin:$${PATH} &&\ + cd test && \ + go test -timeout 30m -json | tee >(go-test-report) | jq -jr .Output 2> /dev/null | sed 's/null//g';\ + retval_bash="$${PIPESTATUS[0]}" retval_zsh="$${pipestatus[1]}" ;\ + exit $$retval_bash $$retval_zsh + +integration-tests: + # Should test code paths in a module of modules and run when on eof the sub-modules is updated. terratest, or `terraform test` use retries to smooth transient errors + # Should not run automatically on PR's from un-trusted contributors, and should only be run on modules where one sub-module is changed + echo "todo" + exit 1 + +e2e-tests: + # Should test code paths in `deploy/` module. Unsure whether it should use tf cloud. terratest, or `terraform test`. + # For deploys that take long you could skip destroy between runs, so e2e is just updating what changed from last iteration, use retries to smooth transient errors. + # Should not run automatically on PR's from any contributors. Update(no destroy) tests run on `/do-e2e-tests` PR comment from maintainers. Full e2e run on release. + echo "todo" + exit 1 + +setup-env: + # using a bin path specific to this project so that different projects can use different versions of the tooling + mkdir -p build/bin/ &&\ + export PATH=$(shell pwd)/build/bin:$${PATH} &&\ + export TF_ARCH=$(shell echo $(ARCH) | sed 's/x86_64/amd64/') &&\ + export TF_OS=$(shell echo $(OS) | tr '[:upper:]' '[:lower:]' | sed 's/macos/darwin/') &&\ + export CT_OS=$(shell echo $(OS) | sed 's/macOS/Darwin/') &&\ + if [ "$$(terraform -v | head -n 1 | sed 's/Terraform v//')" != "$(TERRAFORM_VERSION)" ]; then \ + wget -O tf.zip https://releases.hashicorp.com/terraform/$(TERRAFORM_VERSION)/terraform_$(TERRAFORM_VERSION)_$${TF_OS}_$${TF_ARCH}.zip &&\ + unzip -o tf.zip terraform &&\ + rm tf.zip &&\ + mv -fv terraform build/bin/ ;\ + fi ;\ + if [ "$$(tflint --version | head -n 1 | sed 's/TFLint version //')" != "$(TFLINT_VERSION)" ]; then \ + wget -O tflint.zip https://github.com/terraform-linters/tflint/releases/download/v$(TFLINT_VERSION)/tflint_$${TF_OS}_$${TF_ARCH}.zip &&\ + unzip -o tflint.zip tflint &&\ + rm tflint.zip &&\ + mv -fv tflint build/bin/ ;\ + fi diff --git a/examples/main.tf b/examples/basic/main.tf similarity index 94% rename from examples/main.tf rename to examples/basic/main.tf index b2b8806..2442e83 100644 --- a/examples/main.tf +++ b/examples/basic/main.tf @@ -3,7 +3,7 @@ provider "awscc" { } module "labels" { - source = "../" + source = "../.." name = "measurements" namespace = "link" diff --git a/examples/outputs.tf b/examples/basic/outputs.tf similarity index 100% rename from examples/outputs.tf rename to examples/basic/outputs.tf diff --git a/examples/variables.tf b/examples/basic/variables.tf similarity index 100% rename from examples/variables.tf rename to examples/basic/variables.tf diff --git a/examples/formatted_tags/main.tf b/examples/formatted_tags/main.tf new file mode 100644 index 0000000..1f15514 --- /dev/null +++ b/examples/formatted_tags/main.tf @@ -0,0 +1,59 @@ +provider "aws" { + region = var.region +} + +provider "awscc" { + region = var.region +} + +locals { + # In the AWS provider format + aws_tags = { + "service" : "authorize", + "managed_by" : "terraform" + } + + # In the AWSCC provider format + awcc_tags = [ + { "key" : "service", "value" : "measurements" }, + { "key" = "managed_by", "value" : "terraform" } + ] +} + +# Using aws provider format as input +module "aws_labels" { + source = "../.." + + tags = local.aws_tags +} + +# Using awscc provider format as input +module "awscc_labels" { + source = "../.." + + tags = local.awcc_tags +} + +module "test_aws_to_aws" { + source = "../../internal/modules/aws" + + tags = module.aws_labels.tags_aws +} + +module "test_aws_to_awscc" { + source = "../../internal/modules/awscc" + + tags = module.aws_labels.tags +} + +module "test_awscc_to_aws" { + source = "../../internal/modules/aws" + + tags = module.awscc_labels.tags_aws +} + +module "test_awscc_to_awscc" { + source = "../../internal/modules/awscc" + + tags = module.awscc_labels.tags +} diff --git a/examples/formatted_tags/variables.tf b/examples/formatted_tags/variables.tf new file mode 100644 index 0000000..2d976fb --- /dev/null +++ b/examples/formatted_tags/variables.tf @@ -0,0 +1,5 @@ +variable "region" { + type = string + default = "eu-west-1" + description = "What AWS region to deploy resources in." +} diff --git a/internal/README.md b/internal/README.md new file mode 100644 index 0000000..75b01ef --- /dev/null +++ b/internal/README.md @@ -0,0 +1,2 @@ +# Warning +Do not use anything within the `internal` folder. It is only used for testing and verification of the module. \ No newline at end of file diff --git a/internal/modules/aws/main.tf b/internal/modules/aws/main.tf new file mode 100644 index 0000000..59725b6 --- /dev/null +++ b/internal/modules/aws/main.tf @@ -0,0 +1,29 @@ +/* + * Do not use. This This module only exists to verify the tag formatting. + */ + +variable "tags" { + description = "tags, which could be used for additional tags" + type = map(string) +} + +resource "aws_iam_policy" "policy" { + description = "A temporary policy. Should be deleted by the test." + name_prefix = "test_policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "ec2:Describe*", + ] + Effect = "Deny" + Resource = "*" + }, + ] + }) + + tags = var.tags +} + diff --git a/internal/modules/awscc/main.tf b/internal/modules/awscc/main.tf new file mode 100644 index 0000000..54778fe --- /dev/null +++ b/internal/modules/awscc/main.tf @@ -0,0 +1,27 @@ +/* + * Do not use. This This module only exists to verify the tag formatting. + */ + +variable "tags" { + description = "tags, which could be used for additional tags" +} + +resource "awscc_iam_role" "role" { + description = "A temporary role. Should be deleted by the test." + + assume_role_policy_document = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + }, + ] + }) + + tags = var.tags +} + diff --git a/locals.tf b/locals.tf index fe53734..2aab3ea 100644 --- a/locals.tf +++ b/locals.tf @@ -1,22 +1,35 @@ locals { - generated_tags = [ + generated_tags_awscc = [ { - "key": "Name", - "value": join("", null_resource.default.*.triggers.id) + "key" : "Name", + "value" : join("", null_resource.default.*.triggers.id) }, { - "key": "Namespace", - "value": join("", null_resource.default.*.triggers.namespace), + "key" : "Namespace", + "value" : join("", null_resource.default.*.triggers.namespace), }, { - "key": "Account", - "value": join("", null_resource.default.*.triggers.account) + "key" : "Account", + "value" : join("", null_resource.default.*.triggers.account) }, { - "key": "Env", - "value": join("", null_resource.default.*.triggers.env) + "key" : "Env", + "value" : join("", null_resource.default.*.triggers.env) } ] + generated_tags_aws = { for tag in local.generated_tags_awscc : tag["key"] => tag["value"] } - tags = concat(local.generated_tags, var.tags) + tags = try( + # Tags are already in awscc format + concat(local.generated_tags_awscc, var.tags), + # Tags are in aws format. Convert to awscc format + concat(local.generated_tags_awscc, [for k, v in var.tags : { key : k, value : v }]) + ) + + tags_aws = try( + # Assuming tags are already in aws format + merge(local.generated_tags_aws, var.tags), + # Tags are in awscc format. Converting to aws format + merge(local.generated_tags_aws, { for tag in var.tags : tag["key"] => tag["value"] }) + ) } \ No newline at end of file diff --git a/output.tf b/output.tf index 43363c9..ad8131f 100644 --- a/output.tf +++ b/output.tf @@ -3,6 +3,10 @@ output "tags" { value = local.tags } +output "tags_aws" { + value = local.tags_aws +} + output "id" { value = join("", null_resource.default.*.triggers.id) } diff --git a/variable.tf b/variable.tf index 120cf5f..4a7c414 100644 --- a/variable.tf +++ b/variable.tf @@ -33,11 +33,7 @@ variable "attributes" { } variable "tags" { - type = list(object({ - key = string - value = string - })) - - default = [] description = "tags, which could be used for additional tags" + type = any + default = [] }