From 8c6a08015eafe414ea410300faedb80475f4a336 Mon Sep 17 00:00:00 2001 From: Lotte-Sara Laan Date: Wed, 25 Nov 2020 12:59:30 +0100 Subject: [PATCH] Initial commit --- .gitignore | 45 +++++++ CHANGELOG.md | 9 ++ Makefile | 41 +++++++ README.md | 38 ++++++ README/appendix.tpl | 23 ++++ main.tf | 1 + modules/secret/Makefile | 10 ++ modules/secret/README.md | 40 +++++++ modules/secret/asserts.tf | 19 +++ modules/secret/main.tf | 64 ++++++++++ modules/secret/outputs.tf | 9 ++ modules/secret/variables.tf | 43 +++++++ modules/secret/versions.tf | 8 ++ modules/secret_version/Makefile | 10 ++ modules/secret_version/README.md | 31 +++++ modules/secret_version/main.tf | 4 + modules/secret_version/outputs.tf | 4 + modules/secret_version/variables.tf | 9 ++ modules/secret_version/versions.tf | 8 ++ test/Gopkg.toml | 3 + test/assertions/main.tf | 24 ++++ test/assertions/providers.tf | 1 + test/assertions/variables.tf | 1 + test/assertions/versions.tf | 1 + test/defaults/main.tf | 20 ++++ test/defaults/providers.tf | 1 + test/defaults/variables.tf | 1 + test/defaults/versions.tf | 1 + test/main.tf | 18 +++ test/outputs.tf | 6 + test/overrides/main.tf | 41 +++++++ test/overrides/providers.tf | 1 + test/overrides/variables.tf | 1 + test/overrides/versions.tf | 1 + test/providers.tf | 3 + test/terraform_test.go | 179 ++++++++++++++++++++++++++++ test/variables.tf | 22 ++++ test/versions.tf | 12 ++ 38 files changed, 753 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 Makefile create mode 100644 README.md create mode 100644 README/appendix.tpl create mode 100644 main.tf create mode 100644 modules/secret/Makefile create mode 100644 modules/secret/README.md create mode 100644 modules/secret/asserts.tf create mode 100644 modules/secret/main.tf create mode 100644 modules/secret/outputs.tf create mode 100644 modules/secret/variables.tf create mode 100644 modules/secret/versions.tf create mode 100644 modules/secret_version/Makefile create mode 100644 modules/secret_version/README.md create mode 100644 modules/secret_version/main.tf create mode 100644 modules/secret_version/outputs.tf create mode 100644 modules/secret_version/variables.tf create mode 100644 modules/secret_version/versions.tf create mode 100644 test/Gopkg.toml create mode 100644 test/assertions/main.tf create mode 120000 test/assertions/providers.tf create mode 120000 test/assertions/variables.tf create mode 120000 test/assertions/versions.tf create mode 100644 test/defaults/main.tf create mode 120000 test/defaults/providers.tf create mode 120000 test/defaults/variables.tf create mode 120000 test/defaults/versions.tf create mode 100644 test/main.tf create mode 100644 test/outputs.tf create mode 100644 test/overrides/main.tf create mode 120000 test/overrides/providers.tf create mode 120000 test/overrides/variables.tf create mode 120000 test/overrides/versions.tf create mode 100644 test/providers.tf create mode 100644 test/terraform_test.go create mode 100644 test/variables.tf create mode 100644 test/versions.tf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..da5364b --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# OSX leaves these everywhere on SMB shares +._* + +# OSX trash +.DS_Store +*.pyc* + +# Emacs save files +*~ +\#*\# +.\#* + +# Vim-related files +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist + +### https://raw.github.com/github/gitignore/90f149de451a5433aebd94d02d11b0e28843a1af/Terraform.gitignore + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log + +# Ignore any .tfvars files that are generated automatically for each Terraform run. Most +# .tfvars files are managed as part of configuration and so should be included in +# version control. +# +# example.tfvars +# + +**/terraform.tfvars + +# Secrets +credentials.json + +# Test files +.kitchen/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b5afef0 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## v1.0.0 (2020-11-25) + +* Initial release + +### Features + +* Initial release of this module based on Terraform 0.13 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d282541 --- /dev/null +++ b/Makefile @@ -0,0 +1,41 @@ +SHELL := /bin/bash + +MODULE := $(notdir $(PWD)) +USERID = $(shell id -u) +USERGROUP = $(shell id -g) +DATE_TIME = $(shell date +%s) +GCP_CREDS_FILE = $(notdir ${GOOGLE_CREDENTIALS}) + +.PHONY: readme test + +clear-env-error: + $(eval ENV_ERROR =) + +check-error: + @if [ ! "${ENV_ERROR}" = "" ]; then echo -e "${ENV_ERROR}" && exit 1; fi + +check-env: + $(eval GOOGLE_AUTH = GOOGLE_IMPERSONATE_SERVICE_ACCOUNT=${GOOGLE_IMPERSONATE_SERVICE_ACCOUNT}) +ifeq ($(GOOGLE_CREDENTIALS), ) +ifeq ('$(GOOGLE_IMPERSONATE_SERVICE_ACCOUNT)', '') + $(eval ENV_ERROR = $(ENV_ERROR)Either GOOGLE_CREDENTIALS or GOOGLE_IMPERSONATE_SERVICE_ACCOUNT should be set but was not found in environment.\n) +endif +endif +ifeq ($(GOOGLE_CLOUD_PROJECT), ) + $(eval ENV_ERROR = $(ENV_ERROR)GOOGLE_CLOUD_PROJECT is not set in environment.\n) +endif + +check-gcp-env: clear-env-error check-env check-error + +test: check-gcp-env $(GOOGLE_CREDENTIALS) + docker run --rm -it \ + -v ${GOOGLE_CREDENTIALS}:/root/$(GCP_CREDS_FILE):ro \ + -v $(PWD):/go/src/app/ \ + -e GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT} \ + -e GOOGLE_APPLICATION_CREDENTIALS=/root/$(GCP_CREDS_FILE) \ + -e $(GOOGLE_AUTH) \ + -e TF_VAR_owner=$(USER) \ + binxio/terratest-runner-gcp:0.0.3 + +readme: + docker run --rm -e MODULE=$(MODULE) --user $(USERID):$(USERGROUP) -it -v $(PWD):/go/src/app/$(MODULE) binxio/terraform-module-readme-generator:latest diff --git a/README.md b/README.md new file mode 100644 index 0000000..5f8e0ef --- /dev/null +++ b/README.md @@ -0,0 +1,38 @@ + +# Module `terraform-google-secret-manager` + +## Creating a new release +After adding your changed and committing the code to GIT, you will need to add a new tag. +``` +git tag vx.x.x +git push --tag +``` +If your changes might be breaking current implementations of this module, make sure to bump the major version up by 1. + +If you want to see which tags are already there, you can use the following command: +``` +git tag --list +``` +Required APIs +============= +For the Secret Manager services to deploy, the following APIs should be enabled in your project: + * `iam.googleapis.com` + * `secretmanager.googleapis.com` + +Testing +======= +This module comes with [terratest](https://github.com/gruntwork-io/terratest) scripts for both unit testing and integration testing. +A Makefile is provided to run the tests using docker, but you can also run the tests directly on your machine if you have terratest installed. + +### Run with make +Make sure to set GOOGLE_CLOUD_PROJECT to the right project and GOOGLE_CREDENTIALS to the right credentials json file +You can now run the tests with docker: +``` +make test +``` + +### Run locally +From the module directory, run: +``` +cd test && TF_VAR_owner=$(id -nu) go test +``` diff --git a/README/appendix.tpl b/README/appendix.tpl new file mode 100644 index 0000000..a58fd5b --- /dev/null +++ b/README/appendix.tpl @@ -0,0 +1,23 @@ +Required APIs +============= +For the Secret Manager services to deploy, the following APIs should be enabled in your project: + * `iam.googleapis.com` + * `secretmanager.googleapis.com` + +Testing +======= +This module comes with [terratest](https://github.com/gruntwork-io/terratest) scripts for both unit testing and integration testing. +A Makefile is provided to run the tests using docker, but you can also run the tests directly on your machine if you have terratest installed. + +### Run with make +Make sure to set GOOGLE_CLOUD_PROJECT to the right project and GOOGLE_CREDENTIALS to the right credentials json file +You can now run the tests with docker: +``` +make test +``` + +### Run locally +From the module directory, run: +``` +cd test && TF_VAR_owner=$(id -nu) go test +``` diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..bd66849 --- /dev/null +++ b/main.tf @@ -0,0 +1 @@ +# This file is left empty intentionally. Use the nested modules instead. diff --git a/modules/secret/Makefile b/modules/secret/Makefile new file mode 100644 index 0000000..a40b76c --- /dev/null +++ b/modules/secret/Makefile @@ -0,0 +1,10 @@ +SHELL := /bin/bash + +MODULE := $(notdir $(PWD)) +USERID = $(shell id -u) +USERGROUP = $(shell id -g) + +.PHONY: readme + +readme: + docker run --rm -e MODULE=$(MODULE) --user $(USERID):$(USERGROUP) -it -v $(PWD):/go/src/app/$(MODULE) binxio/terraform-module-readme-generator:latest diff --git a/modules/secret/README.md b/modules/secret/README.md new file mode 100644 index 0000000..ca4f464 --- /dev/null +++ b/modules/secret/README.md @@ -0,0 +1,40 @@ + +# Module `secret` + +Core Version Constraints: +* `>= 0.13` + +Provider Requirements: +* **google (`hashicorp/google`):** (any version) + +## Input Variables +* `environment` (required): Company environment for which the resources are created (e.g. dev, tst, acc, prd, all). +* `owner` (required): Owner of the resource. This variable is used to set the 'owner' label. Will be used as default for each subnet, but can be overridden using the subnet settings. +* `project` (required): Company project name. +* `purpose` (required): The purpose of the secret. This variable is appended to the secret_id and used to set the 'purpose' label. +* `replication` (default `{"automatic":true}`): The replication to be used +* `roles` (required): Map of role name's as `key` and members list as `value` to bind permissions + +## Output Values +* `id`: Id of the secret +* `secret_id`: Id of the secret + +## Managed Resources +* `google_secret_manager_secret.secret` from `google` +* `google_secret_manager_secret_iam_policy.map` from `google` + +## Data Resources +* `data.google_iam_policy.map` from `google` + +## Creating a new release +After adding your changed and committing the code to GIT, you will need to add a new tag. +``` +git tag vx.x.x +git push --tag +``` +If your changes might be breaking current implementations of this module, make sure to bump the major version up by 1. + +If you want to see which tags are already there, you can use the following command: +``` +git tag --list +``` diff --git a/modules/secret/asserts.tf b/modules/secret/asserts.tf new file mode 100644 index 0000000..59d34e3 --- /dev/null +++ b/modules/secret/asserts.tf @@ -0,0 +1,19 @@ +####################################################################################################### +# +# Terraform does not have a easy way to check if the input parameters are in the correct format. +# On top of that, terraform will sometimes produce a valid plan but then fail during apply. +# To handle these errors beforehad, we're using the 'file' hack to throw errors on known mistakes. +# +####################################################################################################### +locals { + # Regular expressions + regex_secret_id = "[a-zA-Z0-9](?:[a-zA-Z0-9_-]{4,253}[a-zA-Z0-9])" + + # Terraform assertion hack + assert_head = "\n\n-------------------------- /!\\ ASSERTION FAILED /!\\ --------------------------\n\n" + assert_foot = "\n\n-------------------------- /!\\ ^^^^^^^^^^^^^^^^ /!\\ --------------------------\n" + asserts = { + secret_id_too_long = length(local.secret_id) > 255 ? file(format("%sSecret's generated id is too long:\n%s\n%s > 255 chars!%s", local.assert_head, local.secret_id, length(local.secret_id), local.assert_foot)) : "ok" + secret_id_regex = length(regexall("^${local.regex_secret_id}$", local.secret_id)) == 0 ? file(format("%sSecret's generated id [%s] does not match regex ^%s$%s", local.assert_head, local.secret_id, local.regex_secret_id, local.assert_foot)) : "ok" + } +} diff --git a/modules/secret/main.tf b/modules/secret/main.tf new file mode 100644 index 0000000..d13c306 --- /dev/null +++ b/modules/secret/main.tf @@ -0,0 +1,64 @@ +locals { + project = var.project + environment = var.environment + owner = var.owner + + labels = { + "project" = substr(replace(lower(local.project), "/[^\\p{Ll}\\p{Lo}\\p{N}_-]+/", "_"), 0, 63) + "env" = substr(replace(lower(local.environment), "/[^\\p{Ll}\\p{Lo}\\p{N}_-]+/", "_"), 0, 63) + "owner" = substr(replace(local.owner, "/[^\\p{Ll}\\p{Lo}\\p{N}_-]+/", "_"), 0, 63) + "creator" = "terraform" + } + + # We like to have the project and env in the secret name, so it's obvious what the secret belongs to + # without checking the GCP project it's part of + # This also allows for central secret management since the naming is more unique by default. + secret_id = replace(lower(format("%s-%s-%s", local.project, local.environment, var.purpose)), " ", "-") + + # Map role members to GCP expected format + roles = { for role, members in try(var.roles, {}) : role => [for member, type in members : format("%s:%s", type, member)] } +} + +resource "google_secret_manager_secret" "secret" { + secret_id = local.secret_id + + replication { + automatic = lookup(var.replication, "automatic", null) + + dynamic "user_managed" { + for_each = lookup(var.replication, "user_managed", []) + + content { + dynamic "replicas" { + for_each = lookup(user_managed.value, "replicas", []) + + content { + location = replicas.value.location + } + } + } + } + } + + labels = local.labels +} + +data "google_iam_policy" "map" { + for_each = (length(keys(local.roles)) > 0 ? { policy = {} } : {}) + + dynamic "binding" { + for_each = local.roles + + content { + role = binding.key + members = binding.value + } + } +} + +resource "google_secret_manager_secret_iam_policy" "map" { + for_each = data.google_iam_policy.map + + secret_id = google_secret_manager_secret.secret.secret_id + policy_data = each.value.policy_data +} diff --git a/modules/secret/outputs.tf b/modules/secret/outputs.tf new file mode 100644 index 0000000..d950c5d --- /dev/null +++ b/modules/secret/outputs.tf @@ -0,0 +1,9 @@ +output "secret_id" { + description = "Id of the secret" + value = google_secret_manager_secret.secret.secret_id +} + +output "id" { + description = "Id of the secret" + value = google_secret_manager_secret.secret.id +} diff --git a/modules/secret/variables.tf b/modules/secret/variables.tf new file mode 100644 index 0000000..2bfe72a --- /dev/null +++ b/modules/secret/variables.tf @@ -0,0 +1,43 @@ +#------------------------------------------------------------------------------------------------------------------------ +# +# Generic variables +# +#------------------------------------------------------------------------------------------------------------------------ +variable "owner" { + description = "Owner of the resource. This variable is used to set the 'owner' label. Will be used as default for each subnet, but can be overridden using the subnet settings." + type = string +} + +variable "project" { + description = "Company project name." + type = string +} + +variable "environment" { + description = "Company environment for which the resources are created (e.g. dev, tst, acc, prd, all)." + type = string +} + +#------------------------------------------------------------------------------------------------------------------------ +# +# secret variables +# +#------------------------------------------------------------------------------------------------------------------------ +variable "purpose" { + description = "The purpose of the secret. This variable is appended to the secret_id and used to set the 'purpose' label." + type = string +} + +variable "replication" { + description = "The replication to be used" + type = any + default = { + automatic = true + } +} + +variable "roles" { + description = "Map of role name's as `key` and members list as `value` to bind permissions" + type = map(map(string)) + default = {} +} diff --git a/modules/secret/versions.tf b/modules/secret/versions.tf new file mode 100644 index 0000000..695f070 --- /dev/null +++ b/modules/secret/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + } + } + required_version = ">= 0.13" +} diff --git a/modules/secret_version/Makefile b/modules/secret_version/Makefile new file mode 100644 index 0000000..a40b76c --- /dev/null +++ b/modules/secret_version/Makefile @@ -0,0 +1,10 @@ +SHELL := /bin/bash + +MODULE := $(notdir $(PWD)) +USERID = $(shell id -u) +USERGROUP = $(shell id -g) + +.PHONY: readme + +readme: + docker run --rm -e MODULE=$(MODULE) --user $(USERID):$(USERGROUP) -it -v $(PWD):/go/src/app/$(MODULE) binxio/terraform-module-readme-generator:latest diff --git a/modules/secret_version/README.md b/modules/secret_version/README.md new file mode 100644 index 0000000..1473a77 --- /dev/null +++ b/modules/secret_version/README.md @@ -0,0 +1,31 @@ + +# Module `secret_version` + +Core Version Constraints: +* `>= 0.13` + +Provider Requirements: +* **google (`hashicorp/google`):** (any version) + +## Input Variables +* `secret_data` (required): The data to be set +* `secret_id` (required): The full id of the secret for which to create a version for + +## Output Values +* `id`: The resource name of the SecretVersion. Format: projects/{{project}}/secrets/{{secret_id}}/versions/{{version}} + +## Managed Resources +* `google_secret_manager_secret_version.version` from `google` + +## Creating a new release +After adding your changed and committing the code to GIT, you will need to add a new tag. +``` +git tag vx.x.x +git push --tag +``` +If your changes might be breaking current implementations of this module, make sure to bump the major version up by 1. + +If you want to see which tags are already there, you can use the following command: +``` +git tag --list +``` diff --git a/modules/secret_version/main.tf b/modules/secret_version/main.tf new file mode 100644 index 0000000..27f3057 --- /dev/null +++ b/modules/secret_version/main.tf @@ -0,0 +1,4 @@ +resource "google_secret_manager_secret_version" "version" { + secret = var.secret_id + secret_data = var.secret_data +} diff --git a/modules/secret_version/outputs.tf b/modules/secret_version/outputs.tf new file mode 100644 index 0000000..d08b916 --- /dev/null +++ b/modules/secret_version/outputs.tf @@ -0,0 +1,4 @@ +output "id" { + description = "The resource name of the SecretVersion. Format: projects/{{project}}/secrets/{{secret_id}}/versions/{{version}}" + value = google_secret_manager_secret_version.version.id +} diff --git a/modules/secret_version/variables.tf b/modules/secret_version/variables.tf new file mode 100644 index 0000000..82aa5b1 --- /dev/null +++ b/modules/secret_version/variables.tf @@ -0,0 +1,9 @@ +variable "secret_id" { + description = "The full id of the secret for which to create a version for" + type = string +} + +variable "secret_data" { + description = "The data to be set" + type = string +} diff --git a/modules/secret_version/versions.tf b/modules/secret_version/versions.tf new file mode 100644 index 0000000..695f070 --- /dev/null +++ b/modules/secret_version/versions.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + } + } + required_version = ">= 0.13" +} diff --git a/test/Gopkg.toml b/test/Gopkg.toml new file mode 100644 index 0000000..c7cc04c --- /dev/null +++ b/test/Gopkg.toml @@ -0,0 +1,3 @@ +[[constraint]] + name = "github.com/gruntwork-io/terratest" + version = "0.30.23" diff --git a/test/assertions/main.tf b/test/assertions/main.tf new file mode 100644 index 0000000..1213869 --- /dev/null +++ b/test/assertions/main.tf @@ -0,0 +1,24 @@ +locals { + project = "testapp" + environment = var.environment +} + +module "secret1" { + source = "../../modules/secret" + + owner = var.owner + project = local.project + environment = local.environment + + purpose = "trigger-assertions for secret by creating too long sentences as it is only allowed to be 255 chars or less but we have to keep going as we are not there yet as somehow GCP decided to go way over the other resoure limits where a name can only be 30 chars orso while we would want them to be longer" +} + +module "secret2" { + source = "../../modules/secret" + + owner = var.owner + project = local.project + environment = local.environment + + purpose = "trigger-assertions for secret 'cause this name contains invalid chars." +} diff --git a/test/assertions/providers.tf b/test/assertions/providers.tf new file mode 120000 index 0000000..7244d01 --- /dev/null +++ b/test/assertions/providers.tf @@ -0,0 +1 @@ +../providers.tf \ No newline at end of file diff --git a/test/assertions/variables.tf b/test/assertions/variables.tf new file mode 120000 index 0000000..3a65dcc --- /dev/null +++ b/test/assertions/variables.tf @@ -0,0 +1 @@ +../variables.tf \ No newline at end of file diff --git a/test/assertions/versions.tf b/test/assertions/versions.tf new file mode 120000 index 0000000..8bd0ff1 --- /dev/null +++ b/test/assertions/versions.tf @@ -0,0 +1 @@ +../versions.tf \ No newline at end of file diff --git a/test/defaults/main.tf b/test/defaults/main.tf new file mode 100644 index 0000000..bc1affa --- /dev/null +++ b/test/defaults/main.tf @@ -0,0 +1,20 @@ +locals { + project = "testapp" + environment = var.environment + + purpose = "1default-test" +} + +module "secret" { + source = "../../modules/secret" + + owner = var.owner + project = local.project + environment = local.environment + + purpose = local.purpose +} + +output "id" { + value = module.secret.id +} diff --git a/test/defaults/providers.tf b/test/defaults/providers.tf new file mode 120000 index 0000000..7244d01 --- /dev/null +++ b/test/defaults/providers.tf @@ -0,0 +1 @@ +../providers.tf \ No newline at end of file diff --git a/test/defaults/variables.tf b/test/defaults/variables.tf new file mode 120000 index 0000000..3a65dcc --- /dev/null +++ b/test/defaults/variables.tf @@ -0,0 +1 @@ +../variables.tf \ No newline at end of file diff --git a/test/defaults/versions.tf b/test/defaults/versions.tf new file mode 120000 index 0000000..8bd0ff1 --- /dev/null +++ b/test/defaults/versions.tf @@ -0,0 +1 @@ +../versions.tf \ No newline at end of file diff --git a/test/main.tf b/test/main.tf new file mode 100644 index 0000000..2984d5d --- /dev/null +++ b/test/main.tf @@ -0,0 +1,18 @@ +locals { + owner = "myself" + project = "testapp" + company = "mycompany" + environment = var.environment + test_sas = { + "reader" = format("reader-%s", replace(local.environment, " ", "-")) + "owner" = format("owner-%s", replace(local.environment, " ", "-")) + } +} + +resource "google_service_account" "map" { + for_each = local.test_sas + + account_id = each.value + display_name = format("%s Terraform Test", each.value) + description = "Service Account to test assignment of secret roles" +} diff --git a/test/outputs.tf b/test/outputs.tf new file mode 100644 index 0000000..f7953e7 --- /dev/null +++ b/test/outputs.tf @@ -0,0 +1,6 @@ +output "sa_reader_email" { + value = google_service_account.map["reader"].email +} +output "sa_owner_email" { + value = google_service_account.map["owner"].email +} diff --git a/test/overrides/main.tf b/test/overrides/main.tf new file mode 100644 index 0000000..638dc64 --- /dev/null +++ b/test/overrides/main.tf @@ -0,0 +1,41 @@ +locals { + project = "testapp" + environment = var.environment + + purpose = "default-test" +} + +module "secret" { + source = "../../modules/secret" + + owner = var.owner + project = local.project + environment = local.environment + + purpose = local.purpose + + roles = { + "roles/secretmanager.admin" = { + (var.sa_owner_email) = "serviceAccount" + } + "roles/secretmanager.secretAccessor" = { + (var.sa_reader_email) = "serviceAccount" + } + } + + replication = { + user_managed = [ + { + replicas = [ + { + location = "europe-west1" + } + ] + } + ] + } +} + +output "id" { + value = module.secret.id +} diff --git a/test/overrides/providers.tf b/test/overrides/providers.tf new file mode 120000 index 0000000..7244d01 --- /dev/null +++ b/test/overrides/providers.tf @@ -0,0 +1 @@ +../providers.tf \ No newline at end of file diff --git a/test/overrides/variables.tf b/test/overrides/variables.tf new file mode 120000 index 0000000..3a65dcc --- /dev/null +++ b/test/overrides/variables.tf @@ -0,0 +1 @@ +../variables.tf \ No newline at end of file diff --git a/test/overrides/versions.tf b/test/overrides/versions.tf new file mode 120000 index 0000000..8bd0ff1 --- /dev/null +++ b/test/overrides/versions.tf @@ -0,0 +1 @@ +../versions.tf \ No newline at end of file diff --git a/test/providers.tf b/test/providers.tf new file mode 100644 index 0000000..58b228c --- /dev/null +++ b/test/providers.tf @@ -0,0 +1,3 @@ +provider "google-beta" { + region = "europe-west3" +} diff --git a/test/terraform_test.go b/test/terraform_test.go new file mode 100644 index 0000000..de18c9d --- /dev/null +++ b/test/terraform_test.go @@ -0,0 +1,179 @@ +package test + +import ( + "fmt" + "os" + "os/signal" + "path/filepath" + "strings" + "testing" + + "github.com/gruntwork-io/terratest/modules/gcp" + "github.com/gruntwork-io/terratest/modules/random" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// Give this secret an environment to operate as a part of, for the purposes of resource tagging +// Give it a random string so we're sure it's created this test run +var expectedEnvironment string +var testPreq *testing.T +var terraformOptions *terraform.Options +var tmpSaReaderEmail string +var tmpSaOwnerEmail string +var blacklistRegions []string +var projectId string +var region string + +func TestMain(m *testing.M) { + expectedEnvironment = fmt.Sprintf("terratest %s", strings.ToLower(random.UniqueId())) + blacklistRegions = []string{"asia-east2"} + + c := make(chan os.Signal, 1) + signal.Notify(c, os.Interrupt) + go func(){ + <-c + TestCleanup(testPreq) + Clean() + os.Exit(1) + }() + + result := 0 + defer func() { + TestCleanup(testPreq) + Clean() + os.Exit(result) + }() + result = m.Run() +} + +// -------------------------------------------------------------------------------------------------------- // +// Utility functions +// -------------------------------------------------------------------------------------------------------- // +func setTerraformOptions(dir string) { + terraformOptions = &terraform.Options { + TerraformDir: dir, + // Pass the expectedEnvironment for tagging + Vars: map[string]interface{}{ + "environment": expectedEnvironment, + "location": region, + "sa_reader_email": tmpSaReaderEmail, + "sa_owner_email": tmpSaOwnerEmail, + }, + EnvVars: map[string]string{ + "GOOGLE_CLOUD_PROJECT": projectId, + }, + } +} + +// A build step that removes temporary build and test files +func Clean() error { + fmt.Println("Cleaning...") + + return filepath.Walk(".", func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() && info.Name() == "vendor" { + return filepath.SkipDir + } + if info.IsDir() && info.Name() == ".terraform" { + os.RemoveAll(path) + fmt.Printf("Removed \"%v\"\n", path) + return filepath.SkipDir + } + if !info.IsDir() && (info.Name() == "terraform.tfstate" || + info.Name() == "terraform.tfplan" || + info.Name() == "terraform.tfstate.backup") { + os.Remove(path) + fmt.Printf("Removed \"%v\"\n", path) + } + return nil + }) +} + +func Test_Prereq(t *testing.T) { + projectId = gcp.GetGoogleProjectIDFromEnvVar(t) + // Pick a random GCP region to test in. This helps ensure your code works in all regions. + region = gcp.GetRandomRegion(t, projectId, nil, blacklistRegions) + + setTerraformOptions(".") + testPreq = t + + terraform.InitAndApply(t, terraformOptions) + + tmpSaReaderEmail = terraform.OutputRequired(t, terraformOptions, "sa_reader_email") + tmpSaOwnerEmail = terraform.OutputRequired(t, terraformOptions, "sa_owner_email") +} + +// -------------------------------------------------------------------------------------------------------- // +// Unit Tests +// -------------------------------------------------------------------------------------------------------- // +func TestUT_Assertions(t *testing.T) { + expectedAssertNameTooLong := "'s generated id is too long:" + expectedAssertNameInvalidChars := "does not match regex" + + setTerraformOptions("assertions") + + out, err := terraform.InitAndPlanE(t, terraformOptions) + + require.Error(t, err) + assert.Contains(t, out, expectedAssertNameTooLong) + assert.Contains(t, out, expectedAssertNameInvalidChars) +} + +func TestUT_Defaults(t *testing.T) { + setTerraformOptions("defaults") + terraform.InitAndPlan(t, terraformOptions) +} + +func TestUT_Overrides(t *testing.T) { + setTerraformOptions("overrides") + terraform.InitAndPlan(t, terraformOptions) +} + +// -------------------------------------------------------------------------------------------------------- // +// Integration Tests +// -------------------------------------------------------------------------------------------------------- // + +func TestIT_Defaults(t *testing.T) { + setTerraformOptions("defaults") + + defer terraform.Destroy(t, terraformOptions) + + terraform.InitAndApply(t, terraformOptions) + + outputs := terraform.OutputAll(t, terraformOptions) + + secret := outputs["id"].(string) + // Make sure our secret is created + fmt.Printf("Checking secret id: %s...\n", secret) +} + +func TestIT_Overrides(t *testing.T) { + setTerraformOptions("overrides") + + defer terraform.Destroy(t, terraformOptions) + + terraform.InitAndApply(t, terraformOptions) + + outputs := terraform.OutputAll(t, terraformOptions) + + secret := outputs["id"].(string) + // Make sure our secret is created + fmt.Printf("Checking secret id: %s...\n", secret) +} + +func TestCleanup(t *testing.T) { + fmt.Println("Cleaning possible lingering resources..") + defer cleanPreq() + terraform.Destroy(t, terraformOptions) +} + +func cleanPreq() { + // Also clean up prereq. resources + fmt.Println("Cleaning our prereq resources...") + setTerraformOptions(".") + terraform.Destroy(testPreq, terraformOptions) +} diff --git a/test/variables.tf b/test/variables.tf new file mode 100644 index 0000000..2d06d5e --- /dev/null +++ b/test/variables.tf @@ -0,0 +1,22 @@ +variable "environment" { + description = "Environment for which the resources are created (e.g. dev, tst, acc or prd)" + type = string +} +variable "owner" { + description = "Owner used for labeling" + type = string +} +variable "location" { + description = "Allows us to use random location for our tests" + type = string +} +variable "sa_reader_email" { + description = "The Reader SA used for testing secret role assignment" + type = string + default = "" +} +variable "sa_owner_email" { + description = "The Owner SA used for testing secret role assignment" + type = string + default = "" +} diff --git a/test/versions.tf b/test/versions.tf new file mode 100644 index 0000000..7693bfa --- /dev/null +++ b/test/versions.tf @@ -0,0 +1,12 @@ +terraform { + required_version = ">= 0.13" + required_providers { + google = { + source = "hashicorp/google" + } + google-beta = { + source = "hashicorp/google-beta" + } + } +} +