From e7ba011011e3f6068fde226a7f1e6438af420149 Mon Sep 17 00:00:00 2001 From: AntonioCS Date: Mon, 15 Dec 2025 14:09:37 +0000 Subject: [PATCH] Add Terraform module for multi-environment deployments mod_info.mk: - Module metadata (v1.0.0, no dependencies) mod_config.mk: - tf_bin, tf_env_dir, tf_shared_vars configuration - tf_auto_approve_envs for environment-specific auto-approve - tf_destroy_confirm for destroy operation safety - tf_chdir_flag for terraform 0.14+ compatibility terraform.mk: - tf_build_chdir: Build -chdir argument - tf_build_var_file: Build -var-file argument - tf_is_auto_approve_env: Check if env allows auto-approve - tf_run: Execute terraform command (auto-includes shared vars if set) - Pattern targets: init, plan, apply, destroy, validate, output, state/list, refresh - Utility targets: fmt, fmt/check, version terraform_test.mk: - 14 tests, 33 assertions covering all functions --- CHANGELOG.md | 15 ++ CLAUDE.md | 10 +- .../infrastructure/terraform/mod_config.mk | 64 +++++ modules/infrastructure/terraform/mod_info.mk | 6 + modules/infrastructure/terraform/terraform.mk | 163 +++++++++++++ .../terraform/terraform_test.mk | 226 ++++++++++++++++++ 6 files changed, 483 insertions(+), 1 deletion(-) create mode 100644 modules/infrastructure/terraform/mod_config.mk create mode 100644 modules/infrastructure/terraform/mod_info.mk create mode 100644 modules/infrastructure/terraform/terraform.mk create mode 100644 modules/infrastructure/terraform/terraform_test.mk diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a3b532..586c5ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,18 @@ +## [2.2.0] - 2025-12-15 + +### Added +- **Terraform module**: New workflow automation for multi-environment Terraform deployments + - `tf_run` function for executing terraform commands with automatic shared vars inclusion + - `tf_build_chdir`, `tf_build_var_file`, `tf_is_auto_approve_env` helper functions + - Pattern targets: `terraform/init/%`, `terraform/plan/%`, `terraform/apply/%`, `terraform/destroy/%`, `terraform/validate/%`, `terraform/output/%`, `terraform/state/list/%`, `terraform/refresh/%` + - Utility targets: `terraform/fmt`, `terraform/fmt/check`, `terraform/version` + - Configuration: `tf_bin`, `tf_root_dir`, `tf_env_dir`, `tf_shared_vars` (multi-file support), `tf_auto_approve_envs`, `tf_destroy_confirm`, `tf_chdir_flag` + - Comprehensive test suite (15 tests, 36 assertions) +- **LocalStack module enhancements**: + - `localstack/s3/ls` target with optional `bucket=` parameter + - `localstack/sqs/ls` target for listing queues + - Comprehensive test suite (11 tests, 26 assertions) + ## [2.1.1] - 2025-12-10 ### Changed diff --git a/CLAUDE.md b/CLAUDE.md index abe43ef..8a01d50 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -319,10 +319,18 @@ Before writing module code, review `main.mk` for active settings: - `.SECONDEXPANSION:` - enables `$$@` and `$$*` in prerequisites for dynamic expansion ### Changelog -When making significant changes, update `CHANGELOG.md`: +**IMPORTANT**: When making significant changes, you MUST update `CHANGELOG.md`. This includes: +- New modules +- New features or targets +- Breaking changes +- Bug fixes + +Rules: - Follow [Keep a Changelog](https://keepachangelog.com/) format - Group changes under: Added, Changed, Deprecated, Removed, Fixed, Security - Include version number and date for releases +- Bump minor version (e.g., 2.1.0 → 2.2.0) for new features/modules +- Bump patch version (e.g., 2.1.0 → 2.1.1) for bug fixes ### Trello Board **Board ID: `vBmmD6it`** - Must be set at the start of each session using `mcp__trello__set_active_board`. diff --git a/modules/infrastructure/terraform/mod_config.mk b/modules/infrastructure/terraform/mod_config.mk new file mode 100644 index 0000000..8eca4e2 --- /dev/null +++ b/modules/infrastructure/terraform/mod_config.mk @@ -0,0 +1,64 @@ +##################################################################################### +# Terraform Module Configuration +##################################################################################### + +## @var tf_bin +## @desc Path to terraform binary +## @type string +## @default terraform +## @group terraform +tf_bin ?= terraform + +## @var tf_root_dir +## @desc Root terraform directory (for fmt, validate operations) +## @type string +## @default terraform +## @group terraform +tf_root_dir ?= terraform + +## @var tf_env_dir +## @desc Environment directory relative to project root +## @type string +## @default terraform/environments +## @group terraform +## @example tf_env_dir=infra/envs make terraform/plan/dev +tf_env_dir ?= terraform/environments + +## @var tf_shared_vars +## @desc Shared tfvars file paths, space-delimited (relative to environment directory) +## @desc For paths with spaces, use mb_space_guard: $(call mb_space_guard,path with spaces/file.tfvars) +## @type string +## @default (empty - no shared vars) +## @group terraform +## @example tf_shared_vars=../../shared/common.tfvars +## @example tf_shared_vars=../../shared/common.tfvars ../../shared/secrets.tfvars +tf_shared_vars ?= + +## @var tf_default_env +## @desc Default environment name for convenience targets +## @type string +## @default local +## @group terraform +tf_default_env ?= local + +## @var tf_auto_approve_envs +## @desc Space-separated list of environments where auto-approve is enabled +## @type string +## @default local +## @group terraform +## @example tf_auto_approve_envs=local dev +tf_auto_approve_envs ?= local + +## @var tf_destroy_confirm +## @desc Require confirmation before destroy operations +## @type boolean +## @default $(mb_true) +## @group terraform +tf_destroy_confirm ?= $(mb_true) + +## @var tf_chdir_flag +## @desc Use -chdir flag (requires terraform 0.14+) +## @type boolean +## @default $(mb_true) +## @group terraform +tf_chdir_flag ?= $(mb_true) diff --git a/modules/infrastructure/terraform/mod_info.mk b/modules/infrastructure/terraform/mod_info.mk new file mode 100644 index 0000000..9837a20 --- /dev/null +++ b/modules/infrastructure/terraform/mod_info.mk @@ -0,0 +1,6 @@ +mb_module_name := terraform +mb_module_version := 1.0.0 +mb_module_description := Terraform workflow automation for multi-environment deployments +mb_module_author := AntonioCS +mb_module_license := MIT +mb_module_depends := diff --git a/modules/infrastructure/terraform/terraform.mk b/modules/infrastructure/terraform/terraform.mk new file mode 100644 index 0000000..79c1213 --- /dev/null +++ b/modules/infrastructure/terraform/terraform.mk @@ -0,0 +1,163 @@ +##################################################################################### +# Terraform Module +# Workflow Automation for Multi-Environment Deployments +# +# Version: 1.0.0 +# Author: AntonioCS +# License: MIT +##################################################################################### +# Overview: +# Provides reusable terraform workflow targets for managing infrastructure +# across multiple environments (local, dev, staging, prod, etc.) +# +# Prerequisites: +# - terraform CLI installed and in PATH +# - Environment directories under tf_env_dir +# +# Key Pattern: +# make terraform// +# +# Examples: +# make terraform/init/local +# make terraform/plan/dev +# make terraform/apply/prod +# +##################################################################################### + +ifndef __MB_MODULES_TERRAFORM__ +__MB_MODULES_TERRAFORM__ := 1 + +##################################################################################### +# Helper Functions +##################################################################################### + +## @function tf_build_chdir +## @desc Build the -chdir argument or empty string based on tf_chdir_flag +## @arg 1: env (required) - Environment name +## @returns -chdir= or empty string +## @group terraform +define tf_build_chdir +$(strip \ + $(if $(value 1),,$(call mb_printf_error,$0: env required)) \ + $(eval $0_arg1_env := $(strip $1)) \ + $(if $(filter $(mb_true),$(tf_chdir_flag)), \ + -chdir=$(tf_env_dir)/$($0_arg1_env) \ + ) \ +) +endef + +## @function tf_build_var_file +## @desc Build -var-file arguments for each file in tf_shared_vars +## @desc Supports multiple space-delimited paths. Use mb_space_guard for paths with spaces. +## @returns -var-file="" for each file, or empty string if tf_shared_vars is empty +## @group terraform +## @example Single file: -var-file="common.tfvars" +## @example Multiple: -var-file="common.tfvars" -var-file="secrets.tfvars" +define tf_build_var_file +$(strip \ + $(if $(value tf_shared_vars), \ + $(foreach $0_file,$(tf_shared_vars), \ + -var-file="$(call mb_space_unguard,$($0_file))" \ + ) \ + ) \ +) +endef + +## @function tf_is_auto_approve_env +## @desc Check if environment is in auto-approve list +## @arg 1: env (required) - Environment name +## @returns $(mb_true) if auto-approve, empty otherwise +## @group terraform +define tf_is_auto_approve_env +$(strip \ + $(if $(value 1),,$(call mb_printf_error,$0: env required)) \ + $(eval $0_arg1_env := $(strip $1)) \ + $(if $(filter $($0_arg1_env),$(tf_auto_approve_envs)),$(mb_true)) \ +) +endef + +## @function tf_run +## @desc Execute terraform command in environment directory +## @desc Automatically includes shared tfvars if tf_shared_vars is set +## @arg 1: env (required) - Environment name (e.g., local, dev, prod) +## @arg 2: command (required) - Terraform command (e.g., init, plan, apply) +## @arg 3: extra_args (optional) - Additional arguments +## @returns Command output via mb_invoke +## @group terraform +## @example $(call tf_run,local,init) +## @example $(call tf_run,dev,plan,-detailed-exitcode) +## @example $(call tf_run,prod,apply,-auto-approve) +define tf_run +$(strip \ + $(if $(value 1),,$(call mb_printf_error,$0: env required)) \ + $(if $(value 2),,$(call mb_printf_error,$0: command required)) \ + $(eval $0_arg1_env := $(strip $1)) \ + $(eval $0_arg2_cmd := $(strip $2)) \ + $(eval $0_arg3_extra := $(if $(value 3),$(strip $3))) \ + $(eval $0_chdir := $(call tf_build_chdir,$($0_arg1_env))) \ + $(eval $0_var_file := $(call tf_build_var_file)) \ + $(call mb_invoke,$(tf_bin) $($0_chdir) $($0_arg2_cmd) $($0_var_file) $($0_arg3_extra)) \ +) +endef + +ifndef __MB_TEST_DISCOVERY__ +##################################################################################### +# Generic Terraform Targets (pattern rules) +##################################################################################### + +terraform/init/%: ## Initialize Terraform for environment (usage: make terraform/init/local) + $(call mb_printf_info,Initializing Terraform for $* environment...) + $(call tf_run,$*,init) + +terraform/plan/%: ## Plan Terraform changes (usage: make terraform/plan/dev) + $(call mb_printf_info,Planning Terraform changes for $* environment...) + $(call tf_run,$*,plan) + +terraform/apply/%: ## Apply Terraform changes (usage: make terraform/apply/local) + $(call mb_printf_info,Applying Terraform for $* environment...) + $(if $(call tf_is_auto_approve_env,$*), \ + $(call tf_run,$*,apply,-auto-approve), \ + $(call tf_run,$*,apply) \ + ) + +terraform/destroy/%: ## Destroy Terraform infrastructure (usage: make terraform/destroy/local) + $(if $(filter $(mb_true),$(tf_destroy_confirm)), \ + $(call mb_user_confirm,This will destroy all infrastructure in $* environment!) \ + ) + $(if $(call tf_is_auto_approve_env,$*), \ + $(call tf_run,$*,destroy,-auto-approve), \ + $(call tf_run,$*,destroy) \ + ) + +terraform/validate/%: ## Validate Terraform configuration (usage: make terraform/validate/local) + $(call mb_printf_info,Validating Terraform configuration for $*...) + $(call tf_run,$*,validate) + +terraform/output/%: ## Show Terraform outputs (usage: make terraform/output/local) + $(call tf_run,$*,output) + +terraform/state/list/%: ## List Terraform state resources (usage: make terraform/state/list/local) + $(call tf_run,$*,state list) + +terraform/refresh/%: ## Refresh Terraform state (usage: make terraform/refresh/local) + $(call mb_printf_info,Refreshing Terraform state for $*...) + $(call tf_run,$*,refresh) + +##################################################################################### +# Utility Targets +##################################################################################### + +terraform/fmt: ## Format all Terraform files in tf_root_dir + $(call mb_printf_info,Formatting Terraform files in $(tf_root_dir)...) + $(call mb_invoke,$(tf_bin) fmt -recursive $(tf_root_dir)) + +terraform/fmt/check: ## Check if Terraform files are formatted + $(call mb_printf_info,Checking Terraform formatting in $(tf_root_dir)...) + $(call mb_invoke,$(tf_bin) fmt -check -recursive $(tf_root_dir)) + +terraform/version: ## Show Terraform version + $(call mb_invoke,$(tf_bin) version) + +endif # __MB_TEST_DISCOVERY__ + +endif # __MB_MODULES_TERRAFORM__ diff --git a/modules/infrastructure/terraform/terraform_test.mk b/modules/infrastructure/terraform/terraform_test.mk new file mode 100644 index 0000000..ba31e26 --- /dev/null +++ b/modules/infrastructure/terraform/terraform_test.mk @@ -0,0 +1,226 @@ +##################################################################################### +# Project: MakeBind +# File: modules/infrastructure/terraform/terraform_test.mk +# Description: Tests for the terraform module +# Author: AntonioCS +# License: MIT License +##################################################################################### + +include $(mb_core_path)/util.mk +include $(mb_core_path)/functions.mk + +## Load terraform module +include $(mb_modules_path)/infrastructure/terraform/mod_config.mk +include $(mb_modules_path)/infrastructure/terraform/terraform.mk + +###################################################################################### +# tf_build_chdir tests +###################################################################################### + +define test_modules_terraform_build_chdir_with_flag + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_chdir_flag := $(mb_true)) + $(eval tf_env_dir := terraform/environments) + + $(eval $0_result := $(call tf_build_chdir,local)) + $(call mb_assert_eq,-chdir=terraform/environments/local,$($0_result)) + + $(eval $0_result := $(call tf_build_chdir,dev)) + $(call mb_assert_eq,-chdir=terraform/environments/dev,$($0_result)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_build_chdir_without_flag + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_chdir_flag := $(mb_false)) + + $(eval $0_result := $(call tf_build_chdir,local)) + $(call mb_assert_empty,$($0_result)) + + $(eval tf_chdir_flag := $(mb_true)) + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_build_chdir_error_without_env + $(eval mb_invoke_silent := $(mb_on)) + + $(call mb_assert_was_called,mb_printf_error,1) + $(eval $0_result := $(call tf_build_chdir,)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +###################################################################################### +# tf_build_var_file tests +###################################################################################### + +define test_modules_terraform_build_var_file_single + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_shared_vars := ../../shared/common.tfvars) + + $(eval $0_result := $(call tf_build_var_file)) + $(call mb_assert_contains,-var-file="../../shared/common.tfvars",$($0_result)) + + $(eval tf_shared_vars :=) + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_build_var_file_multiple + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_shared_vars := ../../shared/common.tfvars ../../shared/secrets.tfvars) + + $(eval $0_result := $(call tf_build_var_file)) + $(call mb_assert_contains,-var-file="../../shared/common.tfvars",$($0_result)) + $(call mb_assert_contains,-var-file="../../shared/secrets.tfvars",$($0_result)) + + $(eval tf_shared_vars :=) + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_build_var_file_empty + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_shared_vars :=) + + $(eval $0_result := $(call tf_build_var_file)) + $(call mb_assert_empty,$($0_result)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +###################################################################################### +# tf_is_auto_approve_env tests +###################################################################################### + +define test_modules_terraform_is_auto_approve_env_match + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_auto_approve_envs := local dev) + + $(eval $0_result := $(call tf_is_auto_approve_env,local)) + $(call mb_assert_eq,$(mb_true),$($0_result)) + + $(eval $0_result := $(call tf_is_auto_approve_env,dev)) + $(call mb_assert_eq,$(mb_true),$($0_result)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_is_auto_approve_env_no_match + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_auto_approve_envs := local) + + $(eval $0_result := $(call tf_is_auto_approve_env,prod)) + $(call mb_assert_empty,$($0_result)) + + $(eval $0_result := $(call tf_is_auto_approve_env,staging)) + $(call mb_assert_empty,$($0_result)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +###################################################################################### +# tf_run tests +###################################################################################### + +define test_modules_terraform_run_basic + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_bin := terraform) + $(eval tf_chdir_flag := $(mb_true)) + $(eval tf_env_dir := terraform/environments) + $(eval tf_shared_vars :=) + + $(eval $0_result := $(call tf_run,local,init)) + $(call mb_assert_contains,terraform,$($0_result)) + $(call mb_assert_contains,-chdir=terraform/environments/local,$($0_result)) + $(call mb_assert_contains,init,$($0_result)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_run_with_extra_args + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_bin := terraform) + $(eval tf_chdir_flag := $(mb_true)) + $(eval tf_env_dir := terraform/environments) + $(eval tf_shared_vars :=) + + $(eval $0_result := $(call tf_run,dev,plan,-detailed-exitcode)) + $(call mb_assert_contains,terraform,$($0_result)) + $(call mb_assert_contains,-chdir=terraform/environments/dev,$($0_result)) + $(call mb_assert_contains,plan,$($0_result)) + $(call mb_assert_contains,-detailed-exitcode,$($0_result)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_run_with_shared_vars + $(eval mb_invoke_silent := $(mb_on)) + $(eval tf_bin := terraform) + $(eval tf_chdir_flag := $(mb_true)) + $(eval tf_env_dir := terraform/environments) + $(eval tf_shared_vars := ../../shared/common.tfvars) + + $(eval $0_result := $(call tf_run,local,plan)) + $(call mb_assert_contains,terraform,$($0_result)) + $(call mb_assert_contains,-chdir=terraform/environments/local,$($0_result)) + $(call mb_assert_contains,plan,$($0_result)) + $(call mb_assert_contains,-var-file="../../shared/common.tfvars",$($0_result)) + + $(eval tf_shared_vars :=) + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_run_error_without_env + $(eval mb_invoke_silent := $(mb_on)) + + ## Expects 2 calls due to cascading: + ## 1. tf_run: env required + ## 2. tf_build_chdir: env required (called internally) + $(call mb_assert_was_called,mb_printf_error,2) + $(eval $0_result := $(call tf_run,,init)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +define test_modules_terraform_run_error_without_command + $(eval mb_invoke_silent := $(mb_on)) + + $(call mb_assert_was_called,mb_printf_error,1) + $(eval $0_result := $(call tf_run,local,)) + + $(eval mb_invoke_silent := $(mb_off)) +endef + +###################################################################################### +# Configuration tests +###################################################################################### + +define test_modules_terraform_config_defaults + ## Reset to defaults + $(eval tf_bin := terraform) + $(eval tf_root_dir := terraform) + $(eval tf_env_dir := terraform/environments) + $(eval tf_default_env := local) + $(eval tf_auto_approve_envs := local) + $(eval tf_destroy_confirm := $(mb_true)) + $(eval tf_chdir_flag := $(mb_true)) + + $(call mb_assert_eq,terraform,$(tf_bin)) + $(call mb_assert_eq,terraform,$(tf_root_dir)) + $(call mb_assert_eq,terraform/environments,$(tf_env_dir)) + $(call mb_assert_eq,local,$(tf_default_env)) + $(call mb_assert_eq,local,$(tf_auto_approve_envs)) + $(call mb_assert_eq,$(mb_true),$(tf_destroy_confirm)) + $(call mb_assert_eq,$(mb_true),$(tf_chdir_flag)) +endef + +###################################################################################### +# Function definition tests +###################################################################################### + +define test_modules_terraform_functions_defined + $(call mb_assert,$(value tf_build_chdir),tf_build_chdir function should be defined) + $(call mb_assert,$(value tf_build_var_file),tf_build_var_file function should be defined) + $(call mb_assert,$(value tf_is_auto_approve_env),tf_is_auto_approve_env function should be defined) + $(call mb_assert,$(value tf_run),tf_run function should be defined) +endef