From c9c94e5051979bfa8c5d397f2edfb56dfcf0beaa Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 11:36:04 -0600 Subject: [PATCH 01/15] Initial test validation effort. --- .../providers/azure/resource-group/test.sh | 218 ++++++++++++++ .../azure/resource-group/testing/main.tf | 35 ++- .../azure/resource-group/testing/unit_test.go | 5 +- .../providers/azure/storage-account/main.tf | 14 + .../providers/azure/storage-account/test.sh | 266 ++++++++++++++++++ .../azure/storage-account/testing/main.tf | 68 +++-- .../storage-account/testing/unit_test.go | 19 +- .../azure/storage-account/tests/tf_options.go | 11 +- .../unit/storage_deployment_unit_test.go | 18 +- .../azure/storage-account/variables.tf | 9 +- 10 files changed, 618 insertions(+), 45 deletions(-) create mode 100755 infra/modules/providers/azure/resource-group/test.sh create mode 100755 infra/modules/providers/azure/storage-account/test.sh diff --git a/infra/modules/providers/azure/resource-group/test.sh b/infra/modules/providers/azure/resource-group/test.sh new file mode 100755 index 000000000..f5fd6a3cf --- /dev/null +++ b/infra/modules/providers/azure/resource-group/test.sh @@ -0,0 +1,218 @@ +#!/bin/bash + +# Exit on error +set -e + +############################### +# Script Path Configuration +############################### +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +TESTING_DIR="${SCRIPT_DIR}/testing" + +############################### +# Required Environment Variables +############################### +# ARM_SUBSCRIPTION_ID should be set in the environment +# Example: export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000" + +############################### +# Optional Variables +############################### +# These can be overridden by setting them before running the script +RESOURCE_GROUP_NAME=${RESOURCE_GROUP_NAME:-"osdu-test"} +LOCATION=${LOCATION:-"eastus2"} + +############################### +# Utility Functions +############################### + +print_header() { + local title="$1" + printf "\n" + tput setaf 3 + echo "==================================================================" + tput setaf 6 + echo " ${title} " + tput setaf 3 + echo "==================================================================" + tput sgr0 + printf "\n" +} + +log() { + local _msg="$1" + local _color="${2:-5}" + + if [[ -t 1 ]]; then + tput setaf $_color + echo "$_msg" + tput sgr0 + else + echo "$_msg" + fi +} + +print_separator() { + if [[ -t 1 ]]; then + echo "" + tput setaf 4 # Blue color for sub-headers + echo "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼" + tput sgr0 + echo "" + else + echo "\n▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼\n" + fi +} + +############################### +# Variable Validation +############################### + +validate_variables() { + print_header "Variable Validation" + + if [ -z "$ARM_SUBSCRIPTION_ID" ]; then + log "Error: ARM_SUBSCRIPTION_ID environment variable is not set" 1 + log "Usage: export ARM_SUBSCRIPTION_ID='00000000-0000-0000-0000-000000000000' && $0" 1 + exit 1 + fi + log "Using Subscription: $ARM_SUBSCRIPTION_ID" 6 + log "Using Resource Group: $RESOURCE_GROUP_NAME" 6 + log "Using Location: $LOCATION" 6 +} + +############################### +# Setup Functions +############################### + +setup_azure() { + print_header "Azure Setup" + + # Only verify subscription access + log "Verifying Azure subscription..." 5 + az account show --subscription "$ARM_SUBSCRIPTION_ID" || { + log "Error: Could not access subscription $ARM_SUBSCRIPTION_ID" 1 + exit 1 + } + log "Azure subscription verified successfully" 2 +} + +create_tfvars_files() { + print_header "Terraform Configuration" + + log "Creating testing.tfvars files..." 5 + mkdir -p "${TESTING_DIR}" + cat > "${TESTING_DIR}/testing.tfvars" << EOF +name = "$RESOURCE_GROUP_NAME" +location = "$LOCATION" +EOF + log "Configuration files created successfully" 2 +} + +############################### +# Testing Functions +############################### + +run_unit_tests() { + print_header "Unit Tests" + log "Running unit tests..." 5 + echo "" + pushd "${TESTING_DIR}" > /dev/null + go test -v + popd > /dev/null + echo "" + log "Unit tests completed" 2 +} + +############################### +# Terraform Functions +############################### + +terraform_init_and_apply() { + print_header "Terraform Deployment" + + log "Initializing Terraform..." 5 + pushd "${TESTING_DIR}" > /dev/null + + print_separator + terraform init + print_separator + + log "Planning Terraform changes..." 5 + print_separator + terraform plan --var-file=testing.tfvars + print_separator + + log "Applying Terraform changes..." 5 + print_separator + terraform apply --var-file=testing.tfvars -auto-approve + print_separator + + log "Terraform deployment completed successfully" 2 + popd > /dev/null +} + +############################### +# Cleanup Functions +############################### + +cleanup_tfvars() { + log "Cleaning up testing.tfvars files..." 6 + rm -f "${TESTING_DIR}/testing.tfvars" +} + +cleanup_terraform() { + if [ -d "${TESTING_DIR}/.terraform" ]; then + pushd "${TESTING_DIR}" > /dev/null + + log "Destroying Terraform resources..." 3 + terraform destroy --var-file=testing.tfvars -auto-approve || true + + log "Cleaning up Terraform files..." 6 + rm -rf .terraform/ + rm -f .terraform.lock.hcl + rm -f terraform.tfstate + rm -f terraform.tfstate.backup + rm -rf terraform.tfstate.d/ + + popd > /dev/null + fi +} + +cleanup() { + print_header "Cleanup" + log "Starting cleanup..." 5 + + cleanup_terraform + cleanup_tfvars + + log "Cleanup completed successfully" 2 +} + +############################### +# Main Execution +############################### + +main() { + print_header "Test Execution" + log "Starting test setup..." 5 + + # Trap cleanup on exit + trap cleanup EXIT + + # Validate variables + validate_variables + + # Setup + setup_azure + create_tfvars_files + + # Run unit tests first (these don't require infrastructure) + run_unit_tests + + # Now deploy and test infrastructure + terraform_init_and_apply +} + +# Execute main function +main \ No newline at end of file diff --git a/infra/modules/providers/azure/resource-group/testing/main.tf b/infra/modules/providers/azure/resource-group/testing/main.tf index fdebf47e0..ad2163dc4 100644 --- a/infra/modules/providers/azure/resource-group/testing/main.tf +++ b/infra/modules/providers/azure/resource-group/testing/main.tf @@ -12,6 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.90.0" + } + } +} + provider "azurerm" { features {} } @@ -19,10 +28,32 @@ provider "azurerm" { module "resource_group" { source = "../" - name = "osdu-module" - location = "eastus2" + name = var.name + location = var.location resource_tags = { environment = "test-environment" } +} + +variable "name" { + type = string + description = "The name of the resource group" +} + +variable "location" { + type = string + description = "The location of the resource group" +} + +output "resource_group_name" { + value = module.resource_group.name +} + +output "resource_group_location" { + value = module.resource_group.location +} + +output "resource_group_id" { + value = module.resource_group.id } \ No newline at end of file diff --git a/infra/modules/providers/azure/resource-group/testing/unit_test.go b/infra/modules/providers/azure/resource-group/testing/unit_test.go index b554f7dc6..5d3603365 100644 --- a/infra/modules/providers/azure/resource-group/testing/unit_test.go +++ b/infra/modules/providers/azure/resource-group/testing/unit_test.go @@ -30,6 +30,10 @@ var count = 2 var tfOptions = &terraform.Options{ TerraformDir: "./", Upgrade: true, + Vars: map[string]interface{}{ + "name": name, + "location": location, + }, } func asMap(t *testing.T, jsonString string) map[string]interface{} { @@ -41,7 +45,6 @@ func asMap(t *testing.T, jsonString string) map[string]interface{} { } func TestTemplate(t *testing.T) { - expectedResult := asMap(t, `{ "location": "`+location+`" }`) diff --git a/infra/modules/providers/azure/storage-account/main.tf b/infra/modules/providers/azure/storage-account/main.tf index 5f1bb1eb2..81583f4b5 100644 --- a/infra/modules/providers/azure/storage-account/main.tf +++ b/infra/modules/providers/azure/storage-account/main.tf @@ -12,6 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. + +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.90.0" + } + } +} + +provider "azurerm" { + features {} +} + data "azurerm_resource_group" "main" { name = var.resource_group_name } diff --git a/infra/modules/providers/azure/storage-account/test.sh b/infra/modules/providers/azure/storage-account/test.sh new file mode 100755 index 000000000..a25a756bc --- /dev/null +++ b/infra/modules/providers/azure/storage-account/test.sh @@ -0,0 +1,266 @@ +#!/bin/bash + +# Exit on error +set -e + +############################### +# Script Path Configuration +############################### +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +TESTING_DIR="${SCRIPT_DIR}/testing" +UNIT_TEST_DIR="${SCRIPT_DIR}/tests/unit" +INTEGRATION_TEST_DIR="${SCRIPT_DIR}/tests/integration" + +############################### +# Required Environment Variables +############################### +# ARM_SUBSCRIPTION_ID should be set in the environment +# Example: export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000" + +############################### +# Optional Variables +############################### +# These can be overridden by setting them before running the script +RESOURCE_GROUP_NAME=${RESOURCE_GROUP_NAME:-"osdu-test"} +LOCATION=${LOCATION:-"eastus2"} +STORAGE_ACCOUNT_NAME=${STORAGE_ACCOUNT_NAME:-""} # Will be auto-generated if not provided + +############################### +# Utility Functions +############################### + +print_header() { + local title="$1" + printf "\n" + tput setaf 3 + echo "==================================================================" + tput setaf 6 + echo " ${title} " + tput setaf 3 + echo "==================================================================" + tput sgr0 + printf "\n" +} + +log() { + local _msg="$1" + local _color="${2:-5}" + + if [[ -t 1 ]]; then + tput setaf $_color + echo "$_msg" + tput sgr0 + else + echo "$_msg" + fi +} + +print_separator() { + if [[ -t 1 ]]; then + echo "" + tput setaf 4 # Blue color for sub-headers + echo "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼" + tput sgr0 + echo "" + else + echo "\n▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼\n" + fi +} + +############################### +# Variable Validation +############################### + +validate_variables() { + print_header "Variable Validation" + + if [ -z "$ARM_SUBSCRIPTION_ID" ]; then + log "Error: ARM_SUBSCRIPTION_ID environment variable is not set" 1 + log "Usage: export ARM_SUBSCRIPTION_ID='00000000-0000-0000-0000-000000000000' && $0" 1 + exit 1 + fi + + # Generate unique storage account name if not provided + if [ -z "$STORAGE_ACCOUNT_NAME" ]; then + UNIQUE=$(openssl rand -base64 20 | tr -dc 'a-z' | head -c 20; echo) + STORAGE_ACCOUNT_NAME="${UNIQUE}sa" + fi + + log "Using Subscription: $ARM_SUBSCRIPTION_ID" 6 + log "Using Resource Group: $RESOURCE_GROUP_NAME" 6 + log "Using Location: $LOCATION" 6 + log "Using Storage Account: $STORAGE_ACCOUNT_NAME" 6 +} + +############################### +# Setup Functions +############################### + +setup_azure() { + print_header "Azure Setup" + + # Only verify subscription access + log "Verifying Azure subscription..." 5 + az account show --subscription "$ARM_SUBSCRIPTION_ID" || { + log "Error: Could not access subscription $ARM_SUBSCRIPTION_ID" 1 + exit 1 + } + log "Azure subscription verified successfully" 2 + + # Create resource group if it doesn't exist + log "Creating resource group if it doesn't exist..." 5 + az group create --name "$RESOURCE_GROUP_NAME" --location "$LOCATION" --subscription "$ARM_SUBSCRIPTION_ID" || { + log "Error: Could not create resource group $RESOURCE_GROUP_NAME" 1 + exit 1 + } + log "Resource group ready" 2 +} + +create_tfvars_files() { + print_header "Terraform Configuration" + + log "Creating testing.tfvars files..." 5 + for dir in "${SCRIPT_DIR}" "${UNIT_TEST_DIR}" "${INTEGRATION_TEST_DIR}" "${TESTING_DIR}"; do + mkdir -p "$dir" + cat > "${dir}/testing.tfvars" << EOF +name = "$STORAGE_ACCOUNT_NAME" +resource_group_name = "$RESOURCE_GROUP_NAME" +EOF + done + log "Configuration files created successfully" 2 +} + +############################### +# Testing Functions +############################### + +run_basic_unit_tests() { + print_header "Basic Unit Tests" + log "Running basic unit tests..." 5 + echo "" + pushd "${TESTING_DIR}" > /dev/null + go test -v + popd > /dev/null + echo "" + log "Basic unit tests completed" 2 +} + +run_extended_unit_tests() { + print_header "Extended Unit Tests" + log "Running extended unit tests..." 5 + echo "" + pushd "${SCRIPT_DIR}/tests" > /dev/null + go test -v ./unit/... + popd > /dev/null + echo "" + log "Extended unit tests completed" 2 +} + +run_integration_tests() { + print_header "Integration Tests" + log "Running integration tests..." 5 + echo "" + pushd "${SCRIPT_DIR}/tests" > /dev/null + go test -v ./integration/... + popd > /dev/null + echo "" + log "Integration tests completed" 2 +} + +############################### +# Terraform Functions +############################### + +terraform_init_and_apply() { + print_header "Terraform Deployment" + + log "Initializing Terraform..." 5 + pushd "${TESTING_DIR}" > /dev/null + + print_separator + terraform init + print_separator + + log "Planning Terraform changes..." 5 + print_separator + terraform plan --var-file=testing.tfvars + print_separator + + log "Applying Terraform changes..." 5 + print_separator + terraform apply --var-file=testing.tfvars -auto-approve + print_separator + + log "Terraform deployment completed successfully" 2 + popd > /dev/null +} + +############################### +# Cleanup Functions +############################### + +cleanup_tfvars() { + log "Cleaning up testing.tfvars files..." 6 + rm -f "${SCRIPT_DIR}/testing.tfvars" + rm -f "${UNIT_TEST_DIR}/testing.tfvars" + rm -f "${INTEGRATION_TEST_DIR}/testing.tfvars" + rm -f "${TESTING_DIR}/testing.tfvars" +} + +cleanup_terraform() { + if [ -d "${TESTING_DIR}/.terraform" ]; then + pushd "${TESTING_DIR}" > /dev/null + + log "Destroying Terraform resources..." 3 + terraform destroy --var-file=testing.tfvars -auto-approve || true + + log "Cleaning up Terraform files..." 6 + rm -rf .terraform/ + rm -f .terraform.lock.hcl + rm -f terraform.tfstate + rm -f terraform.tfstate.backup + rm -rf terraform.tfstate.d/ + + popd > /dev/null + fi +} + +cleanup() { + print_header "Cleanup" + log "Starting cleanup..." 5 + + cleanup_terraform + cleanup_tfvars + + log "Cleanup completed successfully" 2 +} + +############################### +# Main Execution +############################### + +main() { + print_header "Test Execution" + log "Starting test setup..." 5 + + # Trap cleanup on exit + trap cleanup EXIT + + # Validate variables + validate_variables + + # Setup + setup_azure + create_tfvars_files + + # Run all tests + run_basic_unit_tests # Simple tests in testing/ + run_extended_unit_tests # More complex tests in tests/unit/ + run_integration_tests # Integration tests in tests/integration/ + + # Now deploy and test infrastructure + terraform_init_and_apply +} + +# Execute main function +main \ No newline at end of file diff --git a/infra/modules/providers/azure/storage-account/testing/main.tf b/infra/modules/providers/azure/storage-account/testing/main.tf index ab8288f0f..9d280850c 100644 --- a/infra/modules/providers/azure/storage-account/testing/main.tf +++ b/infra/modules/providers/azure/storage-account/testing/main.tf @@ -1,32 +1,60 @@ -provider "azurerm" { - features {} +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.90.0" + } + } } -module "resource_group" { - source = "../../resource-group" - - name = "osdu-module" - location = "eastus2" +provider "azurerm" { + features {} } +# Create the storage account module "storage_account" { - source = "../" - depends_on = [module.resource_group] + source = "../" - resource_group_name = module.resource_group.name - name = substr("osdumodule${module.resource_group.random}", 0, 23) + resource_group_name = var.resource_group_name + name = var.name + location = var.location replication_type = "GZRS" - container_names = [ - "osdu-container" - ] - share_names = [ - "osdu-share" - ] - queue_names = [ - "osdu-queue" - ] + container_names = ["osdu-container", "osdu-container-2"] + share_names = ["osdu-share", "osdu-share-2"] + queue_names = ["osdu-queue", "osdu-queue-2"] resource_tags = { environment = "test-environment" } } + +# Variables +variable "name" { + type = string + description = "The name of the storage account" +} + +variable "resource_group_name" { + type = string + description = "The name of the resource group" +} + +variable "location" { + type = string + description = "The location of the storage account" + default = "eastus2" +} + +# Outputs +output "storage_account_id" { + value = module.storage_account.id +} + +output "storage_account_name" { + value = module.storage_account.name +} + +output "storage_account_primary_access_key" { + value = module.storage_account.primary_access_key + sensitive = true +} diff --git a/infra/modules/providers/azure/storage-account/testing/unit_test.go b/infra/modules/providers/azure/storage-account/testing/unit_test.go index b0bd22bc4..b0342c75f 100644 --- a/infra/modules/providers/azure/storage-account/testing/unit_test.go +++ b/infra/modules/providers/azure/storage-account/testing/unit_test.go @@ -10,11 +10,12 @@ import ( ) var name = "storage-" -var count = 7 +var count = 7 // Just the storage account resources var tfOptions = &terraform.Options{ TerraformDir: "./", Upgrade: true, + VarFiles: []string{"testing.tfvars"}, } func asMap(t *testing.T, jsonString string) map[string]interface{} { @@ -26,6 +27,14 @@ func asMap(t *testing.T, jsonString string) map[string]interface{} { } func TestTemplate(t *testing.T) { + uniqueID := random.UniqueId() + workspace := name + uniqueID + + // Use the same resource group name that the test script creates + tfOptions.Vars = map[string]interface{}{ + "name": workspace + "-sa", + "resource_group_name": "osdu-test", // Match the test script's resource group + } expectedResult := asMap(t, `{ "account_kind" : "StorageV2", @@ -50,14 +59,14 @@ func TestTemplate(t *testing.T) { testFixture := infratests.UnitTestFixture{ GoTest: t, TfOptions: tfOptions, - Workspace: name + random.UniqueId(), + Workspace: workspace, PlanAssertions: nil, ExpectedResourceCount: count, ExpectedResourceAttributeValues: infratests.ResourceDescription{ - "module.storage_account.azurerm_storage_account.main": expectedResult, + "module.storage_account.azurerm_storage_account.main": expectedResult, "module.storage_account.azurerm_storage_container.main[0]": expectedContainer, - "module.storage_account.azurerm_storage_share.main[0]": expectedShare, - "module.storage_account.azurerm_storage_queue.main[0]": expectedQueue, + "module.storage_account.azurerm_storage_share.main[0]": expectedShare, + "module.storage_account.azurerm_storage_queue.main[0]": expectedQueue, }, } diff --git a/infra/modules/providers/azure/storage-account/tests/tf_options.go b/infra/modules/providers/azure/storage-account/tests/tf_options.go index 26a5690bf..fe605376b 100644 --- a/infra/modules/providers/azure/storage-account/tests/tf_options.go +++ b/infra/modules/providers/azure/storage-account/tests/tf_options.go @@ -31,12 +31,17 @@ var ResourceGroupName = os.Getenv("RESOURCE_GROUP_NAME") // StorageTFOptions common terraform options used for unit and integration testing var StorageTFOptions = &terraform.Options{ - TerraformDir: "../../", + TerraformDir: "../../testing", Vars: map[string]interface{}{ "resource_group_name": ResourceGroupName, "name": StorageAccount, - "container_names": []interface{}{ - ContainerName, + "replication_type": "GZRS", + "location": "eastus2", + "container_names": []interface{}{ContainerName}, + "share_names": []interface{}{"osdu-share", "osdu-share-2"}, + "queue_names": []interface{}{"osdu-queue", "osdu-queue-2"}, + "resource_tags": map[string]interface{}{ + "environment": "test-environment", }, }, } diff --git a/infra/modules/providers/azure/storage-account/tests/unit/storage_deployment_unit_test.go b/infra/modules/providers/azure/storage-account/tests/unit/storage_deployment_unit_test.go index 7d0d0bcb4..87d4f3dbd 100644 --- a/infra/modules/providers/azure/storage-account/tests/unit/storage_deployment_unit_test.go +++ b/infra/modules/providers/azure/storage-account/tests/unit/storage_deployment_unit_test.go @@ -33,26 +33,20 @@ func asMap(t *testing.T, jsonString string) map[string]interface{} { } func TestStorageDeployment_Unit(t *testing.T) { - expectedResult := asMap(t, `{ - "account_kind": "StorageV2", - "account_replication_type": "LRS", - "account_tier": "Standard", - "account_encryption_source": "Microsoft.Storage" - }`) - - expectedContainerResult := asMap(t, `{ - "container_access_type": "private", - "name": "`+tests.ContainerName+`" + "account_kind": "StorageV2", + "account_replication_type": "GZRS", + "account_tier": "Standard" }`) testFixture := infratests.UnitTestFixture{ GoTest: t, TfOptions: tests.StorageTFOptions, + Workspace: "default-unit-testing", + PlanAssertions: nil, ExpectedResourceCount: resourceCount, ExpectedResourceAttributeValues: infratests.ResourceDescription{ - "azurerm_storage_account.main": expectedResult, - "azurerm_storage_container.main[0]": expectedContainerResult, + "azurerm_storage_account.main": expectedResult, }, } diff --git a/infra/modules/providers/azure/storage-account/variables.tf b/infra/modules/providers/azure/storage-account/variables.tf index e165994f5..f8e3d1f63 100755 --- a/infra/modules/providers/azure/storage-account/variables.tf +++ b/infra/modules/providers/azure/storage-account/variables.tf @@ -15,7 +15,7 @@ # Naming Items (required) variable "name" { - description = "The name of the storage account service." + description = "The name of the storage account" type = string } @@ -37,10 +37,15 @@ variable "queue_names" { } variable "resource_group_name" { - description = "The name of the resource group." + description = "The name of the resource group" type = string } +variable "location" { + type = string + description = "The location/region where the storage account is created" +} + # Tier Items (optional) -- GitLab From 06907bb979c6992af474e3ccc52eb5a0c5c85260 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 13:05:42 -0600 Subject: [PATCH 02/15] Functioning Storage --- .../providers/azure/storage-account/test.sh | 119 ++++++++++++++++-- .../azure/storage-account/testing/main.tf | 38 ++++++ .../storage-account/testing/unit_test.go | 54 +++++--- .../tests/integration/storage_test.go | 6 +- .../azure/storage-account/tests/tf_options.go | 21 ++-- 5 files changed, 199 insertions(+), 39 deletions(-) diff --git a/infra/modules/providers/azure/storage-account/test.sh b/infra/modules/providers/azure/storage-account/test.sh index a25a756bc..30755b574 100755 --- a/infra/modules/providers/azure/storage-account/test.sh +++ b/infra/modules/providers/azure/storage-account/test.sh @@ -21,14 +21,79 @@ INTEGRATION_TEST_DIR="${SCRIPT_DIR}/tests/integration" # Optional Variables ############################### # These can be overridden by setting them before running the script -RESOURCE_GROUP_NAME=${RESOURCE_GROUP_NAME:-"osdu-test"} -LOCATION=${LOCATION:-"eastus2"} +RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"terraform-test"} +DEFAULT_LOCATION="eastus2" STORAGE_ACCOUNT_NAME=${STORAGE_ACCOUNT_NAME:-""} # Will be auto-generated if not provided +############################### +# Help Documentation +############################### + +print_help() { + cat << EOF +Storage Account Module Test Script +================================ + +This script runs unit tests and integration tests for the Azure Storage Account module. + +Required Environment Variables: +----------------------------- +ARM_SUBSCRIPTION_ID Azure subscription ID to use for testing + +Usage: +------ +$0 [resource_group_name] [location] +$0 -h | --help + +Arguments: +---------- +resource_group_name (Optional) Name of resource group to create. + Default: Auto-generated using prefix "terraform-test" + +location (Optional) Azure region to deploy resources. + Default: eastus2 + +Options: +-------- +-h, --help Show this help message + +Examples: +--------- +# Run with auto-generated names in default location +$0 + +# Run with specific resource group name +$0 my-test-rg + +# Run with specific resource group and location +$0 my-test-rg westus2 + +Notes: +------ +- If not specified, a unique resource group name will be generated +- If not specified, a unique storage account name will be generated +- All resources are automatically cleaned up after the test +- The script uses the default Azure CLI subscription if not specified +EOF +} + ############################### # Utility Functions ############################### +generate_unique_name() { + local prefix="$1" + local suffix="$2" + local unique=$(openssl rand -base64 20 | tr -dc 'a-z0-9' | head -c 8) + + if [ -n "$prefix" ]; then + echo "${prefix}-${unique}${suffix}" + else + # For storage accounts, create a name without dashes + echo "tftest${unique}${suffix}" + fi +} + print_header() { local title="$1" printf "\n" @@ -80,11 +145,30 @@ validate_variables() { exit 1 fi + # Always generate a new resource group name unless explicitly passed as argument + if [ -z "$1" ]; then + RESOURCE_GROUP_NAME=$(generate_unique_name "$RESOURCE_GROUP_PREFIX" "") + else + RESOURCE_GROUP_NAME="$1" + fi + # Export for Go tests + export RESOURCE_GROUP_NAME + + # Use default location unless explicitly passed as second argument + if [ -z "$2" ]; then + LOCATION="$DEFAULT_LOCATION" + else + LOCATION="$2" + fi + # Export for Go tests + export LOCATION + # Generate unique storage account name if not provided if [ -z "$STORAGE_ACCOUNT_NAME" ]; then - UNIQUE=$(openssl rand -base64 20 | tr -dc 'a-z' | head -c 20; echo) - STORAGE_ACCOUNT_NAME="${UNIQUE}sa" + STORAGE_ACCOUNT_NAME=$(generate_unique_name "" "sa") fi + # Export for Go tests + export STORAGE_ACCOUNT_NAME log "Using Subscription: $ARM_SUBSCRIPTION_ID" 6 log "Using Resource Group: $RESOURCE_GROUP_NAME" 6 @@ -223,6 +307,11 @@ cleanup_terraform() { popd > /dev/null fi + + log "Deleting resource group..." 3 + az group delete --name "$RESOURCE_GROUP_NAME" --subscription "$ARM_SUBSCRIPTION_ID" --yes || { + log "Warning: Could not delete resource group $RESOURCE_GROUP_NAME" 3 + } } cleanup() { @@ -247,20 +336,30 @@ main() { trap cleanup EXIT # Validate variables - validate_variables + validate_variables "$1" "$2" # Setup setup_azure create_tfvars_files - # Run all tests + # Run unit tests first run_basic_unit_tests # Simple tests in testing/ run_extended_unit_tests # More complex tests in tests/unit/ - run_integration_tests # Integration tests in tests/integration/ - # Now deploy and test infrastructure - terraform_init_and_apply + # Now deploy infrastructure + terraform_init_and_apply # Deploy infrastructure + + # Run integration tests against deployed infrastructure + run_integration_tests # Integration tests in tests/integration/ } +# Check for help flag +case "$1" in + -h|--help) + print_help + exit 0 + ;; +esac + # Execute main function -main \ No newline at end of file +main "$@" \ No newline at end of file diff --git a/infra/modules/providers/azure/storage-account/testing/main.tf b/infra/modules/providers/azure/storage-account/testing/main.tf index 9d280850c..1c5521735 100644 --- a/infra/modules/providers/azure/storage-account/testing/main.tf +++ b/infra/modules/providers/azure/storage-account/testing/main.tf @@ -58,3 +58,41 @@ output "storage_account_primary_access_key" { value = module.storage_account.primary_access_key sensitive = true } + +output "containers" { + value = module.storage_account.containers +} + +output "queues" { + value = module.storage_account.queues +} + +output "shares" { + value = module.storage_account.shares +} + +output "resource_group_name" { + value = module.storage_account.resource_group_name +} + +output "tenant_id" { + value = module.storage_account.tenant_id +} + +output "managed_identities_id" { + value = module.storage_account.managed_identities_id +} + +output "properties" { + value = module.storage_account.properties + sensitive = true +} + +output "primary_blob_endpoint" { + value = module.storage_account.primary_blob_endpoint + sensitive = true +} + +output "primary_queue_endpoint" { + value = module.storage_account.primary_queue_endpoint +} diff --git a/infra/modules/providers/azure/storage-account/testing/unit_test.go b/infra/modules/providers/azure/storage-account/testing/unit_test.go index b0342c75f..75e585674 100644 --- a/infra/modules/providers/azure/storage-account/testing/unit_test.go +++ b/infra/modules/providers/azure/storage-account/testing/unit_test.go @@ -2,6 +2,7 @@ package test import ( "encoding/json" + "os" "testing" "github.com/gruntwork-io/terratest/modules/random" @@ -10,13 +11,7 @@ import ( ) var name = "storage-" -var count = 7 // Just the storage account resources - -var tfOptions = &terraform.Options{ - TerraformDir: "./", - Upgrade: true, - VarFiles: []string{"testing.tfvars"}, -} +var count = 7 func asMap(t *testing.T, jsonString string) map[string]interface{} { var theMap map[string]interface{} @@ -27,33 +22,54 @@ func asMap(t *testing.T, jsonString string) map[string]interface{} { } func TestTemplate(t *testing.T) { + // Get location from environment variable or use default + location := os.Getenv("LOCATION") + if location == "" { + location = "eastus2" + } + + // Get resource group name from environment or generate it + resourceGroupName := os.Getenv("RESOURCE_GROUP_NAME") + if resourceGroupName == "" { + uniqueID := random.UniqueId() + resourceGroupName = "terraform-test-" + uniqueID + } + + // Generate unique names for resources uniqueID := random.UniqueId() workspace := name + uniqueID + storageAcctName := "tftest" + uniqueID + "sa" - // Use the same resource group name that the test script creates - tfOptions.Vars = map[string]interface{}{ - "name": workspace + "-sa", - "resource_group_name": "osdu-test", // Match the test script's resource group + tfOptions := &terraform.Options{ + TerraformDir: "./", + Upgrade: true, + Vars: map[string]interface{}{ + "name": storageAcctName, + "resource_group_name": resourceGroupName, + "location": location, + }, } expectedResult := asMap(t, `{ - "account_kind" : "StorageV2", + "account_kind": "StorageV2", "account_replication_type": "GZRS", - "account_tier": "Standard" + "account_tier": "Standard", + "enable_https_traffic_only": true, + "min_tls_version": "TLS1_2" }`) expectedContainer := asMap(t, `{ - "name" : "osdu-container", + "name": "osdu-container", "container_access_type": "private" }`) expectedShare := asMap(t, `{ - "name" : "osdu-share", + "name": "osdu-share", "quota": 50 }`) expectedQueue := asMap(t, `{ - "name" : "osdu-queue" + "name": "osdu-queue" }`) testFixture := infratests.UnitTestFixture{ @@ -63,10 +79,10 @@ func TestTemplate(t *testing.T) { PlanAssertions: nil, ExpectedResourceCount: count, ExpectedResourceAttributeValues: infratests.ResourceDescription{ - "module.storage_account.azurerm_storage_account.main": expectedResult, + "module.storage_account.azurerm_storage_account.main": expectedResult, "module.storage_account.azurerm_storage_container.main[0]": expectedContainer, - "module.storage_account.azurerm_storage_share.main[0]": expectedShare, - "module.storage_account.azurerm_storage_queue.main[0]": expectedQueue, + "module.storage_account.azurerm_storage_share.main[0]": expectedShare, + "module.storage_account.azurerm_storage_queue.main[0]": expectedQueue, }, } diff --git a/infra/modules/providers/azure/storage-account/tests/integration/storage_test.go b/infra/modules/providers/azure/storage-account/tests/integration/storage_test.go index 64992406b..7147cd5a9 100644 --- a/infra/modules/providers/azure/storage-account/tests/integration/storage_test.go +++ b/infra/modules/providers/azure/storage-account/tests/integration/storage_test.go @@ -22,7 +22,7 @@ import ( "github.com/microsoft/cobalt/test-harness/infratests" ) -var outputVariableCount = 7 +var outputVariableCount = 12 func TestServiceDeployment(t *testing.T) { if tests.ResourceGroupName == "" { @@ -35,10 +35,10 @@ func TestServiceDeployment(t *testing.T) { testFixture := infratests.IntegrationTestFixture{ GoTest: t, - TfOptions: tests.StorageTFOptions, + TfOptions: tests.StorageIntegrationTFOptions, ExpectedTfOutputCount: outputVariableCount, TfOutputAssertions: []infratests.TerraformOutputValidation{ - InspectStorageAccount("name", "containers", "resource_group_name"), + InspectStorageAccount("storage_account_name", "containers", "resource_group_name"), }, } infratests.RunIntegrationTests(&testFixture) diff --git a/infra/modules/providers/azure/storage-account/tests/tf_options.go b/infra/modules/providers/azure/storage-account/tests/tf_options.go index fe605376b..44dec69fa 100644 --- a/infra/modules/providers/azure/storage-account/tests/tf_options.go +++ b/infra/modules/providers/azure/storage-account/tests/tf_options.go @@ -29,19 +29,26 @@ var ContainerName = os.Getenv("CONTAINER_NAME") // ResourceGroupName - The Resource Group Name var ResourceGroupName = os.Getenv("RESOURCE_GROUP_NAME") -// StorageTFOptions common terraform options used for unit and integration testing +// StorageTFOptions common terraform options used for unit testing var StorageTFOptions = &terraform.Options{ - TerraformDir: "../../testing", + TerraformDir: "../../", // Point to module directory for unit tests + Vars: map[string]interface{}{ + "resource_group_name": ResourceGroupName, + "name": StorageAccount, + "replication_type": "GZRS", + "location": "eastus2", + "container_names": []interface{}{ContainerName}, + }, +} + +// StorageIntegrationTFOptions terraform options used for integration testing +var StorageIntegrationTFOptions = &terraform.Options{ + TerraformDir: "../../testing", // Point to testing directory for integration tests Vars: map[string]interface{}{ "resource_group_name": ResourceGroupName, "name": StorageAccount, "replication_type": "GZRS", "location": "eastus2", "container_names": []interface{}{ContainerName}, - "share_names": []interface{}{"osdu-share", "osdu-share-2"}, - "queue_names": []interface{}{"osdu-queue", "osdu-queue-2"}, - "resource_tags": map[string]interface{}{ - "environment": "test-environment", - }, }, } -- GitLab From 0698c57b83435e000d1240ae93f1766980bbc2bb Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 13:14:55 -0600 Subject: [PATCH 03/15] Script cleanup --- .../providers/azure/storage-account/test.sh | 189 +++++++++--------- 1 file changed, 97 insertions(+), 92 deletions(-) diff --git a/infra/modules/providers/azure/storage-account/test.sh b/infra/modules/providers/azure/storage-account/test.sh index 30755b574..a8601ca80 100755 --- a/infra/modules/providers/azure/storage-account/test.sh +++ b/infra/modules/providers/azure/storage-account/test.sh @@ -14,8 +14,7 @@ INTEGRATION_TEST_DIR="${SCRIPT_DIR}/tests/integration" ############################### # Required Environment Variables ############################### -# ARM_SUBSCRIPTION_ID should be set in the environment -# Example: export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000" +: "${ARM_SUBSCRIPTION_ID:?'ARM_SUBSCRIPTION_ID environment variable is required'}" ############################### # Optional Variables @@ -82,28 +81,37 @@ EOF ############################### generate_unique_name() { - local prefix="$1" - local suffix="$2" - local unique=$(openssl rand -base64 20 | tr -dc 'a-z0-9' | head -c 8) + local prefix="${1:-}" + local suffix="${2:-}" + local unique - if [ -n "$prefix" ]; then - echo "${prefix}-${unique}${suffix}" + # More portable way to generate random string + if command -v openssl >/dev/null 2>&1; then + unique=$(openssl rand -hex 4 | tr '[:upper:]' '[:lower:]') else - # For storage accounts, create a name without dashes - echo "tftest${unique}${suffix}" + # Fallback to built-in $RANDOM if openssl is not available + unique=$(printf "%08x" $((RANDOM + RANDOM + RANDOM + RANDOM))) fi + + [[ -n "$prefix" ]] && echo "${prefix}-${unique}${suffix}" || echo "tftest${unique}${suffix}" } print_header() { local title="$1" printf "\n" - tput setaf 3 - echo "==================================================================" - tput setaf 6 - echo " ${title} " - tput setaf 3 - echo "==================================================================" - tput sgr0 + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + tput setaf 3 2>/dev/null || true + printf "%s\n" "==================================================================" + tput setaf 6 2>/dev/null || true + printf "%s\n" " ${title} " + tput setaf 3 2>/dev/null || true + printf "%s\n" "==================================================================" + tput sgr0 2>/dev/null || true + else + printf "%s\n" "==================================================================" + printf "%s\n" " ${title} " + printf "%s\n" "==================================================================" + fi printf "\n" } @@ -111,64 +119,59 @@ log() { local _msg="$1" local _color="${2:-5}" - if [[ -t 1 ]]; then - tput setaf $_color - echo "$_msg" - tput sgr0 + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + tput setaf "$_color" 2>/dev/null || true + printf "%s\n" "$_msg" + tput sgr0 2>/dev/null || true else - echo "$_msg" + printf "%s\n" "$_msg" fi } print_separator() { - if [[ -t 1 ]]; then - echo "" - tput setaf 4 # Blue color for sub-headers - echo "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼" - tput sgr0 - echo "" + printf "\n" + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + tput setaf 4 2>/dev/null || true + printf "%s\n" "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼" + tput sgr0 2>/dev/null || true else - echo "\n▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼\n" + printf "%s\n" "==================================================" fi + printf "\n" } ############################### -# Variable Validation +# Configuration Setup ############################### -validate_variables() { - print_header "Variable Validation" - - if [ -z "$ARM_SUBSCRIPTION_ID" ]; then - log "Error: ARM_SUBSCRIPTION_ID environment variable is not set" 1 - log "Usage: export ARM_SUBSCRIPTION_ID='00000000-0000-0000-0000-000000000000' && $0" 1 - exit 1 - fi +setup_configuration() { + # Set location from args or default + LOCATION=${2:-$DEFAULT_LOCATION} - # Always generate a new resource group name unless explicitly passed as argument + # Generate resource group name if not provided if [ -z "$1" ]; then RESOURCE_GROUP_NAME=$(generate_unique_name "$RESOURCE_GROUP_PREFIX" "") else RESOURCE_GROUP_NAME="$1" fi - # Export for Go tests - export RESOURCE_GROUP_NAME - # Use default location unless explicitly passed as second argument - if [ -z "$2" ]; then - LOCATION="$DEFAULT_LOCATION" - else - LOCATION="$2" - fi - # Export for Go tests - export LOCATION - - # Generate unique storage account name if not provided + # Generate storage account name if not provided if [ -z "$STORAGE_ACCOUNT_NAME" ]; then STORAGE_ACCOUNT_NAME=$(generate_unique_name "" "sa") fi - # Export for Go tests + + # Export variables for Go tests + export RESOURCE_GROUP_NAME + export LOCATION export STORAGE_ACCOUNT_NAME +} + +############################### +# Variable Validation +############################### + +validate_variables() { + print_header "Variable Validation" log "Using Subscription: $ARM_SUBSCRIPTION_ID" 6 log "Using Resource Group: $RESOURCE_GROUP_NAME" 6 @@ -183,7 +186,6 @@ validate_variables() { setup_azure() { print_header "Azure Setup" - # Only verify subscription access log "Verifying Azure subscription..." 5 az account show --subscription "$ARM_SUBSCRIPTION_ID" || { log "Error: Could not access subscription $ARM_SUBSCRIPTION_ID" 1 @@ -191,9 +193,12 @@ setup_azure() { } log "Azure subscription verified successfully" 2 - # Create resource group if it doesn't exist - log "Creating resource group if it doesn't exist..." 5 - az group create --name "$RESOURCE_GROUP_NAME" --location "$LOCATION" --subscription "$ARM_SUBSCRIPTION_ID" || { + log "Creating resource group..." 5 + az group create \ + --name "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --subscription "$ARM_SUBSCRIPTION_ID" \ + --output json || { log "Error: Could not create resource group $RESOURCE_GROUP_NAME" 1 exit 1 } @@ -203,13 +208,11 @@ setup_azure() { create_tfvars_files() { print_header "Terraform Configuration" + local tfvars_content="name = \"$STORAGE_ACCOUNT_NAME\"\nresource_group_name = \"$RESOURCE_GROUP_NAME\"" + log "Creating testing.tfvars files..." 5 - for dir in "${SCRIPT_DIR}" "${UNIT_TEST_DIR}" "${INTEGRATION_TEST_DIR}" "${TESTING_DIR}"; do - mkdir -p "$dir" - cat > "${dir}/testing.tfvars" << EOF -name = "$STORAGE_ACCOUNT_NAME" -resource_group_name = "$RESOURCE_GROUP_NAME" -EOF + for dir in "$SCRIPT_DIR" "$UNIT_TEST_DIR" "$INTEGRATION_TEST_DIR" "$TESTING_DIR"; do + [[ -d "$dir" ]] && echo -e "$tfvars_content" > "$dir/testing.tfvars" done log "Configuration files created successfully" 2 } @@ -218,37 +221,36 @@ EOF # Testing Functions ############################### -run_basic_unit_tests() { - print_header "Basic Unit Tests" - log "Running basic unit tests..." 5 +run_test() { + local test_dir="$1" + local test_name="$2" + + print_header "$test_name" + log "Running $test_name..." 5 echo "" - pushd "${TESTING_DIR}" > /dev/null - go test -v - popd > /dev/null + + pushd "$test_dir" > /dev/null || exit 1 + if ! go test -v; then + log "Error: $test_name failed" 1 + popd > /dev/null || exit 1 + exit 1 + fi + popd > /dev/null || exit 1 + echo "" - log "Basic unit tests completed" 2 + log "$test_name completed" 2 +} + +run_basic_unit_tests() { + run_test "$TESTING_DIR" "Basic Unit Tests" } run_extended_unit_tests() { - print_header "Extended Unit Tests" - log "Running extended unit tests..." 5 - echo "" - pushd "${SCRIPT_DIR}/tests" > /dev/null - go test -v ./unit/... - popd > /dev/null - echo "" - log "Extended unit tests completed" 2 + run_test "$SCRIPT_DIR/tests" "Extended Unit Tests" "./unit/..." } run_integration_tests() { - print_header "Integration Tests" - log "Running integration tests..." 5 - echo "" - pushd "${SCRIPT_DIR}/tests" > /dev/null - go test -v ./integration/... - popd > /dev/null - echo "" - log "Integration tests completed" 2 + run_test "$SCRIPT_DIR/tests" "Integration Tests" "./integration/..." } ############################### @@ -259,7 +261,7 @@ terraform_init_and_apply() { print_header "Terraform Deployment" log "Initializing Terraform..." 5 - pushd "${TESTING_DIR}" > /dev/null + pushd "$TESTING_DIR" > /dev/null print_separator terraform init @@ -285,15 +287,15 @@ terraform_init_and_apply() { cleanup_tfvars() { log "Cleaning up testing.tfvars files..." 6 - rm -f "${SCRIPT_DIR}/testing.tfvars" - rm -f "${UNIT_TEST_DIR}/testing.tfvars" - rm -f "${INTEGRATION_TEST_DIR}/testing.tfvars" - rm -f "${TESTING_DIR}/testing.tfvars" + rm -f "$SCRIPT_DIR/testing.tfvars" + rm -f "$UNIT_TEST_DIR/testing.tfvars" + rm -f "$INTEGRATION_TEST_DIR/testing.tfvars" + rm -f "$TESTING_DIR/testing.tfvars" } cleanup_terraform() { - if [ -d "${TESTING_DIR}/.terraform" ]; then - pushd "${TESTING_DIR}" > /dev/null + if [ -d "$TESTING_DIR/.terraform" ]; then + pushd "$TESTING_DIR" > /dev/null log "Destroying Terraform resources..." 3 terraform destroy --var-file=testing.tfvars -auto-approve || true @@ -335,8 +337,11 @@ main() { # Trap cleanup on exit trap cleanup EXIT + # Setup configuration + setup_configuration "$@" + # Validate variables - validate_variables "$1" "$2" + validate_variables # Setup setup_azure -- GitLab From a3102081c45fb39e7b20be2fb01cbf34c8895ae6 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 14:43:34 -0600 Subject: [PATCH 04/15] Implemented common pattern and test script inheritance. --- .../providers/azure/resource-group/test.sh | 213 ++------ .../azure/resource-group/testing/unit_test.go | 38 +- .../providers/azure/storage-account/test.sh | 326 ++---------- .../modules/providers/azure/test-functions.sh | 493 ++++++++++++++++++ 4 files changed, 594 insertions(+), 476 deletions(-) create mode 100644 infra/modules/providers/azure/test-functions.sh diff --git a/infra/modules/providers/azure/resource-group/test.sh b/infra/modules/providers/azure/resource-group/test.sh index f5fd6a3cf..c09a13c2b 100755 --- a/infra/modules/providers/azure/resource-group/test.sh +++ b/infra/modules/providers/azure/resource-group/test.sh @@ -4,215 +4,86 @@ set -e ############################### -# Script Path Configuration +# Source Common Functions ############################### -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -TESTING_DIR="${SCRIPT_DIR}/testing" +COMMON_LIB="../test-functions.sh" +if [ ! -f "$COMMON_LIB" ]; then + echo "Error: Common library not found at $COMMON_LIB" + exit 1 +fi +source "$COMMON_LIB" -############################### -# Required Environment Variables -############################### -# ARM_SUBSCRIPTION_ID should be set in the environment -# Example: export ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000" - -############################### -# Optional Variables -############################### -# These can be overridden by setting them before running the script -RESOURCE_GROUP_NAME=${RESOURCE_GROUP_NAME:-"osdu-test"} -LOCATION=${LOCATION:-"eastus2"} ############################### -# Utility Functions +# Script Configuration ############################### +SCRIPT_DIR=$(get_script_dir) +setup_test_directories "$SCRIPT_DIR" -print_header() { - local title="$1" - printf "\n" - tput setaf 3 - echo "==================================================================" - tput setaf 6 - echo " ${title} " - tput setaf 3 - echo "==================================================================" - tput sgr0 - printf "\n" -} - -log() { - local _msg="$1" - local _color="${2:-5}" - - if [[ -t 1 ]]; then - tput setaf $_color - echo "$_msg" - tput sgr0 - else - echo "$_msg" - fi -} - -print_separator() { - if [[ -t 1 ]]; then - echo "" - tput setaf 4 # Blue color for sub-headers - echo "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼" - tput sgr0 - echo "" - else - echo "\n▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼\n" - fi -} ############################### -# Variable Validation +# Required Environment Variables ############################### +validate_azure_credentials -validate_variables() { - print_header "Variable Validation" - - if [ -z "$ARM_SUBSCRIPTION_ID" ]; then - log "Error: ARM_SUBSCRIPTION_ID environment variable is not set" 1 - log "Usage: export ARM_SUBSCRIPTION_ID='00000000-0000-0000-0000-000000000000' && $0" 1 - exit 1 - fi - log "Using Subscription: $ARM_SUBSCRIPTION_ID" 6 - log "Using Resource Group: $RESOURCE_GROUP_NAME" 6 - log "Using Location: $LOCATION" 6 -} ############################### -# Setup Functions +# Optional Variables ############################### +# These can be overridden by setting them before running the script +RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"terraform-test"} +DEFAULT_LOCATION="eastus2" -setup_azure() { - print_header "Azure Setup" - - # Only verify subscription access - log "Verifying Azure subscription..." 5 - az account show --subscription "$ARM_SUBSCRIPTION_ID" || { - log "Error: Could not access subscription $ARM_SUBSCRIPTION_ID" 1 - exit 1 - } - log "Azure subscription verified successfully" 2 -} - -create_tfvars_files() { - print_header "Terraform Configuration" - - log "Creating testing.tfvars files..." 5 - mkdir -p "${TESTING_DIR}" - cat > "${TESTING_DIR}/testing.tfvars" << EOF -name = "$RESOURCE_GROUP_NAME" -location = "$LOCATION" -EOF - log "Configuration files created successfully" 2 -} ############################### -# Testing Functions +# Help Documentation ############################### - -run_unit_tests() { - print_header "Unit Tests" - log "Running unit tests..." 5 - echo "" - pushd "${TESTING_DIR}" > /dev/null - go test -v - popd > /dev/null - echo "" - log "Unit tests completed" 2 +print_help() { + print_common_help "Resource Group" } -############################### -# Terraform Functions -############################### - -terraform_init_and_apply() { - print_header "Terraform Deployment" - - log "Initializing Terraform..." 5 - pushd "${TESTING_DIR}" > /dev/null - - print_separator - terraform init - print_separator - - log "Planning Terraform changes..." 5 - print_separator - terraform plan --var-file=testing.tfvars - print_separator - - log "Applying Terraform changes..." 5 - print_separator - terraform apply --var-file=testing.tfvars -auto-approve - print_separator - - log "Terraform deployment completed successfully" 2 - popd > /dev/null -} ############################### -# Cleanup Functions +# Required Module Functions ############################### - -cleanup_tfvars() { - log "Cleaning up testing.tfvars files..." 6 - rm -f "${TESTING_DIR}/testing.tfvars" +setup_configuration() { + # Setup base configuration + setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" } -cleanup_terraform() { - if [ -d "${TESTING_DIR}/.terraform" ]; then - pushd "${TESTING_DIR}" > /dev/null - - log "Destroying Terraform resources..." 3 - terraform destroy --var-file=testing.tfvars -auto-approve || true - - log "Cleaning up Terraform files..." 6 - rm -rf .terraform/ - rm -f .terraform.lock.hcl - rm -f terraform.tfstate - rm -f terraform.tfstate.backup - rm -rf terraform.tfstate.d/ - - popd > /dev/null - fi -} - -cleanup() { - print_header "Cleanup" - log "Starting cleanup..." 5 - - cleanup_terraform - cleanup_tfvars - - log "Cleanup completed successfully" 2 +create_tfvars_files() { + local tfvars_content="name = \"$RESOURCE_GROUP_NAME\"\nlocation = \"$LOCATION\"" + create_base_tfvars_files "$tfvars_content" } ############################### # Main Execution ############################### - main() { - print_header "Test Execution" - log "Starting test setup..." 5 - # Trap cleanup on exit - trap cleanup EXIT + trap 'cleanup; cleanup_resource_group "$RESOURCE_GROUP_NAME" "$ARM_SUBSCRIPTION_ID"' EXIT + + # Setup configuration + setup_configuration "$@" # Validate variables - validate_variables + validate_base_variables # Setup setup_azure create_tfvars_files - # Run unit tests first (these don't require infrastructure) - run_unit_tests - - # Now deploy and test infrastructure - terraform_init_and_apply + # Run all tests + run_standard_test_sequence } +# Check for help flag +case "$1" in + -h|--help) + print_help + exit 0 + ;; +esac + # Execute main function -main \ No newline at end of file +main "$@" \ No newline at end of file diff --git a/infra/modules/providers/azure/resource-group/testing/unit_test.go b/infra/modules/providers/azure/resource-group/testing/unit_test.go index 5d3603365..a749b0048 100644 --- a/infra/modules/providers/azure/resource-group/testing/unit_test.go +++ b/infra/modules/providers/azure/resource-group/testing/unit_test.go @@ -16,6 +16,7 @@ package test import ( "encoding/json" + "os" "testing" "github.com/gruntwork-io/terratest/modules/random" @@ -24,18 +25,8 @@ import ( ) var name = "resourcegroup-" -var location = "eastus2" var count = 2 -var tfOptions = &terraform.Options{ - TerraformDir: "./", - Upgrade: true, - Vars: map[string]interface{}{ - "name": name, - "location": location, - }, -} - func asMap(t *testing.T, jsonString string) map[string]interface{} { var theMap map[string]interface{} if err := json.Unmarshal([]byte(jsonString), &theMap); err != nil { @@ -45,6 +36,31 @@ func asMap(t *testing.T, jsonString string) map[string]interface{} { } func TestTemplate(t *testing.T) { + t.Parallel() + + // Get location from environment variable or use default + location := os.Getenv("LOCATION") + if location == "" { + location = "eastus2" + } + + // Generate unique names for resources + uniqueID := random.UniqueId() + workspace := name + uniqueID + resourceGroupName := os.Getenv("RESOURCE_GROUP_NAME") + if resourceGroupName == "" { + resourceGroupName = "terraform-test-" + uniqueID + } + + tfOptions := &terraform.Options{ + TerraformDir: "./", + Upgrade: true, + Vars: map[string]interface{}{ + "name": resourceGroupName, + "location": location, + }, + } + expectedResult := asMap(t, `{ "location": "`+location+`" }`) @@ -52,7 +68,7 @@ func TestTemplate(t *testing.T) { testFixture := infratests.UnitTestFixture{ GoTest: t, TfOptions: tfOptions, - Workspace: name + random.UniqueId(), + Workspace: workspace, PlanAssertions: nil, ExpectedResourceCount: count, ExpectedResourceAttributeValues: infratests.ResourceDescription{ diff --git a/infra/modules/providers/azure/storage-account/test.sh b/infra/modules/providers/azure/storage-account/test.sh index a8601ca80..8544f4864 100755 --- a/infra/modules/providers/azure/storage-account/test.sh +++ b/infra/modules/providers/azure/storage-account/test.sh @@ -4,17 +4,28 @@ set -e ############################### -# Script Path Configuration +# Source Common Functions ############################### -SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) -TESTING_DIR="${SCRIPT_DIR}/testing" -UNIT_TEST_DIR="${SCRIPT_DIR}/tests/unit" -INTEGRATION_TEST_DIR="${SCRIPT_DIR}/tests/integration" +COMMON_LIB="../test-functions.sh" +if [ ! -f "$COMMON_LIB" ]; then + echo "Error: Common library not found at $COMMON_LIB" + exit 1 +fi +source "$COMMON_LIB" + + +############################### +# Script Configuration +############################### +SCRIPT_DIR=$(get_script_dir) +setup_test_directories "$SCRIPT_DIR" + ############################### # Required Environment Variables ############################### -: "${ARM_SUBSCRIPTION_ID:?'ARM_SUBSCRIPTION_ID environment variable is required'}" +validate_azure_credentials + ############################### # Optional Variables @@ -24,318 +35,52 @@ RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"terraform-test"} DEFAULT_LOCATION="eastus2" STORAGE_ACCOUNT_NAME=${STORAGE_ACCOUNT_NAME:-""} # Will be auto-generated if not provided + ############################### # Help Documentation ############################### - print_help() { - cat << EOF -Storage Account Module Test Script -================================ - -This script runs unit tests and integration tests for the Azure Storage Account module. - -Required Environment Variables: ------------------------------ -ARM_SUBSCRIPTION_ID Azure subscription ID to use for testing - -Usage: ------- -$0 [resource_group_name] [location] -$0 -h | --help - -Arguments: ----------- -resource_group_name (Optional) Name of resource group to create. - Default: Auto-generated using prefix "terraform-test" - -location (Optional) Azure region to deploy resources. - Default: eastus2 - -Options: --------- --h, --help Show this help message - -Examples: ---------- -# Run with auto-generated names in default location -$0 - -# Run with specific resource group name -$0 my-test-rg - -# Run with specific resource group and location -$0 my-test-rg westus2 - -Notes: ------- -- If not specified, a unique resource group name will be generated -- If not specified, a unique storage account name will be generated -- All resources are automatically cleaned up after the test -- The script uses the default Azure CLI subscription if not specified -EOF -} - -############################### -# Utility Functions -############################### - -generate_unique_name() { - local prefix="${1:-}" - local suffix="${2:-}" - local unique - - # More portable way to generate random string - if command -v openssl >/dev/null 2>&1; then - unique=$(openssl rand -hex 4 | tr '[:upper:]' '[:lower:]') - else - # Fallback to built-in $RANDOM if openssl is not available - unique=$(printf "%08x" $((RANDOM + RANDOM + RANDOM + RANDOM))) - fi - - [[ -n "$prefix" ]] && echo "${prefix}-${unique}${suffix}" || echo "tftest${unique}${suffix}" -} - -print_header() { - local title="$1" - printf "\n" - if [ -t 1 ] && command -v tput >/dev/null 2>&1; then - tput setaf 3 2>/dev/null || true - printf "%s\n" "==================================================================" - tput setaf 6 2>/dev/null || true - printf "%s\n" " ${title} " - tput setaf 3 2>/dev/null || true - printf "%s\n" "==================================================================" - tput sgr0 2>/dev/null || true - else - printf "%s\n" "==================================================================" - printf "%s\n" " ${title} " - printf "%s\n" "==================================================================" - fi - printf "\n" -} - -log() { - local _msg="$1" - local _color="${2:-5}" - - if [ -t 1 ] && command -v tput >/dev/null 2>&1; then - tput setaf "$_color" 2>/dev/null || true - printf "%s\n" "$_msg" - tput sgr0 2>/dev/null || true - else - printf "%s\n" "$_msg" - fi + print_common_help "Storage Account" "\n- If not specified, a unique storage account name will be generated" } -print_separator() { - printf "\n" - if [ -t 1 ] && command -v tput >/dev/null 2>&1; then - tput setaf 4 2>/dev/null || true - printf "%s\n" "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼" - tput sgr0 2>/dev/null || true - else - printf "%s\n" "==================================================" - fi - printf "\n" -} ############################### -# Configuration Setup +# Required Module Functions ############################### - setup_configuration() { - # Set location from args or default - LOCATION=${2:-$DEFAULT_LOCATION} - - # Generate resource group name if not provided - if [ -z "$1" ]; then - RESOURCE_GROUP_NAME=$(generate_unique_name "$RESOURCE_GROUP_PREFIX" "") - else - RESOURCE_GROUP_NAME="$1" - fi + # Setup base configuration first + setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" # Generate storage account name if not provided if [ -z "$STORAGE_ACCOUNT_NAME" ]; then STORAGE_ACCOUNT_NAME=$(generate_unique_name "" "sa") fi - # Export variables for Go tests - export RESOURCE_GROUP_NAME - export LOCATION + # Export additional variables for Go tests export STORAGE_ACCOUNT_NAME } -############################### -# Variable Validation -############################### - -validate_variables() { - print_header "Variable Validation" - - log "Using Subscription: $ARM_SUBSCRIPTION_ID" 6 - log "Using Resource Group: $RESOURCE_GROUP_NAME" 6 - log "Using Location: $LOCATION" 6 - log "Using Storage Account: $STORAGE_ACCOUNT_NAME" 6 -} - -############################### -# Setup Functions -############################### - -setup_azure() { - print_header "Azure Setup" - - log "Verifying Azure subscription..." 5 - az account show --subscription "$ARM_SUBSCRIPTION_ID" || { - log "Error: Could not access subscription $ARM_SUBSCRIPTION_ID" 1 - exit 1 - } - log "Azure subscription verified successfully" 2 - - log "Creating resource group..." 5 - az group create \ - --name "$RESOURCE_GROUP_NAME" \ - --location "$LOCATION" \ - --subscription "$ARM_SUBSCRIPTION_ID" \ - --output json || { - log "Error: Could not create resource group $RESOURCE_GROUP_NAME" 1 - exit 1 - } - log "Resource group ready" 2 -} - create_tfvars_files() { - print_header "Terraform Configuration" - local tfvars_content="name = \"$STORAGE_ACCOUNT_NAME\"\nresource_group_name = \"$RESOURCE_GROUP_NAME\"" - - log "Creating testing.tfvars files..." 5 - for dir in "$SCRIPT_DIR" "$UNIT_TEST_DIR" "$INTEGRATION_TEST_DIR" "$TESTING_DIR"; do - [[ -d "$dir" ]] && echo -e "$tfvars_content" > "$dir/testing.tfvars" - done - log "Configuration files created successfully" 2 -} - -############################### -# Testing Functions -############################### - -run_test() { - local test_dir="$1" - local test_name="$2" - - print_header "$test_name" - log "Running $test_name..." 5 - echo "" - - pushd "$test_dir" > /dev/null || exit 1 - if ! go test -v; then - log "Error: $test_name failed" 1 - popd > /dev/null || exit 1 - exit 1 - fi - popd > /dev/null || exit 1 - - echo "" - log "$test_name completed" 2 -} - -run_basic_unit_tests() { - run_test "$TESTING_DIR" "Basic Unit Tests" + create_base_tfvars_files "$tfvars_content" } -run_extended_unit_tests() { - run_test "$SCRIPT_DIR/tests" "Extended Unit Tests" "./unit/..." -} - -run_integration_tests() { - run_test "$SCRIPT_DIR/tests" "Integration Tests" "./integration/..." -} - -############################### -# Terraform Functions -############################### - -terraform_init_and_apply() { - print_header "Terraform Deployment" - - log "Initializing Terraform..." 5 - pushd "$TESTING_DIR" > /dev/null - - print_separator - terraform init - print_separator - - log "Planning Terraform changes..." 5 - print_separator - terraform plan --var-file=testing.tfvars - print_separator - - log "Applying Terraform changes..." 5 - print_separator - terraform apply --var-file=testing.tfvars -auto-approve - print_separator - - log "Terraform deployment completed successfully" 2 - popd > /dev/null -} ############################### -# Cleanup Functions +# Optional Module Functions ############################### - -cleanup_tfvars() { - log "Cleaning up testing.tfvars files..." 6 - rm -f "$SCRIPT_DIR/testing.tfvars" - rm -f "$UNIT_TEST_DIR/testing.tfvars" - rm -f "$INTEGRATION_TEST_DIR/testing.tfvars" - rm -f "$TESTING_DIR/testing.tfvars" -} - -cleanup_terraform() { - if [ -d "$TESTING_DIR/.terraform" ]; then - pushd "$TESTING_DIR" > /dev/null - - log "Destroying Terraform resources..." 3 - terraform destroy --var-file=testing.tfvars -auto-approve || true - - log "Cleaning up Terraform files..." 6 - rm -rf .terraform/ - rm -f .terraform.lock.hcl - rm -f terraform.tfstate - rm -f terraform.tfstate.backup - rm -rf terraform.tfstate.d/ - - popd > /dev/null - fi - - log "Deleting resource group..." 3 - az group delete --name "$RESOURCE_GROUP_NAME" --subscription "$ARM_SUBSCRIPTION_ID" --yes || { - log "Warning: Could not delete resource group $RESOURCE_GROUP_NAME" 3 - } +validate_variables() { + validate_base_variables + log "Using Storage Account: $STORAGE_ACCOUNT_NAME" 6 } -cleanup() { - print_header "Cleanup" - log "Starting cleanup..." 5 - - cleanup_terraform - cleanup_tfvars - - log "Cleanup completed successfully" 2 -} ############################### # Main Execution ############################### - main() { - print_header "Test Execution" - log "Starting test setup..." 5 - # Trap cleanup on exit - trap cleanup EXIT + trap 'cleanup; cleanup_resource_group "$RESOURCE_GROUP_NAME" "$ARM_SUBSCRIPTION_ID"' EXIT # Setup configuration setup_configuration "$@" @@ -344,18 +89,11 @@ main() { validate_variables # Setup - setup_azure + setup_azure_with_rg create_tfvars_files - # Run unit tests first - run_basic_unit_tests # Simple tests in testing/ - run_extended_unit_tests # More complex tests in tests/unit/ - - # Now deploy infrastructure - terraform_init_and_apply # Deploy infrastructure - - # Run integration tests against deployed infrastructure - run_integration_tests # Integration tests in tests/integration/ + # Run all tests + run_standard_test_sequence } # Check for help flag diff --git a/infra/modules/providers/azure/test-functions.sh b/infra/modules/providers/azure/test-functions.sh new file mode 100644 index 000000000..9ad75e58e --- /dev/null +++ b/infra/modules/providers/azure/test-functions.sh @@ -0,0 +1,493 @@ +#!/bin/bash + +############################### +# Common Test Functions Library +############################### +# This library provides a base testing framework for Azure Terraform modules. +# It follows an object-oriented pattern where this file acts as a base "class" +# and each module's test.sh implements module-specific functionality. +# +# Usage: +# 1. Source this file in your module's test.sh +# 2. Implement the required functions (see Required Module Functions section) +# 3. Use the common functions for standard operations +# +# Example module test.sh: +# ```bash +# #!/bin/bash +# source "../test-functions.sh" +# +# setup_configuration() { +# setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" +# # Add module-specific configuration +# } +# +# create_tfvars_files() { +# local tfvars_content="..." +# create_base_tfvars_files "$tfvars_content" +# } +# +# main() { +# trap 'cleanup; cleanup_resource_group "$RESOURCE_GROUP_NAME" "$ARM_SUBSCRIPTION_ID"' EXIT +# setup_configuration "$@" +# validate_variables +# setup_azure +# create_tfvars_files +# run_standard_test_sequence +# } +# ``` + +# Exit on error if sourced directly +[[ "${BASH_SOURCE[0]}" == "${0}" ]] && set -e + +############################### +# Directory Configuration +############################### +get_script_dir() { + echo "$( cd -- "$( dirname -- "${BASH_SOURCE[1]}" )" &> /dev/null && pwd )" +} + +setup_test_directories() { + local SCRIPT_DIR="$1" + TESTING_DIR="${SCRIPT_DIR}/testing" + UNIT_TEST_DIR="${SCRIPT_DIR}/tests/unit" + INTEGRATION_TEST_DIR="${SCRIPT_DIR}/tests/integration" + + # Export for use in tests + export TESTING_DIR UNIT_TEST_DIR INTEGRATION_TEST_DIR +} + + +############################### +# Required Environment Variables +############################### +validate_azure_credentials() { + : "${ARM_SUBSCRIPTION_ID:?'ARM_SUBSCRIPTION_ID environment variable is required'}" +} + + +############################### +# Utility Functions +############################### +generate_unique_name() { + local prefix="${1:-}" + local suffix="${2:-}" + local unique + + # More portable way to generate random string + if command -v openssl >/dev/null 2>&1; then + unique=$(openssl rand -hex 4 | tr '[:upper:]' '[:lower:]') + else + # Fallback to built-in $RANDOM if openssl is not available + unique=$(printf "%08x" $((RANDOM + RANDOM + RANDOM + RANDOM))) + fi + + [[ -n "$prefix" ]] && echo "${prefix}-${unique}${suffix}" || echo "tftest${unique}${suffix}" +} + +print_header() { + local title="$1" + printf "\n" + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + tput setaf 3 2>/dev/null || true + printf "%s\n" "==================================================================" + tput setaf 6 2>/dev/null || true + printf "%s\n" " ${title} " + tput setaf 3 2>/dev/null || true + printf "%s\n" "==================================================================" + tput sgr0 2>/dev/null || true + else + printf "%s\n" "==================================================================" + printf "%s\n" " ${title} " + printf "%s\n" "==================================================================" + fi + printf "\n" +} + +log() { + local _msg="$1" + local _color="${2:-5}" + + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + tput setaf "$_color" 2>/dev/null || true + printf "%s\n" "$_msg" + tput sgr0 2>/dev/null || true + else + printf "%s\n" "$_msg" + fi +} + +print_separator() { + printf "\n" + if [ -t 1 ] && command -v tput >/dev/null 2>&1; then + tput setaf 4 2>/dev/null || true + printf "%s\n" "▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼▼" + tput sgr0 2>/dev/null || true + else + printf "%s\n" "==================================================" + fi + printf "\n" +} + +print_common_help() { + local module_name="$1" + local additional_notes="${2:-}" + local script_name=$(basename "${BASH_SOURCE[2]}") + + cat << EOF +${module_name} Module Test Script +================================ + +This script runs unit tests and integration tests for the Azure ${module_name} module. + +Required Environment Variables: +----------------------------- +ARM_SUBSCRIPTION_ID Azure subscription ID to use for testing + +Usage: +------ +${script_name} [resource_group_name] [location] +${script_name} -h | --help + +Arguments: +---------- +resource_group_name (Optional) Name of resource group to create. + Default: Auto-generated using prefix "terraform-test" + +location (Optional) Azure region to deploy resources. + Default: eastus2 + +Options: +-------- +-h, --help Show this help message + +Examples: +--------- +# Run with auto-generated names in default location +${script_name} + +# Run with specific resource group name +${script_name} my-test-rg + +# Run with specific resource group and location +${script_name} my-test-rg westus2 + +Notes: +------ +- If not specified, a unique resource group name will be generated${additional_notes} +- All resources are automatically cleaned up after the test +- The script uses the default Azure CLI subscription if not specified +EOF +} + + +############################### +# Azure Functions +############################### +setup_azure() { + print_header "Azure Setup" + + log "Verifying Azure subscription..." 5 + az account show --subscription "$ARM_SUBSCRIPTION_ID" || { + log "Error: Could not access subscription $ARM_SUBSCRIPTION_ID" 1 + exit 1 + } + log "Azure subscription verified successfully" 2 +} + + +############################### +# Common Configuration Functions +############################### +setup_base_configuration() { + local prefix="$1" + local default_location="$2" + local args=("${@:3}") # Get remaining args + + # Set location from args or default + LOCATION=${args[1]:-$default_location} + + # Generate resource group name if not provided + if [ -z "${args[0]}" ]; then + RESOURCE_GROUP_NAME=$(generate_unique_name "$prefix" "") + else + RESOURCE_GROUP_NAME="${args[0]}" + fi + + # Export common variables for Go tests + export RESOURCE_GROUP_NAME + export LOCATION +} + +validate_base_variables() { + print_header "Variable Validation" + + log "Using Subscription: $ARM_SUBSCRIPTION_ID" 6 + log "Using Resource Group: $RESOURCE_GROUP_NAME" 6 + log "Using Location: $LOCATION" 6 +} + +setup_azure_with_rg() { + print_header "Azure Setup" + + log "Verifying Azure subscription..." 5 + az account show --subscription "$ARM_SUBSCRIPTION_ID" || { + log "Error: Could not access subscription $ARM_SUBSCRIPTION_ID" 1 + exit 1 + } + log "Azure subscription verified successfully" 2 + + log "Creating resource group..." 5 + az group create \ + --name "$RESOURCE_GROUP_NAME" \ + --location "$LOCATION" \ + --subscription "$ARM_SUBSCRIPTION_ID" \ + --output json || { + log "Error: Could not create resource group $RESOURCE_GROUP_NAME" 1 + exit 1 + } + log "Resource group ready" 2 +} + +create_base_tfvars_files() { + local tfvars_content="$1" + + print_header "Terraform Configuration" + + log "Creating testing.tfvars files..." 5 + for dir in "$SCRIPT_DIR" "$UNIT_TEST_DIR" "$INTEGRATION_TEST_DIR" "$TESTING_DIR"; do + [[ -d "$dir" ]] && echo -e "$tfvars_content" > "$dir/testing.tfvars" + done + log "Configuration files created successfully" 2 +} + + +############################### +# Required Module Functions +############################### +# Each module must implement the following functions: +# +# setup_configuration() +# - Purpose: Set up module-specific configuration +# - Requirements: +# - Call setup_base_configuration +# - Set any additional module-specific variables +# - Export any variables needed for tests +# +# create_tfvars_files() +# - Purpose: Create module-specific tfvars content +# - Requirements: +# - Create appropriate tfvars content +# - Call create_base_tfvars_files with the content +# +# Optional overrides: +# - validate_variables() +# - Add module-specific variable validation +# - Call validate_base_variables first +# - Any additional module-specific functions + +############################### +# Module Implementation Validation +############################### +validate_module_implementation() { + local required_functions=("setup_configuration" "create_tfvars_files") + local missing_functions=() + + for func in "${required_functions[@]}"; do + if ! declare -f "$func" > /dev/null; then + missing_functions+=("$func") + fi + done + + if [ ${#missing_functions[@]} -ne 0 ]; then + log "Error: Module must implement the following functions:" 1 + printf '%s\n' "${missing_functions[@]}" >&2 + exit 1 + fi +} + + +############################### +# Standard Test Lifecycle +############################### +# The standard test sequence is: +# 1. Setup +# - Configure variables +# - Validate environment +# - Create resource group +# 2. Test +# - Run basic unit tests +# - Run extended unit tests +# - Deploy infrastructure +# - Run integration tests +# 3. Cleanup +# - Destroy infrastructure +# - Remove resource group +# - Clean up files +run_standard_test_sequence() { + # Validate module implementation + validate_module_implementation + + print_header "Test Execution" + log "Starting test setup..." 5 + + # Run unit tests first + run_basic_unit_tests # Simple tests in testing/ + run_extended_unit_tests # More complex tests in tests/unit/ + + # Now deploy infrastructure + terraform_init_and_apply # Deploy infrastructure + + # Run integration tests against deployed infrastructure + run_integration_tests # Integration tests in tests/integration/ +} + + +############################### +# Testing Functions +############################### +run_test() { + local test_dir="$1" + local test_name="$2" + local test_path="${3:-}" # Optional path for extended/integration tests + + # Skip if directory doesn't exist + if [ ! -d "$test_dir" ]; then + log "Skipping $test_name - directory not found: $test_dir" 3 + return 0 + fi + + print_header "$test_name" + log "Running $test_name..." 5 + echo "" + + pushd "$test_dir" > /dev/null || exit 1 + if [ -n "$test_path" ]; then + # Use proper Go test path pattern + if ! ls ./"$test_path"/*_test.go >/dev/null 2>&1; then + log "No test files found in $test_path" 3 + popd > /dev/null || exit 1 + return 0 + fi + if ! go test -v "./$test_path/..."; then + log "Error: $test_name failed" 1 + popd > /dev/null || exit 1 + exit 1 + fi + else + if ! go test -v ./...; then + log "Error: $test_name failed" 1 + popd > /dev/null || exit 1 + exit 1 + fi + fi + popd > /dev/null || exit 1 + + echo "" + log "$test_name completed" 2 +} + +run_basic_unit_tests() { + run_test "$TESTING_DIR" "Basic Unit Tests" +} + +run_extended_unit_tests() { + # Only run if the extended unit tests directory exists + if [ -d "$UNIT_TEST_DIR" ]; then + run_test "$SCRIPT_DIR/tests" "Extended Unit Tests" "unit" + else + log "Skipping Extended Unit Tests - no extended tests found" 3 + fi +} + +run_integration_tests() { + # Only run if the integration test directory exists + if [ -d "$INTEGRATION_TEST_DIR" ]; then + run_test "$SCRIPT_DIR/tests" "Integration Tests" "integration" + else + log "Skipping Integration Tests - no integration tests found" 3 + fi +} + + +############################### +# Terraform Functions +############################### +terraform_init_and_apply() { + print_header "Terraform Deployment" + + log "Initializing Terraform..." 5 + pushd "$TESTING_DIR" > /dev/null + + print_separator + terraform init + print_separator + + log "Planning Terraform changes..." 5 + print_separator + terraform plan --var-file=testing.tfvars + print_separator + + log "Applying Terraform changes..." 5 + print_separator + terraform apply --var-file=testing.tfvars -auto-approve + print_separator + + log "Terraform deployment completed successfully" 2 + popd > /dev/null +} + + +############################### +# Cleanup Functions +############################### +cleanup_tfvars() { + log "Cleaning up testing.tfvars files..." 6 + rm -f "$SCRIPT_DIR/testing.tfvars" + rm -f "$UNIT_TEST_DIR/testing.tfvars" + rm -f "$INTEGRATION_TEST_DIR/testing.tfvars" + rm -f "$TESTING_DIR/testing.tfvars" +} + +cleanup_terraform() { + if [ -d "$TESTING_DIR/.terraform" ]; then + pushd "$TESTING_DIR" > /dev/null + + log "Destroying Terraform resources..." 3 + terraform destroy --var-file=testing.tfvars -auto-approve || true + + log "Cleaning up Terraform files..." 6 + rm -rf .terraform/ + rm -f .terraform.lock.hcl + rm -f terraform.tfstate + rm -f terraform.tfstate.backup + rm -rf terraform.tfstate.d/ + + popd > /dev/null + fi +} + +cleanup_resource_group() { + local resource_group_name="$1" + local subscription_id="$2" + + # Check if resource group exists before trying to delete + if az group exists --name "$resource_group_name" --subscription "$subscription_id" >/dev/null 2>&1; then + log "Deleting resource group $resource_group_name..." 3 + az group delete --name "$resource_group_name" --subscription "$subscription_id" --yes || { + log "Warning: Could not delete resource group $resource_group_name" 3 + } + else + log "Resource group $resource_group_name already deleted" 6 + fi +} + +cleanup() { + print_header "Cleanup" + log "Starting cleanup..." 5 + + cleanup_terraform + cleanup_tfvars + + log "Cleanup completed successfully" 2 +} \ No newline at end of file -- GitLab From 2dbea4677f13b7f0e745bb0b619d522f444711e7 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 16:00:20 -0600 Subject: [PATCH 05/15] Implemented log-analytics tests. --- .../.cursor/rules/terraform-module.mdc | 249 ++++++++++++++++++ infra/modules/magefile.go | 71 +++++ .../providers/azure/aks/testing/unit_test.go | 20 +- .../providers/azure/log-analytics/test.sh | 108 ++++++++ .../storage-account/testing/unit_test.go | 2 +- .../azure/storage-account/tests/tf_options.go | 4 +- .../providers/azure/terraform-module.mdc | 1 + .../modules/providers/azure/test-functions.sh | 5 +- 8 files changed, 446 insertions(+), 14 deletions(-) create mode 100644 infra/modules/.cursor/rules/terraform-module.mdc create mode 100755 infra/modules/providers/azure/log-analytics/test.sh create mode 100644 infra/modules/providers/azure/terraform-module.mdc diff --git a/infra/modules/.cursor/rules/terraform-module.mdc b/infra/modules/.cursor/rules/terraform-module.mdc new file mode 100644 index 000000000..de8ea8499 --- /dev/null +++ b/infra/modules/.cursor/rules/terraform-module.mdc @@ -0,0 +1,249 @@ +--- +description: This rule defines how osdu rules are created. +globs: +--- +# Terraform Module Development Guidelines + +This document outlines the standards and patterns for developing Terraform modules in our infrastructure codebase. + +## Module Structure + +``` +module-name/ +├── README.md # Module documentation +├── main.tf # Main module configuration +├── variables.tf # Input variable definitions +├── outputs.tf # Output definitions +├── test.sh # Test execution script +├── testing/ # Basic test configuration +│ ├── main.tf # Test implementation +│ └── unit_test.go # Basic unit tests +└── tests/ # Extended test suite + ├── .env.testing.template # Environment variables template + ├── tf_options.go # Common test configuration + ├── unit/ # Extended unit tests + │ └── *_test.go + └── integration/ # Integration tests + └── *_test.go +``` + +## Code Organization + +### 1. Main Configuration (main.tf) +- Start with required provider configuration +- Include copyright header +- Group resources logically +- Use data sources for existing resources +- Implement resource configurations +- Use dynamic blocks for optional features + +```hcl +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.90.0" # Pin to specific version + } + } +} + +provider "azurerm" { + features {} +} + +data "azurerm_resource_group" "main" { + name = var.resource_group_name +} + +resource "azurerm_example" "main" { + name = var.name + resource_group_name = data.azurerm_resource_group.main.name + location = data.azurerm_resource_group.main.location + + dynamic "optional_block" { + for_each = var.optional_feature_enabled ? [1] : [] + content { + // Configuration + } + } +} +``` + +### 2. Variables (variables.tf) +- Group variables by purpose +- Include clear descriptions +- Provide type constraints +- Set defaults where appropriate +- Mark sensitive variables + +```hcl +variable "name" { + description = "The name of the resource" + type = string +} + +variable "resource_tags" { + description = "Map of tags to apply to resources" + type = map(string) + default = {} +} +``` + +### 3. Outputs (outputs.tf) +- Include essential resource information +- Mark sensitive outputs +- Group related outputs +- Use maps for collections +- Include resource IDs and names + +```hcl +output "id" { + description = "The ID of the created resource" + value = azurerm_example.main.id +} + +output "properties" { + description = "Properties of the deployed resource" + value = { + id = azurerm_example.main.id + name = azurerm_example.main.name + } + sensitive = true +} +``` + +## Testing Framework + +### 1. Test Script (test.sh) +- Source common test functions +- Implement required module functions +- Setup configuration +- Create tfvars files +- Execute test sequence + +```bash +#!/bin/bash +source "../test-functions.sh" + +setup_configuration() { + setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" + # Add module-specific configuration +} + +create_tfvars_files() { + local tfvars_content="..." + create_base_tfvars_files "$tfvars_content" +} + +main() { + trap 'cleanup' EXIT + setup_configuration "$@" + validate_variables + setup_azure + create_tfvars_files + run_standard_test_sequence +} +``` + +### 2. Unit Tests +- Basic tests in testing/unit_test.go +- Extended tests in tests/unit/ +- Validate resource attributes +- Check resource counts +- Verify configurations + +```go +func TestTemplate(t *testing.T) { + testFixture := infratests.UnitTestFixture{ + GoTest: t, + TfOptions: tfOptions, + ExpectedResourceCount: count, + ExpectedResourceAttributeValues: infratests.ResourceDescription{ + "module.example.azurerm_example.main": expectedResult, + }, + } + infratests.RunUnitTests(&testFixture) +} +``` + +### 3. Integration Tests +- Implement in tests/integration/ +- Test actual resource creation +- Verify resource properties +- Clean up resources after tests + +## Documentation + +### 1. README.md +- Module description +- Usage examples +- Input variables table +- Output variables table +- License information + +### 2. Code Comments +- Include copyright header +- Document non-obvious logic +- Explain variable purposes +- Detail resource configurations + +## Best Practices + +1. **Resource Naming** +- Use consistent resource names +- Prefix resources appropriately +- Follow Azure naming conventions + +2. **Security** +- Enable HTTPS by default +- Use latest TLS versions +- Implement proper access controls +- Mark sensitive outputs + +3. **Resource Management** +- Implement proper cleanup +- Use resource locks where needed +- Handle dependencies correctly + +4. **Testing** +- Implement both unit and integration tests +- Use meaningful test names +- Clean up test resources +- Validate all configurations + +## Continuous Integration + +1. **Pre-commit Checks** +- Run terraform fmt +- Validate configurations +- Check for sensitive data + +2. **Automated Testing** +- Execute unit tests +- Run integration tests +- Verify outputs + +3. **Documentation Updates** +- Keep README current +- Update examples +- Document changes + +## License and Copyright + +All terraform and go files must include the Apache 2.0 license header: + +```hcl +// Copyright © Microsoft Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +``` \ No newline at end of file diff --git a/infra/modules/magefile.go b/infra/modules/magefile.go index 2ff7b4c76..89ef8cb08 100644 --- a/infra/modules/magefile.go +++ b/infra/modules/magefile.go @@ -27,6 +27,7 @@ func TestModules() error { return FindAndRunTests("testing") } + // Validate both Terraform code and Go code. func Check() { mg.Deps(LintTF) @@ -97,6 +98,11 @@ func verifyRunsQuietly(instructionsToFix string, cmd string, args ...string) err // FindAndRunTests finds all tests with a given path suffix and runs them using `go test` func FindAndRunTests(pathSuffix string) error { + // Set default resource group name if not set + if os.Getenv("RESOURCE_GROUP_NAME") == "" { + os.Setenv("RESOURCE_GROUP_NAME", "osdu-testing") + } + goModules, err := sh.Output("go", "list", "./...") if err != nil { return err @@ -118,3 +124,68 @@ func FindAndRunTests(pathSuffix string) error { cmdArgs = append(cmdArgs, "-v", "-timeout", "7200s") return sh.RunV("go", cmdArgs...) } + +// ModuleTest runs the test.sh script for a specific module +func Test(module string) error { + fmt.Printf("INFO: Running test script for module: %s\n", module) + + // Check required environment variables + if os.Getenv("ARM_SUBSCRIPTION_ID") == "" { + return fmt.Errorf("ERROR: ARM_SUBSCRIPTION_ID environment variable is required") + } + + // Ensure we're in the right directory context + moduleDir := filepath.Join("providers", "azure", module) + scriptPath := filepath.Join(moduleDir, "test.sh") + + if _, err := os.Stat(scriptPath); os.IsNotExist(err) { + return fmt.Errorf("test.sh script not found for module '%s' at path: %s", module, scriptPath) + } + + // Make the script executable + if err := os.Chmod(scriptPath, 0755); err != nil { + return fmt.Errorf("failed to make script executable: %v", err) + } + + // Set default environment variables if not already set + if os.Getenv("RESOURCE_GROUP_NAME") == "" { + os.Setenv("RESOURCE_GROUP_NAME", "osdu-testing") + } + if os.Getenv("LOCATION") == "" { + os.Setenv("LOCATION", "eastus2") + } + + // Run the script from its directory with debug output + fmt.Printf("INFO: Running script from directory: %s\n", moduleDir) + fmt.Printf("INFO: Using RESOURCE_GROUP_NAME: %s\n", os.Getenv("RESOURCE_GROUP_NAME")) + fmt.Printf("INFO: Using LOCATION: %s\n", os.Getenv("LOCATION")) + + // Change to the module directory before running the script + currentDir, err := os.Getwd() + if err != nil { + return fmt.Errorf("failed to get current directory: %v", err) + } + + if err := os.Chdir(moduleDir); err != nil { + return fmt.Errorf("failed to change to module directory: %v", err) + } + + // Change back to original directory when done + defer os.Chdir(currentDir) + + // Set environment variables for the script + env := map[string]string{ + "SCRIPT_DIR": moduleDir, + "ARM_SUBSCRIPTION_ID": os.Getenv("ARM_SUBSCRIPTION_ID"), + "RESOURCE_GROUP_NAME": os.Getenv("RESOURCE_GROUP_NAME"), + "LOCATION": os.Getenv("LOCATION"), + } + + // Set the environment variables + for k, v := range env { + os.Setenv(k, v) + } + + // Use RunV to show all output + return sh.RunV("./test.sh") +} diff --git a/infra/modules/providers/azure/aks/testing/unit_test.go b/infra/modules/providers/azure/aks/testing/unit_test.go index 438be6c71..878905156 100644 --- a/infra/modules/providers/azure/aks/testing/unit_test.go +++ b/infra/modules/providers/azure/aks/testing/unit_test.go @@ -1,16 +1,16 @@ -// Copyright © Microsoft Corporation +// Copyright © Microsoft Corporation // -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package test import ( diff --git a/infra/modules/providers/azure/log-analytics/test.sh b/infra/modules/providers/azure/log-analytics/test.sh new file mode 100755 index 000000000..ced5db769 --- /dev/null +++ b/infra/modules/providers/azure/log-analytics/test.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# Exit on error +set -e + +############################### +# Source Common Functions +############################### +COMMON_LIB="../test-functions.sh" +if [ ! -f "$COMMON_LIB" ]; then + echo "Error: Common library not found at $COMMON_LIB" + exit 1 +fi +source "$COMMON_LIB" + + +############################### +# Script Configuration +############################### +SCRIPT_DIR=$(get_script_dir) +setup_test_directories "$SCRIPT_DIR" + + +############################### +# Required Environment Variables +############################### +validate_azure_credentials + + +############################### +# Optional Variables +############################### +# These can be overridden by setting them before running the script +RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"terraform-test"} +DEFAULT_LOCATION="eastus2" +LOG_ANALYTICS_NAME=${LOG_ANALYTICS_NAME:-""} # Will be auto-generated if not provided + + +############################### +# Help Documentation +############################### +print_help() { + print_common_help "Log Analytics" "\n- If not specified, a unique log analytics workspace name will be generated" +} + + +############################### +# Required Module Functions +############################### +setup_configuration() { + # Setup base configuration first + setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" + + # Generate log analytics name if not provided + if [ -z "$LOG_ANALYTICS_NAME" ]; then + LOG_ANALYTICS_NAME=$(generate_unique_name "" "logs") + fi + + # Export additional variables for Go tests + export LOG_ANALYTICS_NAME +} + +create_tfvars_files() { + local tfvars_content="name = \"$LOG_ANALYTICS_NAME\"\nresource_group_name = \"$RESOURCE_GROUP_NAME\"\n\nsolutions = [\n {\n solution_name = \"ContainerInsights\"\n publisher = \"Microsoft\"\n product = \"OMSGallery/ContainerInsights\"\n }\n]\n\nresource_tags = {\n osdu = \"module\"\n}" + create_base_tfvars_files "$tfvars_content" +} + + +############################### +# Optional Module Functions +############################### +validate_variables() { + validate_base_variables + log "Using Log Analytics Workspace: $LOG_ANALYTICS_NAME" 6 +} + + +############################### +# Main Execution +############################### +main() { + # Trap cleanup on exit + trap 'cleanup; cleanup_resource_group "$RESOURCE_GROUP_NAME" "$ARM_SUBSCRIPTION_ID"' EXIT + + # Setup configuration + setup_configuration "$@" + + # Validate variables + validate_variables + + # Setup + setup_azure_with_rg + create_tfvars_files + + # Run all tests + run_standard_test_sequence +} + +# Check for help flag +case "$1" in + -h|--help) + print_help + exit 0 + ;; +esac + +# Execute main function +main "$@" \ No newline at end of file diff --git a/infra/modules/providers/azure/storage-account/testing/unit_test.go b/infra/modules/providers/azure/storage-account/testing/unit_test.go index 75e585674..50e377b35 100644 --- a/infra/modules/providers/azure/storage-account/testing/unit_test.go +++ b/infra/modules/providers/azure/storage-account/testing/unit_test.go @@ -46,7 +46,7 @@ func TestTemplate(t *testing.T) { Vars: map[string]interface{}{ "name": storageAcctName, "resource_group_name": resourceGroupName, - "location": location, + "location": location, }, } diff --git a/infra/modules/providers/azure/storage-account/tests/tf_options.go b/infra/modules/providers/azure/storage-account/tests/tf_options.go index 44dec69fa..370f857eb 100644 --- a/infra/modules/providers/azure/storage-account/tests/tf_options.go +++ b/infra/modules/providers/azure/storage-account/tests/tf_options.go @@ -31,7 +31,7 @@ var ResourceGroupName = os.Getenv("RESOURCE_GROUP_NAME") // StorageTFOptions common terraform options used for unit testing var StorageTFOptions = &terraform.Options{ - TerraformDir: "../../", // Point to module directory for unit tests + TerraformDir: "../../", // Point to module directory for unit tests Vars: map[string]interface{}{ "resource_group_name": ResourceGroupName, "name": StorageAccount, @@ -43,7 +43,7 @@ var StorageTFOptions = &terraform.Options{ // StorageIntegrationTFOptions terraform options used for integration testing var StorageIntegrationTFOptions = &terraform.Options{ - TerraformDir: "../../testing", // Point to testing directory for integration tests + TerraformDir: "../../testing", // Point to testing directory for integration tests Vars: map[string]interface{}{ "resource_group_name": ResourceGroupName, "name": StorageAccount, diff --git a/infra/modules/providers/azure/terraform-module.mdc b/infra/modules/providers/azure/terraform-module.mdc new file mode 100644 index 000000000..0519ecba6 --- /dev/null +++ b/infra/modules/providers/azure/terraform-module.mdc @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/infra/modules/providers/azure/test-functions.sh b/infra/modules/providers/azure/test-functions.sh index 9ad75e58e..544592d73 100644 --- a/infra/modules/providers/azure/test-functions.sh +++ b/infra/modules/providers/azure/test-functions.sh @@ -199,9 +199,12 @@ setup_azure() { ############################### # Common Configuration Functions ############################### +# Default Azure location for all tests +DEFAULT_LOCATION="eastus2" + setup_base_configuration() { local prefix="$1" - local default_location="$2" + local default_location="${2:-$DEFAULT_LOCATION}" # Use provided location or DEFAULT_LOCATION local args=("${@:3}") # Get remaining args # Set location from args or default -- GitLab From b2c6e384f607aac920d109aa59b0db44f2e1d6a5 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 18:46:49 -0600 Subject: [PATCH 06/15] Updated rules --- .../providers/azure/terraform-module.mdc | 297 +++++++++++++++++- 1 file changed, 296 insertions(+), 1 deletion(-) diff --git a/infra/modules/providers/azure/terraform-module.mdc b/infra/modules/providers/azure/terraform-module.mdc index 0519ecba6..97b4e1052 100644 --- a/infra/modules/providers/azure/terraform-module.mdc +++ b/infra/modules/providers/azure/terraform-module.mdc @@ -1 +1,296 @@ - \ No newline at end of file +# Terraform Module Development Guidelines + +This document outlines the standards and patterns for developing Terraform modules in our infrastructure codebase. + +## Module Structure + +``` +module-name/ +├── README.md # Module documentation +├── main.tf # Main module configuration +├── variables.tf # Input variable definitions +├── outputs.tf # Output definitions +├── test.sh # Test execution script +├── testing/ # Basic test configuration +│ ├── main.tf # Test implementation +│ └── unit_test.go # Basic unit tests +└── tests/ # Extended test suite + ├── .env.testing.template # Environment variables template + ├── tf_options.go # Common test configuration + ├── unit/ # Extended unit tests + │ └── *_test.go + └── integration/ # Integration tests + └── *_test.go +``` + +## Code Organization + +### 1. Main Configuration (main.tf) +- Start with required provider configuration +- Include copyright header +- Group resources logically +- Use data sources for existing resources +- Implement resource configurations +- Use dynamic blocks for optional features + +```hcl +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "=3.90.0" # Pin to specific version + } + } +} + +provider "azurerm" { + features {} +} + +data "azurerm_resource_group" "main" { + name = var.resource_group_name +} + +resource "azurerm_example" "main" { + name = var.name + resource_group_name = data.azurerm_resource_group.main.name + location = data.azurerm_resource_group.main.location + + dynamic "optional_block" { + for_each = var.optional_feature_enabled ? [1] : [] + content { + // Configuration + } + } +} +``` + +### 2. Variables (variables.tf) +- Group variables by purpose +- Include clear descriptions +- Provide type constraints +- Set defaults where appropriate +- Mark sensitive variables + +```hcl +variable "name" { + description = "The name of the resource" + type = string +} + +variable "resource_tags" { + description = "Map of tags to apply to resources" + type = map(string) + default = {} +} +``` + +### 3. Outputs (outputs.tf) +- Include essential resource information +- Mark sensitive outputs +- Group related outputs +- Use maps for collections +- Include resource IDs and names + +```hcl +output "id" { + description = "The ID of the created resource" + value = azurerm_example.main.id +} + +output "properties" { + description = "Properties of the deployed resource" + value = { + id = azurerm_example.main.id + name = azurerm_example.main.name + } + sensitive = true +} +``` + +## Testing Framework + +### 1. Common Test Functions Library (test-functions.sh) +The common test functions library provides essential functionality for all module tests. Key functions include: + +```bash +# Base Configuration Functions +setup_base_configuration() # Sets up basic test configuration +validate_base_variables() # Validates required environment variables +setup_azure() # Basic Azure setup without resource group +setup_azure_with_rg() # Azure setup including resource group creation + +# Resource Management +generate_unique_name() # Generates unique resource names +cleanup() # Cleans up test artifacts +cleanup_resource_group() # Removes Azure resource group + +# Test Utilities +get_script_dir() # Gets the current script directory +setup_test_directories() # Sets up test directory structure +log() # Formatted logging with colors +print_separator() # Prints visual separator in output +print_common_help() # Prints standardized help message + +# Test Execution +run_standard_test_sequence() # Executes the standard test suite +``` + +### 2. Test Script Implementation (test.sh) +Each module must implement these required components: + +1. **Required Variables** +```bash +COMMON_LIB="../test-functions.sh" # Path to common functions +SCRIPT_DIR # Current script directory +RESOURCE_GROUP_PREFIX # Prefix for resource group names +DEFAULT_LOCATION # Default Azure region +RESOURCE_NAME # Module-specific resource name +``` + +2. **Required Functions** +```bash +setup_configuration() { + # Must implement: + # 1. Call setup_base_configuration + # 2. Generate resource names if needed + # 3. Export variables for Go tests +} + +create_tfvars_files() { + # Must implement: + # 1. Define tfvars content + # 2. Call create_base_tfvars_files +} + +validate_variables() { + # Should implement: + # 1. Call validate_base_variables + # 2. Validate module-specific variables +} + +print_help() { + # Should implement: + # 1. Call print_common_help + # 2. Add module-specific help info +} +``` + +3. **Standard Error Handling** +```bash +# Required error handling patterns +set -e # Exit on any error +trap 'cleanup' EXIT # Ensure cleanup on exit +validate_azure_credentials # Check Azure authentication + +# Error checking examples +if [ ! -f "$COMMON_LIB" ]; then + echo "Error: Common library not found" + exit 1 +fi + +if [ -z "$REQUIRED_VAR" ]; then + log "Error: Required variable not set" 1 + exit 1 +fi +``` + +### 3. Testing Directory Structure +Explanation of required test files and their purposes: + +``` +module-name/ +├── testing/ # Basic test configuration +│ ├── main.tf # Basic test implementation +│ │ # Required sections: +│ │ # - Provider configuration +│ │ # - Resource group module +│ │ # - Module under test +│ │ # - Required variables +│ │ +│ └── unit_test.go # Basic unit tests +│ # Required tests: +│ # - Resource count validation +│ # - Basic attribute validation +│ # - Required tag validation +│ +└── tests/ # Extended test suite + ├── tf_options.go # Test configuration + │ # Required configuration: + │ # - TF_VAR environment variables + │ # - Terraform options + │ # - Test fixtures + │ + ├── unit/ # Extended unit tests + │ └── *_test.go # Specific resource tests + │ # Should include: + │ # - Detailed attribute validation + │ # - Configuration variants + │ # - Error cases + │ + └── integration/ # Integration tests + └── *_test.go # Live resource tests + # Should include: + # - Resource creation verification + # - Resource update testing + # - Resource deletion testing +``` + +### 4. Common Test Patterns + +1. **Resource Name Generation** +```bash +# Standard pattern for resource names +generate_unique_name() { + local prefix="$1" + local resource_type="$2" + echo "${prefix}${resource_type}${RANDOM}" +} + +# Usage example +STORAGE_ACCOUNT_NAME=$(generate_unique_name "" "sa") +LOG_ANALYTICS_NAME=$(generate_unique_name "" "logs") +``` + +2. **Variable Validation** +```bash +validate_variables() { + validate_base_variables + + # Resource name validation + if [[ ! "$RESOURCE_NAME" =~ ^[a-z0-9]+$ ]]; then + log "Error: Resource name must be lowercase alphanumeric" 1 + exit 1 + fi + + # Location validation + if [[ ! "$LOCATION" =~ ^[a-z]+[a-z0-9]+$ ]]; then + log "Error: Invalid location format" 1 + exit 1 + fi +} +``` + +3. **Terraform Variable File Creation** +```bash +create_tfvars_files() { + # Standard format for tfvars content + local tfvars_content=" +name = \"$RESOURCE_NAME\" +resource_group_name = \"$RESOURCE_GROUP_NAME\" +location = \"$LOCATION\" + +# Optional configurations +tags = { + environment = \"testing\" + module = \"example\" +} + +# Resource-specific configurations +specific_setting = \"value\" +" + create_base_tfvars_files "$tfvars_content" +} +``` + +// ... rest of the file remains unchanged ... \ No newline at end of file -- GitLab From c15aa90d403cdbe736df7c8655e1dc75f8e9b2fe Mon Sep 17 00:00:00 2001 From: "danielscholl (aider)" <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 19:19:09 -0600 Subject: [PATCH 07/15] feat: Create test script for app-insights Terraform module --- .../providers/azure/app-insights/test.sh | 93 +++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 infra/modules/providers/azure/app-insights/test.sh diff --git a/infra/modules/providers/azure/app-insights/test.sh b/infra/modules/providers/azure/app-insights/test.sh new file mode 100644 index 000000000..41bc31d42 --- /dev/null +++ b/infra/modules/providers/azure/app-insights/test.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# Exit on error +set -e + +############################### +# Source Common Functions +############################### +COMMON_LIB="../../test-functions.sh" +if [ ! -f "$COMMON_LIB" ]; then + echo "Error: Common library not found at $COMMON_LIB" + exit 1 +fi +source "$COMMON_LIB" + +############################### +# Script Configuration +############################### +SCRIPT_DIR=$(get_script_dir) +setup_test_directories "$SCRIPT_DIR" + +############################### +# Required Environment Variables +############################### +validate_azure_credentials + +############################### +# Optional Variables +############################### +# These can be overridden by setting them before running the script +RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"app-insights-test"} +DEFAULT_LOCATION="eastus2" + +############################### +# Help Documentation +############################### +print_help() { + print_common_help "App Insights" +} + +############################### +# Required Module Functions +############################### +setup_configuration() { + # Setup base configuration + setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" + export APP_INSIGHTS_NAME="${RESOURCE_GROUP_NAME}-app-insights" +} + +create_tfvars_files() { + local tfvars_content=" +appinsights_name = \"${APP_INSIGHTS_NAME}\" +service_plan_resource_group_name = \"${RESOURCE_GROUP_NAME}\" +appinsights_application_type = \"java\" +resource_tags = { + environment = \"testing\" + module = \"app-insights\" +} +" + create_base_tfvars_files "$tfvars_content" +} + +############################### +# Main Execution +############################### +main() { + # Trap cleanup on exit + trap 'cleanup; cleanup_resource_group "$RESOURCE_GROUP_NAME" "$ARM_SUBSCRIPTION_ID"' EXIT + + # Setup configuration + setup_configuration "$@" + + # Validate variables + validate_base_variables + + # Setup + setup_azure_with_rg + create_tfvars_files + + # Run all tests + run_standard_test_sequence +} + +# Check for help flag +case "$1" in + -h|--help) + print_help + exit 0 + ;; +esac + +# Execute main function +main "$@" -- GitLab From 51b77f0f3bdaacb059c3a2cd0d601deda0607c46 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 19:20:39 -0600 Subject: [PATCH 08/15] chore: Update test.sh file permissions to executable --- infra/modules/providers/azure/app-insights/test.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 infra/modules/providers/azure/app-insights/test.sh diff --git a/infra/modules/providers/azure/app-insights/test.sh b/infra/modules/providers/azure/app-insights/test.sh old mode 100644 new mode 100755 -- GitLab From 7eeb4bab0d4412f76b6547c1a360c0f69b64a83c Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 19:26:21 -0600 Subject: [PATCH 09/15] Reset code --- .../.cursor/rules/terraform-module.mdc | 209 ++++++++++--- .../providers/azure/app-insights/test.sh | 93 ------ .../providers/azure/terraform-module.mdc | 296 ------------------ 3 files changed, 167 insertions(+), 431 deletions(-) delete mode 100755 infra/modules/providers/azure/app-insights/test.sh delete mode 100644 infra/modules/providers/azure/terraform-module.mdc diff --git a/infra/modules/.cursor/rules/terraform-module.mdc b/infra/modules/.cursor/rules/terraform-module.mdc index de8ea8499..97fab95d1 100644 --- a/infra/modules/.cursor/rules/terraform-module.mdc +++ b/infra/modules/.cursor/rules/terraform-module.mdc @@ -114,63 +114,188 @@ output "properties" { ## Testing Framework -### 1. Test Script (test.sh) -- Source common test functions -- Implement required module functions -- Setup configuration -- Create tfvars files -- Execute test sequence +### 1. Common Test Functions Library (test-functions.sh) +The common test functions library provides essential functionality for all module tests. Key functions include: ```bash -#!/bin/bash -source "../test-functions.sh" +# Base Configuration Functions +setup_base_configuration() # Sets up basic test configuration +validate_base_variables() # Validates required environment variables +setup_azure() # Basic Azure setup without resource group +setup_azure_with_rg() # Azure setup including resource group creation + +# Resource Management +generate_unique_name() # Generates unique resource names +cleanup() # Cleans up test artifacts +cleanup_resource_group() # Removes Azure resource group + +# Test Utilities +get_script_dir() # Gets the current script directory +setup_test_directories() # Sets up test directory structure +log() # Formatted logging with colors +print_separator() # Prints visual separator in output +print_common_help() # Prints standardized help message + +# Test Execution +run_standard_test_sequence() # Executes the standard test suite +``` + +### 2. Test Script Implementation (test.sh) +Each module must implement these required components: + +1. **Required Variables** +```bash +COMMON_LIB="../test-functions.sh" # Path to common functions +SCRIPT_DIR # Current script directory +RESOURCE_GROUP_PREFIX # Prefix for resource group names +DEFAULT_LOCATION # Default Azure region +RESOURCE_NAME # Module-specific resource name +``` +2. **Required Functions** +```bash setup_configuration() { - setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" - # Add module-specific configuration + # Must implement: + # 1. Call setup_base_configuration + # 2. Generate resource names if needed + # 3. Export variables for Go tests } create_tfvars_files() { - local tfvars_content="..." - create_base_tfvars_files "$tfvars_content" + # Must implement: + # 1. Define tfvars content + # 2. Call create_base_tfvars_files +} + +validate_variables() { + # Should implement: + # 1. Call validate_base_variables + # 2. Validate module-specific variables } -main() { - trap 'cleanup' EXIT - setup_configuration "$@" - validate_variables - setup_azure - create_tfvars_files - run_standard_test_sequence +print_help() { + # Should implement: + # 1. Call print_common_help + # 2. Add module-specific help info } ``` -### 2. Unit Tests -- Basic tests in testing/unit_test.go -- Extended tests in tests/unit/ -- Validate resource attributes -- Check resource counts -- Verify configurations - -```go -func TestTemplate(t *testing.T) { - testFixture := infratests.UnitTestFixture{ - GoTest: t, - TfOptions: tfOptions, - ExpectedResourceCount: count, - ExpectedResourceAttributeValues: infratests.ResourceDescription{ - "module.example.azurerm_example.main": expectedResult, - }, - } - infratests.RunUnitTests(&testFixture) +3. **Standard Error Handling** +```bash +# Required error handling patterns +set -e # Exit on any error +trap 'cleanup' EXIT # Ensure cleanup on exit +validate_azure_credentials # Check Azure authentication + +# Error checking examples +if [ ! -f "$COMMON_LIB" ]; then + echo "Error: Common library not found" + exit 1 +fi + +if [ -z "$REQUIRED_VAR" ]; then + log "Error: Required variable not set" 1 + exit 1 +fi +``` + +### 3. Testing Directory Structure +Explanation of required test files and their purposes: + +``` +module-name/ +├── testing/ # Basic test configuration +│ ├── main.tf # Basic test implementation +│ │ # Required sections: +│ │ # - Provider configuration +│ │ # - Resource group module +│ │ # - Module under test +│ │ # - Required variables +│ │ +│ └── unit_test.go # Basic unit tests +│ # Required tests: +│ # - Resource count validation +│ # - Basic attribute validation +│ # - Required tag validation +│ +└── tests/ # Extended test suite + ├── tf_options.go # Test configuration + │ # Required configuration: + │ # - TF_VAR environment variables + │ # - Terraform options + │ # - Test fixtures + │ + ├── unit/ # Extended unit tests + │ └── *_test.go # Specific resource tests + │ # Should include: + │ # - Detailed attribute validation + │ # - Configuration variants + │ # - Error cases + │ + └── integration/ # Integration tests + └── *_test.go # Live resource tests + # Should include: + # - Resource creation verification + # - Resource update testing + # - Resource deletion testing +``` + +### 4. Common Test Patterns + +1. **Resource Name Generation** +```bash +# Standard pattern for resource names +generate_unique_name() { + local prefix="$1" + local resource_type="$2" + echo "${prefix}${resource_type}${RANDOM}" +} + +# Usage example +STORAGE_ACCOUNT_NAME=$(generate_unique_name "" "sa") +LOG_ANALYTICS_NAME=$(generate_unique_name "" "logs") +``` + +2. **Variable Validation** +```bash +validate_variables() { + validate_base_variables + + # Resource name validation + if [[ ! "$RESOURCE_NAME" =~ ^[a-z0-9]+$ ]]; then + log "Error: Resource name must be lowercase alphanumeric" 1 + exit 1 + fi + + # Location validation + if [[ ! "$LOCATION" =~ ^[a-z]+[a-z0-9]+$ ]]; then + log "Error: Invalid location format" 1 + exit 1 + fi } ``` -### 3. Integration Tests -- Implement in tests/integration/ -- Test actual resource creation -- Verify resource properties -- Clean up resources after tests +3. **Terraform Variable File Creation** +```bash +create_tfvars_files() { + # Standard format for tfvars content + local tfvars_content=" +name = \"$RESOURCE_NAME\" +resource_group_name = \"$RESOURCE_GROUP_NAME\" +location = \"$LOCATION\" + +# Optional configurations +tags = { + environment = \"testing\" + module = \"example\" +} + +# Resource-specific configurations +specific_setting = \"value\" +" + create_base_tfvars_files "$tfvars_content" +} +``` ## Documentation diff --git a/infra/modules/providers/azure/app-insights/test.sh b/infra/modules/providers/azure/app-insights/test.sh deleted file mode 100755 index 41bc31d42..000000000 --- a/infra/modules/providers/azure/app-insights/test.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash - -# Exit on error -set -e - -############################### -# Source Common Functions -############################### -COMMON_LIB="../../test-functions.sh" -if [ ! -f "$COMMON_LIB" ]; then - echo "Error: Common library not found at $COMMON_LIB" - exit 1 -fi -source "$COMMON_LIB" - -############################### -# Script Configuration -############################### -SCRIPT_DIR=$(get_script_dir) -setup_test_directories "$SCRIPT_DIR" - -############################### -# Required Environment Variables -############################### -validate_azure_credentials - -############################### -# Optional Variables -############################### -# These can be overridden by setting them before running the script -RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"app-insights-test"} -DEFAULT_LOCATION="eastus2" - -############################### -# Help Documentation -############################### -print_help() { - print_common_help "App Insights" -} - -############################### -# Required Module Functions -############################### -setup_configuration() { - # Setup base configuration - setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" - export APP_INSIGHTS_NAME="${RESOURCE_GROUP_NAME}-app-insights" -} - -create_tfvars_files() { - local tfvars_content=" -appinsights_name = \"${APP_INSIGHTS_NAME}\" -service_plan_resource_group_name = \"${RESOURCE_GROUP_NAME}\" -appinsights_application_type = \"java\" -resource_tags = { - environment = \"testing\" - module = \"app-insights\" -} -" - create_base_tfvars_files "$tfvars_content" -} - -############################### -# Main Execution -############################### -main() { - # Trap cleanup on exit - trap 'cleanup; cleanup_resource_group "$RESOURCE_GROUP_NAME" "$ARM_SUBSCRIPTION_ID"' EXIT - - # Setup configuration - setup_configuration "$@" - - # Validate variables - validate_base_variables - - # Setup - setup_azure_with_rg - create_tfvars_files - - # Run all tests - run_standard_test_sequence -} - -# Check for help flag -case "$1" in - -h|--help) - print_help - exit 0 - ;; -esac - -# Execute main function -main "$@" diff --git a/infra/modules/providers/azure/terraform-module.mdc b/infra/modules/providers/azure/terraform-module.mdc deleted file mode 100644 index 97b4e1052..000000000 --- a/infra/modules/providers/azure/terraform-module.mdc +++ /dev/null @@ -1,296 +0,0 @@ -# Terraform Module Development Guidelines - -This document outlines the standards and patterns for developing Terraform modules in our infrastructure codebase. - -## Module Structure - -``` -module-name/ -├── README.md # Module documentation -├── main.tf # Main module configuration -├── variables.tf # Input variable definitions -├── outputs.tf # Output definitions -├── test.sh # Test execution script -├── testing/ # Basic test configuration -│ ├── main.tf # Test implementation -│ └── unit_test.go # Basic unit tests -└── tests/ # Extended test suite - ├── .env.testing.template # Environment variables template - ├── tf_options.go # Common test configuration - ├── unit/ # Extended unit tests - │ └── *_test.go - └── integration/ # Integration tests - └── *_test.go -``` - -## Code Organization - -### 1. Main Configuration (main.tf) -- Start with required provider configuration -- Include copyright header -- Group resources logically -- Use data sources for existing resources -- Implement resource configurations -- Use dynamic blocks for optional features - -```hcl -terraform { - required_providers { - azurerm = { - source = "hashicorp/azurerm" - version = "=3.90.0" # Pin to specific version - } - } -} - -provider "azurerm" { - features {} -} - -data "azurerm_resource_group" "main" { - name = var.resource_group_name -} - -resource "azurerm_example" "main" { - name = var.name - resource_group_name = data.azurerm_resource_group.main.name - location = data.azurerm_resource_group.main.location - - dynamic "optional_block" { - for_each = var.optional_feature_enabled ? [1] : [] - content { - // Configuration - } - } -} -``` - -### 2. Variables (variables.tf) -- Group variables by purpose -- Include clear descriptions -- Provide type constraints -- Set defaults where appropriate -- Mark sensitive variables - -```hcl -variable "name" { - description = "The name of the resource" - type = string -} - -variable "resource_tags" { - description = "Map of tags to apply to resources" - type = map(string) - default = {} -} -``` - -### 3. Outputs (outputs.tf) -- Include essential resource information -- Mark sensitive outputs -- Group related outputs -- Use maps for collections -- Include resource IDs and names - -```hcl -output "id" { - description = "The ID of the created resource" - value = azurerm_example.main.id -} - -output "properties" { - description = "Properties of the deployed resource" - value = { - id = azurerm_example.main.id - name = azurerm_example.main.name - } - sensitive = true -} -``` - -## Testing Framework - -### 1. Common Test Functions Library (test-functions.sh) -The common test functions library provides essential functionality for all module tests. Key functions include: - -```bash -# Base Configuration Functions -setup_base_configuration() # Sets up basic test configuration -validate_base_variables() # Validates required environment variables -setup_azure() # Basic Azure setup without resource group -setup_azure_with_rg() # Azure setup including resource group creation - -# Resource Management -generate_unique_name() # Generates unique resource names -cleanup() # Cleans up test artifacts -cleanup_resource_group() # Removes Azure resource group - -# Test Utilities -get_script_dir() # Gets the current script directory -setup_test_directories() # Sets up test directory structure -log() # Formatted logging with colors -print_separator() # Prints visual separator in output -print_common_help() # Prints standardized help message - -# Test Execution -run_standard_test_sequence() # Executes the standard test suite -``` - -### 2. Test Script Implementation (test.sh) -Each module must implement these required components: - -1. **Required Variables** -```bash -COMMON_LIB="../test-functions.sh" # Path to common functions -SCRIPT_DIR # Current script directory -RESOURCE_GROUP_PREFIX # Prefix for resource group names -DEFAULT_LOCATION # Default Azure region -RESOURCE_NAME # Module-specific resource name -``` - -2. **Required Functions** -```bash -setup_configuration() { - # Must implement: - # 1. Call setup_base_configuration - # 2. Generate resource names if needed - # 3. Export variables for Go tests -} - -create_tfvars_files() { - # Must implement: - # 1. Define tfvars content - # 2. Call create_base_tfvars_files -} - -validate_variables() { - # Should implement: - # 1. Call validate_base_variables - # 2. Validate module-specific variables -} - -print_help() { - # Should implement: - # 1. Call print_common_help - # 2. Add module-specific help info -} -``` - -3. **Standard Error Handling** -```bash -# Required error handling patterns -set -e # Exit on any error -trap 'cleanup' EXIT # Ensure cleanup on exit -validate_azure_credentials # Check Azure authentication - -# Error checking examples -if [ ! -f "$COMMON_LIB" ]; then - echo "Error: Common library not found" - exit 1 -fi - -if [ -z "$REQUIRED_VAR" ]; then - log "Error: Required variable not set" 1 - exit 1 -fi -``` - -### 3. Testing Directory Structure -Explanation of required test files and their purposes: - -``` -module-name/ -├── testing/ # Basic test configuration -│ ├── main.tf # Basic test implementation -│ │ # Required sections: -│ │ # - Provider configuration -│ │ # - Resource group module -│ │ # - Module under test -│ │ # - Required variables -│ │ -│ └── unit_test.go # Basic unit tests -│ # Required tests: -│ # - Resource count validation -│ # - Basic attribute validation -│ # - Required tag validation -│ -└── tests/ # Extended test suite - ├── tf_options.go # Test configuration - │ # Required configuration: - │ # - TF_VAR environment variables - │ # - Terraform options - │ # - Test fixtures - │ - ├── unit/ # Extended unit tests - │ └── *_test.go # Specific resource tests - │ # Should include: - │ # - Detailed attribute validation - │ # - Configuration variants - │ # - Error cases - │ - └── integration/ # Integration tests - └── *_test.go # Live resource tests - # Should include: - # - Resource creation verification - # - Resource update testing - # - Resource deletion testing -``` - -### 4. Common Test Patterns - -1. **Resource Name Generation** -```bash -# Standard pattern for resource names -generate_unique_name() { - local prefix="$1" - local resource_type="$2" - echo "${prefix}${resource_type}${RANDOM}" -} - -# Usage example -STORAGE_ACCOUNT_NAME=$(generate_unique_name "" "sa") -LOG_ANALYTICS_NAME=$(generate_unique_name "" "logs") -``` - -2. **Variable Validation** -```bash -validate_variables() { - validate_base_variables - - # Resource name validation - if [[ ! "$RESOURCE_NAME" =~ ^[a-z0-9]+$ ]]; then - log "Error: Resource name must be lowercase alphanumeric" 1 - exit 1 - fi - - # Location validation - if [[ ! "$LOCATION" =~ ^[a-z]+[a-z0-9]+$ ]]; then - log "Error: Invalid location format" 1 - exit 1 - fi -} -``` - -3. **Terraform Variable File Creation** -```bash -create_tfvars_files() { - # Standard format for tfvars content - local tfvars_content=" -name = \"$RESOURCE_NAME\" -resource_group_name = \"$RESOURCE_GROUP_NAME\" -location = \"$LOCATION\" - -# Optional configurations -tags = { - environment = \"testing\" - module = \"example\" -} - -# Resource-specific configurations -specific_setting = \"value\" -" - create_base_tfvars_files "$tfvars_content" -} -``` - -// ... rest of the file remains unchanged ... \ No newline at end of file -- GitLab From 291f57874aaad63a1e678b20a29726b5bf924a97 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 19:56:17 -0600 Subject: [PATCH 10/15] Updated rules --- .../.cursor/rules/terraform-module.mdc | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/infra/modules/.cursor/rules/terraform-module.mdc b/infra/modules/.cursor/rules/terraform-module.mdc index 97fab95d1..69654a5af 100644 --- a/infra/modules/.cursor/rules/terraform-module.mdc +++ b/infra/modules/.cursor/rules/terraform-module.mdc @@ -117,27 +117,8 @@ output "properties" { ### 1. Common Test Functions Library (test-functions.sh) The common test functions library provides essential functionality for all module tests. Key functions include: -```bash -# Base Configuration Functions -setup_base_configuration() # Sets up basic test configuration -validate_base_variables() # Validates required environment variables -setup_azure() # Basic Azure setup without resource group -setup_azure_with_rg() # Azure setup including resource group creation - -# Resource Management -generate_unique_name() # Generates unique resource names -cleanup() # Cleans up test artifacts -cleanup_resource_group() # Removes Azure resource group - -# Test Utilities -get_script_dir() # Gets the current script directory -setup_test_directories() # Sets up test directory structure -log() # Formatted logging with colors -print_separator() # Prints visual separator in output -print_common_help() # Prints standardized help message - -# Test Execution -run_standard_test_sequence() # Executes the standard test suite +Note: The `test-functions.sh` script should be sourced into test scripts using the relative path `../test-functions.sh`. + ``` ### 2. Test Script Implementation (test.sh) -- GitLab From ed902228e3c57fd06c81fdcfbbbb972ffb857c8e Mon Sep 17 00:00:00 2001 From: "danielscholl (aider)" <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 19:59:41 -0600 Subject: [PATCH 11/15] feat: Add executable test script for app-insights module following guidelines --- .../providers/azure/app-insights/test.sh | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 infra/modules/providers/azure/app-insights/test.sh diff --git a/infra/modules/providers/azure/app-insights/test.sh b/infra/modules/providers/azure/app-insights/test.sh new file mode 100644 index 000000000..7b6a2ff66 --- /dev/null +++ b/infra/modules/providers/azure/app-insights/test.sh @@ -0,0 +1,89 @@ +#!/bin/bash + +# Exit on error +set -e + +############################### +# Source Common Functions +############################### +COMMON_LIB="../test-functions.sh" +if [ ! -f "$COMMON_LIB" ]; then + echo "Error: Common library not found at $COMMON_LIB" + exit 1 +fi +source "$COMMON_LIB" + + +############################### +# Script Configuration +############################### +SCRIPT_DIR=$(get_script_dir) +setup_test_directories "$SCRIPT_DIR" + + +############################### +# Required Environment Variables +############################### +validate_azure_credentials + + +############################### +# Optional Variables +############################### +# These can be overridden by setting them before running the script +RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"terraform-test"} +DEFAULT_LOCATION="eastus2" + + +############################### +# Help Documentation +############################### +print_help() { + print_common_help "App Insights" +} + + +############################### +# Required Module Functions +############################### +setup_configuration() { + # Setup base configuration + setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" +} + +create_tfvars_files() { + local tfvars_content="appinsights_name = \"${RESOURCE_GROUP_NAME}-app-insights\"\nlocation = \"$LOCATION\"\nservice_plan_resource_group_name = \"$RESOURCE_GROUP_NAME\"" + create_base_tfvars_files "$tfvars_content" +} + +############################### +# Main Execution +############################### +main() { + # Trap cleanup on exit + trap 'cleanup; cleanup_resource_group "$RESOURCE_GROUP_NAME" "$ARM_SUBSCRIPTION_ID"' EXIT + + # Setup configuration + setup_configuration "$@" + + # Validate variables + validate_base_variables + + # Setup + setup_azure + create_tfvars_files + + # Run all tests + run_standard_test_sequence +} + +# Check for help flag +case "$1" in + -h|--help) + print_help + exit 0 + ;; +esac + +# Execute main function +main "$@" -- GitLab From 95f3cfc03a75899b54bb33fbd7fce584ea71ff50 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Sun, 2 Feb 2025 20:03:01 -0600 Subject: [PATCH 12/15] chore: Update file permissions for test.sh in app-insights module --- infra/modules/providers/azure/app-insights/test.sh | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 infra/modules/providers/azure/app-insights/test.sh diff --git a/infra/modules/providers/azure/app-insights/test.sh b/infra/modules/providers/azure/app-insights/test.sh old mode 100644 new mode 100755 -- GitLab From cca2184d7d4adc155775849afe5b0feb419821aa Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Mon, 3 Feb 2025 14:21:38 -0600 Subject: [PATCH 13/15] Fixed lint errors. --- infra/modules/magefile.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/infra/modules/magefile.go b/infra/modules/magefile.go index 89ef8cb08..ee1a48271 100644 --- a/infra/modules/magefile.go +++ b/infra/modules/magefile.go @@ -27,7 +27,6 @@ func TestModules() error { return FindAndRunTests("testing") } - // Validate both Terraform code and Go code. func Check() { mg.Deps(LintTF) @@ -178,7 +177,7 @@ func Test(module string) error { "SCRIPT_DIR": moduleDir, "ARM_SUBSCRIPTION_ID": os.Getenv("ARM_SUBSCRIPTION_ID"), "RESOURCE_GROUP_NAME": os.Getenv("RESOURCE_GROUP_NAME"), - "LOCATION": os.Getenv("LOCATION"), + "LOCATION": os.Getenv("LOCATION"), } // Set the environment variables -- GitLab From ea247c80d6bfa3b82832dd39bbc5956d1f32d334 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Mon, 3 Feb 2025 17:16:31 -0600 Subject: [PATCH 14/15] Fixed the initial module tests so they all pass. --- .../providers/azure/app-insights/test.sh | 82 +++++++++++++++++-- .../azure/app-insights/testing/main.tf | 10 +++ .../azure/app-insights/testing/unit_test.go | 2 +- .../azure/storage-account/tests/tf_options.go | 10 ++- 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/infra/modules/providers/azure/app-insights/test.sh b/infra/modules/providers/azure/app-insights/test.sh index 7b6a2ff66..c1a76e265 100755 --- a/infra/modules/providers/azure/app-insights/test.sh +++ b/infra/modules/providers/azure/app-insights/test.sh @@ -33,13 +33,18 @@ validate_azure_credentials # These can be overridden by setting them before running the script RESOURCE_GROUP_PREFIX=${RESOURCE_GROUP_PREFIX:-"terraform-test"} DEFAULT_LOCATION="eastus2" +APP_INSIGHTS_NAME=${APP_INSIGHTS_NAME:-""} # Will be auto-generated if not provided +APP_INSIGHTS_TYPE=${APP_INSIGHTS_TYPE:-"web"} # Default to web application type + +# Workspace ID should be a full resource ID +WORKSPACE_ID=${WORKSPACE_ID:-""} # Will be auto-generated if not provided ############################### # Help Documentation ############################### print_help() { - print_common_help "App Insights" + print_common_help "App Insights" "\n- If not specified, a unique App Insights name will be generated\n- Default application type is 'web'\n- A Log Analytics workspace will be created for testing" } @@ -47,15 +52,80 @@ print_help() { # Required Module Functions ############################### setup_configuration() { - # Setup base configuration + # Setup base configuration first setup_base_configuration "$RESOURCE_GROUP_PREFIX" "$DEFAULT_LOCATION" "$@" + + # Generate App Insights name if not provided + if [ -z "$APP_INSIGHTS_NAME" ]; then + APP_INSIGHTS_NAME=$(generate_unique_name "" "ai") + fi + + # Generate workspace ID if not provided + if [ -z "$WORKSPACE_ID" ]; then + local workspace_name=$(generate_unique_name "" "log") + WORKSPACE_ID="/subscriptions/$ARM_SUBSCRIPTION_ID/resourceGroups/$RESOURCE_GROUP_NAME/providers/Microsoft.OperationalInsights/workspaces/$workspace_name" + fi + + # Export additional variables for Go tests + export APP_INSIGHTS_NAME + export WORKSPACE_ID + export APP_INSIGHTS_TYPE } create_tfvars_files() { - local tfvars_content="appinsights_name = \"${RESOURCE_GROUP_NAME}-app-insights\"\nlocation = \"$LOCATION\"\nservice_plan_resource_group_name = \"$RESOURCE_GROUP_NAME\"" + local tfvars_content=" +appinsights_name = \"$APP_INSIGHTS_NAME\" +service_plan_resource_group_name = \"$RESOURCE_GROUP_NAME\" +appinsights_application_type = \"$APP_INSIGHTS_TYPE\" +workspace_id = \"$WORKSPACE_ID\" + +resource_tags = { + environment = \"testing\" + module = \"app-insights\" +}" create_base_tfvars_files "$tfvars_content" } + +############################### +# Optional Module Functions +############################### +validate_variables() { + validate_base_variables + + # Validate App Insights specific variables + if [[ ! "$APP_INSIGHTS_NAME" =~ ^[a-zA-Z0-9-]+$ ]]; then + log "Error: App Insights name must be alphanumeric with hyphens" 1 + exit 1 + fi + + # Validate application type + local valid_types=("ios" "java" "MobileCenter" "Node.JS" "other" "phone" "store" "web") + local type_valid=false + for valid_type in "${valid_types[@]}"; do + if [ "$APP_INSIGHTS_TYPE" == "$valid_type" ]; then + type_valid=true + break + fi + done + + if [ "$type_valid" != true ]; then + log "Error: Invalid application type. Must be one of: ${valid_types[*]}" 1 + exit 1 + fi + + # Validate workspace ID format + if [[ ! "$WORKSPACE_ID" =~ ^/subscriptions/[^/]+/resourceGroups/[^/]+/providers/Microsoft.OperationalInsights/workspaces/[^/]+$ ]]; then + log "Error: Invalid workspace ID format. Expected format: /subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.OperationalInsights/workspaces/{workspace_name}" 1 + exit 1 + fi + + log "Using App Insights: $APP_INSIGHTS_NAME" 6 + log "Using Application Type: $APP_INSIGHTS_TYPE" 6 + log "Using Workspace ID: $WORKSPACE_ID" 6 +} + + ############################### # Main Execution ############################### @@ -67,10 +137,10 @@ main() { setup_configuration "$@" # Validate variables - validate_base_variables + validate_variables # Setup - setup_azure + setup_azure_with_rg create_tfvars_files # Run all tests @@ -86,4 +156,4 @@ case "$1" in esac # Execute main function -main "$@" +main "$@" \ No newline at end of file diff --git a/infra/modules/providers/azure/app-insights/testing/main.tf b/infra/modules/providers/azure/app-insights/testing/main.tf index 8c024156b..4a025f1e0 100644 --- a/infra/modules/providers/azure/app-insights/testing/main.tf +++ b/infra/modules/providers/azure/app-insights/testing/main.tf @@ -23,6 +23,15 @@ module "resource_group" { location = "eastus2" } +# Create Log Analytics workspace for App Insights +resource "azurerm_log_analytics_workspace" "test" { + name = "osdu-module-workspace-${module.resource_group.random}" + resource_group_name = module.resource_group.name + location = module.resource_group.location + sku = "PerGB2018" + retention_in_days = 30 +} + module "app-insights" { source = "../" depends_on = [module.resource_group] @@ -30,6 +39,7 @@ module "app-insights" { appinsights_name = "osdu-module-app-insights-${module.resource_group.random}" service_plan_resource_group_name = module.resource_group.name appinsights_application_type = "java" + workspace_id = azurerm_log_analytics_workspace.test.id resource_tags = { osdu = "module" diff --git a/infra/modules/providers/azure/app-insights/testing/unit_test.go b/infra/modules/providers/azure/app-insights/testing/unit_test.go index df35af7e4..dc9c0b9c3 100644 --- a/infra/modules/providers/azure/app-insights/testing/unit_test.go +++ b/infra/modules/providers/azure/app-insights/testing/unit_test.go @@ -12,7 +12,7 @@ import ( var workspace = "osdu-services-" + strings.ToLower(random.UniqueId()) var location = "eastus" -var count = 4 +var count = 5 var tfOptions = &terraform.Options{ TerraformDir: "./", diff --git a/infra/modules/providers/azure/storage-account/tests/tf_options.go b/infra/modules/providers/azure/storage-account/tests/tf_options.go index 370f857eb..a3479f52f 100644 --- a/infra/modules/providers/azure/storage-account/tests/tf_options.go +++ b/infra/modules/providers/azure/storage-account/tests/tf_options.go @@ -24,11 +24,19 @@ import ( var StorageAccount = os.Getenv("STORAGE_ACCOUNT_NAME") // ContainerName - The Container Name -var ContainerName = os.Getenv("CONTAINER_NAME") +var ContainerName = getContainerName() // ResourceGroupName - The Resource Group Name var ResourceGroupName = os.Getenv("RESOURCE_GROUP_NAME") +// getContainerName returns the container name from env var or a default value +func getContainerName() string { + if name := os.Getenv("CONTAINER_NAME"); name != "" { + return name + } + return "test-container" +} + // StorageTFOptions common terraform options used for unit testing var StorageTFOptions = &terraform.Options{ TerraformDir: "../../", // Point to module directory for unit tests -- GitLab From feffa91d089be2c81da1ba01a4ab45d256fb5912 Mon Sep 17 00:00:00 2001 From: danielscholl <dascholl@microsoft.com> Date: Mon, 3 Feb 2025 18:30:10 -0600 Subject: [PATCH 15/15] Lint Checked --- infra/modules/providers/azure/app-insights/testing/main.tf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/infra/modules/providers/azure/app-insights/testing/main.tf b/infra/modules/providers/azure/app-insights/testing/main.tf index 4a025f1e0..65b7257b6 100644 --- a/infra/modules/providers/azure/app-insights/testing/main.tf +++ b/infra/modules/providers/azure/app-insights/testing/main.tf @@ -27,9 +27,9 @@ module "resource_group" { resource "azurerm_log_analytics_workspace" "test" { name = "osdu-module-workspace-${module.resource_group.random}" resource_group_name = module.resource_group.name - location = module.resource_group.location - sku = "PerGB2018" - retention_in_days = 30 + location = module.resource_group.location + sku = "PerGB2018" + retention_in_days = 30 } module "app-insights" { -- GitLab