Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refactor terraform CLI tests with mock component #1018

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
427bee7
test: refactor terraform CLI tests with mock component
Cerebrovinny Feb 4, 2025
c05cfea
Add test fixtures for atmos configuration files
Cerebrovinny Feb 5, 2025
aee2abd
Add test fixtures documentation and directory structure
Cerebrovinny Feb 5, 2025
3ca5ace
Sanitize snapshots (#1002)
osterman Feb 4, 2025
f1f4cdc
Update tests/fixtures/scenarios/mock-terraform/atmos.yaml
Cerebrovinny Feb 5, 2025
78ab753
Remove inheritance from mock-terraform test scenario
Cerebrovinny Feb 5, 2025
a7f6e8e
test: improve terraform integration test reliability
Cerebrovinny Feb 6, 2025
5eb6087
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 6, 2025
154a323
Merge branch 'main' into tests/fixes
Cerebrovinny Feb 6, 2025
8411f19
Add random test component and remove myapp component
Cerebrovinny Feb 7, 2025
8007383
Update tests/fixtures/scenarios/mock-terraform/stacks/prod/mock.yaml
Cerebrovinny Feb 7, 2025
95dd117
Update tests/fixtures/scenarios/mock-terraform/atmos.yaml
Cerebrovinny Feb 7, 2025
30f49a0
Delete mock terraform test fixtures
Cerebrovinny Feb 7, 2025
c6a154a
refactor: update stack configurations and import paths
Cerebrovinny Feb 7, 2025
5e2b478
Update test fixtures and paths for terraform components
Cerebrovinny Feb 8, 2025
2fab060
test: update test cases and add random component fixtures
Cerebrovinny Feb 8, 2025
9587b48
[autofix.ci] apply automated fixes
autofix-ci[bot] Feb 8, 2025
e95ffea
Revert "test: update test cases and add random component fixtures"
Cerebrovinny Feb 8, 2025
bfc2451
update main tf
Cerebrovinny Feb 9, 2025
adcf46e
update tf main
Cerebrovinny Feb 9, 2025
9b53fa6
add variables
Cerebrovinny Feb 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 92 additions & 18 deletions tests/cli_terraform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ func TestCLITerraformClean(t *testing.T) {
t.Fatalf("Failed to apply updated PATH: %v", err)
}
fmt.Printf("Updated PATH: %s\n", pathManager.GetPath())

// Setup cleanup function to run after test completion
defer func() {
// Change back to the original working directory after the test
if err := os.Chdir(startingDir); err != nil {
Expand All @@ -33,7 +35,7 @@ func TestCLITerraformClean(t *testing.T) {
}()

// Define the work directory and change to it
workDir := "../examples/quick-start-simple"
workDir := "fixtures/scenarios/mock-terraform"
if err := os.Chdir(workDir); err != nil {
t.Fatalf("Failed to change directory to %q: %v", workDir, err)
}
Expand All @@ -44,29 +46,32 @@ func TestCLITerraformClean(t *testing.T) {
t.Fatalf("Binary not found: %s. Current PATH: %s", "atmos", pathManager.GetPath())
}

// Force clean everything
runTerraformCleanCommand(t, binaryPath, "--force")
// Clean everything
runTerraformCleanCommand(t, binaryPath)
// Clean specific component
runTerraformCleanCommand(t, binaryPath, "station")
// Clean component with stack
runTerraformCleanCommand(t, binaryPath, "station", "-s", "dev")
// Clean up any existing Terraform state before starting tests
cleanupTerraformState(t)

// Initialize terraform for both environments
runTerraformInit(t, binaryPath, "prod")
runTerraformInit(t, binaryPath, "dev")

// Run terraform apply for prod environment
runTerraformApply(t, binaryPath, "prod")
verifyStateFilesExist(t, []string{"./components/terraform/weather/terraform.tfstate.d/prod-station"})
verifyStateFilesExist(t, []string{
"terraform/components/mock/.terraform",
"terraform/components/mock/.terraform.lock.hcl",
})
runCLITerraformCleanComponent(t, binaryPath, "prod")
verifyStateFilesDeleted(t, []string{"./components/terraform/weather/terraform.tfstate.d/prod-station"})
verifyStateFilesDeleted(t, []string{
"terraform/components/mock/.terraform",
"terraform/components/mock/.terraform.lock.hcl",
})

// Run terraform apply for dev environment
runTerraformApply(t, binaryPath, "dev")

// Verify if state files exist before cleaning
stateFiles := []string{
"./components/terraform/weather/.terraform",
"./components/terraform/weather/terraform.tfstate.d",
"./components/terraform/weather/.terraform.lock.hcl",
"terraform/components/mock/.terraform",
"terraform/components/mock/.terraform.lock.hcl",
}
verifyStateFilesExist(t, stateFiles)

Expand All @@ -75,12 +80,11 @@ func TestCLITerraformClean(t *testing.T) {

// Verify if state files have been deleted after clean
verifyStateFilesDeleted(t, stateFiles)

}

// runTerraformApply runs the terraform apply command for a given environment.
func runTerraformApply(t *testing.T, binaryPath, environment string) {
cmd := exec.Command(binaryPath, "terraform", "apply", "station", "-s", environment)
cmd := exec.Command(binaryPath, "terraform", "apply", "mock", "-s", environment)
envVars := os.Environ()
envVars = append(envVars, "ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE=true")
cmd.Env = envVars
Expand All @@ -91,7 +95,7 @@ func runTerraformApply(t *testing.T, binaryPath, environment string) {
err := cmd.Run()
t.Log(stdout.String())
if err != nil {
t.Fatalf("Failed to run terraform apply station -s %s: %v", environment, stderr.String())
t.Fatalf("Failed to run terraform apply mock -s %s: %v", environment, stderr.String())
}
}

Expand Down Expand Up @@ -138,7 +142,7 @@ func verifyStateFilesDeleted(t *testing.T, stateFiles []string) {
}

func runCLITerraformCleanComponent(t *testing.T, binaryPath, environment string) {
cmd := exec.Command(binaryPath, "terraform", "clean", "station", "-s", environment, "--force")
cmd := exec.Command(binaryPath, "terraform", "clean", "mock", "-s", environment, "--force")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
Expand All @@ -148,6 +152,7 @@ func runCLITerraformCleanComponent(t *testing.T, binaryPath, environment string)
t.Fatalf("Failed to run terraform clean: %v", stderr.String())
}
}

func runCLITerraformClean(t *testing.T, binaryPath string) {
cmd := exec.Command(binaryPath, "terraform", "clean")
var stdout, stderr bytes.Buffer
Expand All @@ -158,7 +163,46 @@ func runCLITerraformClean(t *testing.T, binaryPath string) {
if err != nil {
t.Fatalf("Failed to run terraform clean: %v", stderr.String())
}
}

// runTerraformInit initializes terraform for a given environment
func runTerraformInit(t *testing.T, binaryPath, environment string) {
cmd := exec.Command(binaryPath, "terraform", "init", "mock", "-s", environment)

// Set environment variables
envVars := os.Environ()
envVars = append(envVars,
"ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT=true",
"ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE=true",
"ATMOS_LOGS_LEVEL=Debug",
"ATMOS_LOGS_FILE=/dev/stderr")
cmd.Env = envVars

var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

// Log the command being executed
t.Logf("Running terraform init for environment %s: %s %v", environment, binaryPath, cmd.Args)

err := cmd.Run()
// Always log both stdout and stderr
t.Logf("Init command stdout:\n%s", stdout.String())
t.Logf("Init command stderr:\n%s", stderr.String())

if err != nil {
t.Fatalf("Failed to run terraform init mock -s %s: %v\nStdout: %s\nStderr: %s",
environment, err, stdout.String(), stderr.String())
}

// Verify that terraform was properly initialized
terraformDir := filepath.Join("terraform", "components", "mock")
if _, err := os.Stat(filepath.Join(terraformDir, ".terraform")); os.IsNotExist(err) {
t.Fatalf("Terraform was not properly initialized: .terraform directory not found in %s", terraformDir)
}
if _, err := os.Stat(filepath.Join(terraformDir, ".terraform.lock.hcl")); os.IsNotExist(err) {
t.Fatalf("Terraform was not properly initialized: .terraform.lock.hcl not found in %s", terraformDir)
}
}

func runTerraformCleanCommand(t *testing.T, binaryPath string, args ...string) {
Expand All @@ -173,3 +217,33 @@ func runTerraformCleanCommand(t *testing.T, binaryPath string, args ...string) {
t.Fatalf("Failed to run terraform clean: %v", stderr.String())
}
}

func cleanupTerraformState(t *testing.T) {
t.Helper()

// Define paths to clean
terraformDir := filepath.Join("terraform", "components", "mock")
pathsToClean := []string{
filepath.Join(terraformDir, ".terraform"),
filepath.Join(terraformDir, ".terraform.lock.hcl"),
filepath.Join(terraformDir, "terraform.tfstate"),
filepath.Join(terraformDir, "terraform.tfstate.backup"),
filepath.Join(terraformDir, ".terraform.tfstate.lock.info"),
filepath.Join(terraformDir, "terraform.tfstate.d"),
}

// Remove each path
for _, path := range pathsToClean {
err := os.RemoveAll(path)
if err != nil && !os.IsNotExist(err) {
t.Fatalf("Failed to clean up Terraform state at %s: %v", path, err)
}
}

// Ensure the terraform directory exists
if err := os.MkdirAll(terraformDir, 0o755); err != nil {
t.Fatalf("Failed to create terraform directory at %s: %v", terraformDir, err)
}

t.Logf("Successfully cleaned up Terraform state in %s", terraformDir)
}
6 changes: 6 additions & 0 deletions tests/fixtures/components/terraform/random/backend.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
terraform {
# Using local backend for testing
backend "local" {
path = "terraform.tfstate"
}
}
97 changes: 97 additions & 0 deletions tests/fixtures/components/terraform/random/main.tf
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# 🎲 Random Component for Testing
# This component is designed to be simple, reusable, and perfect for testing Atmos functionality

terraform {
required_providers {
local = {
source = "hashicorp/local"
version = ">= 2.0"
}
}
}

# Common variables used across test scenarios
variable "stage" {
type = string
description = "Stage (e.g., dev, staging, prod)"
}

variable "environment" {
type = string
description = "Environment (e.g., ue2, uw2)"
}

variable "tenant" {
type = string
description = "Tenant name"
}

# Test-specific variables with defaults
variable "foo" {
type = string
default = "foo"
description = "Test variable foo"
}

variable "bar" {
type = string
default = "bar"
description = "Test variable bar"
}

variable "baz" {
type = string
default = "baz"
description = "Test variable baz"
}

# Simple local file resource for testing
resource "local_file" "test" {
content = jsonencode({
metadata = {
stage = var.stage
environment = var.environment
tenant = var.tenant
timestamp = timestamp()
}
config = {
foo = var.foo
bar = var.bar
baz = var.baz
}
})
filename = "${path.module}/test-${var.environment}-${var.stage}.json"
}

# Outputs for testing
output "stage" {
value = var.stage
}

output "environment" {
value = var.environment
}

output "tenant" {
value = var.tenant
}

output "foo" {
value = var.foo
}

output "bar" {
value = var.bar
}

output "baz" {
value = var.baz
}

output "file_path" {
value = local_file.test.filename
}

output "file_content" {
value = local_file.test.content
}
10 changes: 10 additions & 0 deletions tests/fixtures/components/terraform/random/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
terraform {
required_version = ">= 1.0.0"

required_providers {
local = {
source = "hashicorp/local"
version = ">= 2.0"
}
}
}
22 changes: 22 additions & 0 deletions tests/fixtures/scenarios/mock-terraform/atmos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
components:
terraform:
base_path: "terraform/components"
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
apply:
auto_approve: true
clean:
auto_approve: true
deploy_run_init: true
init_run_reconfigure: true
command: terraform

stacks:
base_path: "stacks"
included_paths:
- "**/*"
excluded_paths:
- "**/templates/**/*"
name_pattern: "{environment}-{component}"

logs:
file: "/dev/stderr"
level: "Info"
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
terraform {
backend "local" {
path = "terraform.tfstate"
}
}
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
variable "stage" {
type = string
}

variable "environment" {
type = string
}

variable "tenant" {
type = string
}

variable "foo" {
type = string
default = "foo"
}

variable "bar" {
type = string
default = "bar"
}

variable "baz" {
type = string
default = "baz"
}

# Mock resource for testing
resource "local_file" "mock" {
content = jsonencode({
foo = var.foo
bar = var.bar
baz = var.baz
stage = var.stage
environment = var.environment
tenant = var.tenant
})
filename = "${path.module}/mock.json"
}

output "foo" {
value = var.foo
}

output "bar" {
value = var.bar
}

output "baz" {
value = var.baz
}

output "stage" {
value = var.stage
}

output "environment" {
value = var.environment
}

output "tenant" {
value = var.tenant
}
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"bar":"dev-bar","baz":"dev-baz","environment":"dev","foo":"dev-foo","stage":"dev","tenant":"test"}
Loading
Loading