843 lines
38 KiB
Groovy
843 lines
38 KiB
Groovy
pipeline {
|
||
agent any
|
||
|
||
parameters {
|
||
choice(
|
||
name: 'ACTION',
|
||
choices: ['plan', 'apply', 'destroy', 'cleanup'],
|
||
description: 'Action to perform: plan (review), apply (deploy), destroy (remove infra), cleanup (remove bootstrap)'
|
||
)
|
||
booleanParam(
|
||
name: 'AUTO_APPROVE',
|
||
defaultValue: false,
|
||
description: 'Auto-approve terraform apply (use with caution)'
|
||
)
|
||
booleanParam(
|
||
name: 'SKIP_SONAR',
|
||
defaultValue: false,
|
||
description: 'Skip SonarQube analysis (not recommended)'
|
||
)
|
||
booleanParam(
|
||
name: 'SKIP_BOOTSTRAP',
|
||
defaultValue: false,
|
||
description: 'Skip bootstrap phase (S3/DynamoDB already exist)'
|
||
)
|
||
string(
|
||
name: 'PROJECT_NAME',
|
||
defaultValue: 'nvhi-atsila-microservice',
|
||
description: 'Project name for resource naming'
|
||
)
|
||
string(
|
||
name: 'AWS_CREDENTIALS_ID',
|
||
defaultValue: 'aws-ci',
|
||
description: 'AWS credentials stored in Jenkins'
|
||
)
|
||
string(
|
||
name: 'AWS_REGION_ID',
|
||
defaultValue: 'AWS_REGION',
|
||
description: 'AWS region credential stored in Jenkins'
|
||
)
|
||
choice(
|
||
name: 'ENVIRONMENT',
|
||
choices: ['dev', 'staging', 'prod'],
|
||
description: 'Environment to deploy'
|
||
)
|
||
}
|
||
|
||
environment {
|
||
// Terraform configuration
|
||
TF_VERSION = '1.5.7'
|
||
TF_IN_AUTOMATION = 'true'
|
||
TF_INPUT = 'false'
|
||
TF_CLI_ARGS = '-no-color'
|
||
|
||
// Working directory
|
||
TF_WORKING_DIR = 'infrastructure/foundation'
|
||
|
||
// Project configuration (AWS_REGION will be injected from Jenkins credentials)
|
||
PROJECT_NAME = "${params.PROJECT_NAME}"
|
||
ENVIRONMENT = "${params.ENVIRONMENT}"
|
||
|
||
// SonarQube configuration
|
||
SONAR_PROJECT_KEY = "${params.PROJECT_NAME}-foundation"
|
||
SONAR_PROJECT_NAME = "${params.PROJECT_NAME} Foundation Layer"
|
||
SONAR_PROJECT_VERSION = "${BUILD_NUMBER}"
|
||
}
|
||
|
||
stages {
|
||
stage('🔍 Checkout & Validation') {
|
||
steps {
|
||
echo "=== Enterprise CI/CD Foundation Layer Pipeline ==="
|
||
echo "Action: ${params.ACTION}"
|
||
echo "Environment: ${params.ENVIRONMENT}"
|
||
echo "Project: ${params.PROJECT_NAME}"
|
||
echo "AWS Credentials: ${params.AWS_CREDENTIALS_ID}"
|
||
echo "AWS Region Credential: ${params.AWS_REGION_ID}"
|
||
echo "Authentication: Jenkins Credential Store (Enterprise Standard)"
|
||
echo "Build: #${BUILD_NUMBER}"
|
||
echo "Working Directory: ${env.TF_WORKING_DIR}"
|
||
|
||
// Clean workspace and checkout latest code
|
||
deleteDir()
|
||
checkout scm
|
||
|
||
// Verify repository structure
|
||
script {
|
||
sh '''
|
||
echo "Repository structure validation:"
|
||
|
||
# Check for required directory
|
||
if [ ! -d "${TF_WORKING_DIR}" ]; then
|
||
echo "❌ Missing foundation directory: ${TF_WORKING_DIR}"
|
||
exit 1
|
||
fi
|
||
|
||
cd "${TF_WORKING_DIR}"
|
||
|
||
# 1️⃣ Core Terraform files (always required)
|
||
for file in main.tf variables.tf outputs.tf versions.tf; do
|
||
if [ ! -f "$file" ]; then
|
||
echo "❌ Missing required file: $file"
|
||
exit 1
|
||
fi
|
||
echo "✅ Found: $file"
|
||
done
|
||
|
||
# 2️⃣ Bootstrap & cleanup scripts (only if ACTION≠plan AND SKIP_BOOTSTRAP=false)
|
||
if [ "${ACTION}" != "plan" ] && [ "${SKIP_BOOTSTRAP}" != "true" ]; then
|
||
for file in bootstrap.bash cleanup.bash; do
|
||
if [ ! -f "$file" ]; then
|
||
echo "❌ Missing required file: $file"
|
||
exit 1
|
||
fi
|
||
echo "✅ Found: $file"
|
||
done
|
||
else
|
||
echo "✅ Skipping bootstrap/cleanup checks (ACTION=${ACTION}, SKIP_BOOTSTRAP=${SKIP_BOOTSTRAP})"
|
||
fi
|
||
|
||
# Make scripts executable (if they aren't already)
|
||
chmod +x bootstrap.bash cleanup.bash || {
|
||
echo "⚠️ Could not make scripts executable, but continuing..."
|
||
}
|
||
|
||
echo "✅ Repository structure validated"
|
||
'''
|
||
} // end script
|
||
} // end steps
|
||
} // end stage
|
||
|
||
stage('🔧 Setup Tools') {
|
||
steps {
|
||
script {
|
||
// Verify Terraform is available
|
||
sh '''
|
||
echo "✅ Checking for Terraform..."
|
||
if command -v terraform > /dev/null 2>&1; then
|
||
echo "❌ Terraform not found. Please install Terraform ${TF_VERSION}"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ Terraform is already installed"
|
||
terraform version
|
||
'''
|
||
|
||
// Verify AWS credentials and permissions via Jenkins credential store
|
||
try {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
sh '''
|
||
echo "AWS CLI version:"
|
||
aws --version || {
|
||
echo "❌ AWS CLI not available. Please install AWS CLI in Jenkins container."
|
||
exit 1
|
||
}
|
||
|
||
echo "Verifying Jenkins stored AWS credentials..."
|
||
echo "AWS Region: ${AWS_REGION}"
|
||
|
||
# Test AWS credentials
|
||
aws sts get-caller-identity || {
|
||
echo "❌ AWS credentials validation failed"
|
||
echo "Check that credential IDs '${AWS_CREDENTIALS_ID}' and '${AWS_REGION_ID}' exist in Jenkins"
|
||
exit 1
|
||
}
|
||
|
||
echo "Testing AWS permissions..."
|
||
aws ec2 describe-vpcs --max-items 1 --region ${AWS_REGION} > /dev/null && echo "✅ EC2 permissions OK" || echo "⚠️ EC2 permissions limited"
|
||
aws s3 ls > /dev/null 2>&1 && echo "✅ S3 permissions OK" || echo "⚠️ S3 permissions limited"
|
||
aws dynamodb list-tables --region ${AWS_REGION} > /dev/null 2>&1 && echo "✅ DynamoDB permissions OK" || echo "⚠️ DynamoDB permissions limited"
|
||
|
||
echo "✅ Jenkins credential store authentication verified"
|
||
'''
|
||
}
|
||
} catch (Exception e) {
|
||
error """
|
||
❌ AWS Credentials Setup Failed: ${e.getMessage()}
|
||
|
||
🔧 Check these in Jenkins:
|
||
1. Manage Jenkins → Manage Credentials → Global
|
||
2. Verify credential exists: '${params.AWS_CREDENTIALS_ID}' (Type: AWS Credentials)
|
||
3. Verify credential exists: '${params.AWS_REGION_ID}' (Type: Secret text)
|
||
4. Ensure AWS CLI is installed in Jenkins container
|
||
|
||
💡 Or run with different credential IDs if yours are named differently.
|
||
"""
|
||
}
|
||
}
|
||
}
|
||
}
|
||
stage('🔍 SonarQube Analysis') {
|
||
when {
|
||
allOf {
|
||
expression { !params.SKIP_SONAR }
|
||
expression { params.ACTION != 'cleanup' }
|
||
}
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
script {
|
||
// Create comprehensive SonarQube configuration
|
||
writeFile file: 'sonar-project.properties', text: """
|
||
sonar.projectKey=${env.SONAR_PROJECT_KEY}
|
||
sonar.projectName=${env.SONAR_PROJECT_NAME}
|
||
sonar.projectVersion=${env.SONAR_PROJECT_VERSION}
|
||
sonar.sources=.
|
||
sonar.sourceEncoding=UTF-8
|
||
|
||
# Terraform-specific configuration
|
||
sonar.terraform.file.suffixes=.tf
|
||
sonar.exclusions=**/*.tfstate,**/*.tfstate.backup,**/.terraform/**,**/*.tfplan
|
||
|
||
# Include scripts in analysis
|
||
sonar.inclusions=**/*.tf,**/*.sh
|
||
|
||
# Quality gate settings
|
||
sonar.qualitygate.wait=true
|
||
|
||
# Coverage and duplications
|
||
sonar.cpd.exclusions=**/*.tf
|
||
|
||
# Custom properties for enterprise analysis
|
||
sonar.tags=terraform,infrastructure,enterprise-cicd
|
||
"""
|
||
|
||
// Run SonarQube analysis
|
||
try {
|
||
withSonarQubeEnv('SonarQube') {
|
||
sh '''
|
||
echo "🔍 Running SonarQube analysis on Terraform infrastructure..."
|
||
|
||
# Check if sonar-scanner is available
|
||
if command -v sonar-scanner &> /dev/null; then
|
||
sonar-scanner
|
||
else
|
||
echo "⚠️ sonar-scanner not found. Attempting to use docker fallback..."
|
||
if command -v docker &> /dev/null; then
|
||
docker run --rm -v "$(pwd):/usr/src" sonarsource/sonar-scanner-cli
|
||
else
|
||
echo "❌ Neither sonar-scanner nor docker available"
|
||
echo "Please install SonarQube Scanner or skip SonarQube analysis"
|
||
exit 1
|
||
fi
|
||
fi
|
||
'''
|
||
}
|
||
} catch (Exception e) {
|
||
echo "❌ SonarQube analysis failed: ${e.getMessage()}"
|
||
echo "💡 Consider running with SKIP_SONAR=true or configure SonarQube properly"
|
||
|
||
if (params.ACTION == 'apply') {
|
||
def proceed = input(
|
||
message: 'SonarQube analysis failed. How do you want to proceed?',
|
||
parameters: [
|
||
choice(
|
||
name: 'DECISION',
|
||
choices: ['Abort', 'Continue without SonarQube'],
|
||
description: 'SonarQube failed - your decision'
|
||
)
|
||
]
|
||
)
|
||
if (proceed == 'Abort') {
|
||
error "Pipeline aborted due to SonarQube failure"
|
||
}
|
||
} else {
|
||
error "SonarQube analysis failed for ${params.ACTION} action"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('🎯 Quality Gate') {
|
||
when {
|
||
allOf {
|
||
expression { !params.SKIP_SONAR }
|
||
expression { params.ACTION != 'cleanup' }
|
||
}
|
||
}
|
||
steps {
|
||
script {
|
||
timeout(time: 5, unit: 'MINUTES') {
|
||
try {
|
||
def qg = waitForQualityGate()
|
||
if (qg.status != 'OK') {
|
||
echo "❌ SonarQube Quality Gate failed: ${qg.status}"
|
||
echo "Quality gate details: ${qg}"
|
||
|
||
if (params.ACTION == 'apply' && !params.AUTO_APPROVE) {
|
||
def proceed = input(
|
||
message: 'SonarQube Quality Gate failed. How do you want to proceed?',
|
||
parameters: [
|
||
choice(
|
||
name: 'DECISION',
|
||
choices: ['Abort', 'Proceed anyway'],
|
||
description: 'Quality gate failed - your decision'
|
||
)
|
||
]
|
||
)
|
||
if (proceed == 'Abort') {
|
||
error "Deployment aborted due to quality gate failure"
|
||
}
|
||
} else if (params.ACTION == 'apply' && params.AUTO_APPROVE) {
|
||
echo "⚠️ Quality gate failed but AUTO_APPROVE is enabled, proceeding..."
|
||
} else {
|
||
error "Quality gate failed and action is ${params.ACTION}"
|
||
}
|
||
} else {
|
||
echo "✅ SonarQube Quality Gate passed"
|
||
}
|
||
} catch (Exception e) {
|
||
echo "❌ Quality Gate check failed: ${e.getMessage()}"
|
||
echo "💡 This might be due to SonarQube server issues or configuration problems"
|
||
|
||
if (params.ACTION == 'apply') {
|
||
def proceed = input(
|
||
message: 'Quality Gate check failed (server error). Proceed anyway?',
|
||
parameters: [
|
||
choice(
|
||
name: 'DECISION',
|
||
choices: ['Abort', 'Proceed without quality gate'],
|
||
description: 'SonarQube server error - your decision'
|
||
)
|
||
]
|
||
)
|
||
if (proceed == 'Abort') {
|
||
error "Pipeline aborted due to quality gate server error"
|
||
}
|
||
} else {
|
||
echo "⚠️ Quality gate check failed for ${params.ACTION}, but continuing..."
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('🚀 Bootstrap Backend') {
|
||
when {
|
||
allOf {
|
||
expression { params.ACTION == 'apply' }
|
||
expression { !params.SKIP_BOOTSTRAP }
|
||
}
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
script {
|
||
echo "=== Bootstrapping Terraform Backend ==="
|
||
|
||
sh '''
|
||
# Set environment variables for bootstrap script
|
||
export PROJECT_NAME="${PROJECT_NAME}"
|
||
export ENVIRONMENT="${ENVIRONMENT}"
|
||
export AWS_REGION="${AWS_REGION}"
|
||
|
||
# Run bootstrap script (uses Jenkins credentials)
|
||
./bootstrap.bash
|
||
|
||
# Verify backend configuration was created
|
||
if [ ! -f backend.tf ]; then
|
||
echo "❌ Bootstrap failed - backend.tf not created"
|
||
exit 1
|
||
fi
|
||
|
||
echo "✅ Backend bootstrap completed"
|
||
echo "Generated backend.tf:"
|
||
cat backend.tf
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('🔄 Terraform Init & Validate') {
|
||
when {
|
||
expression { params.ACTION != 'cleanup' }
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
script {
|
||
sh '''
|
||
echo "=== Terraform Initialization ==="
|
||
|
||
# Create terraform.tfvars if not exists
|
||
if [ ! -f terraform.tfvars ]; then
|
||
echo "Creating terraform.tfvars..."
|
||
cat > terraform.tfvars << EOF
|
||
# Generated by Jenkins Pipeline Build #${BUILD_NUMBER}
|
||
project_name = "${PROJECT_NAME}"
|
||
environment = "${ENVIRONMENT}"
|
||
aws_region = "${AWS_REGION}"
|
||
|
||
# Free tier optimized settings
|
||
enable_private_subnets = false
|
||
enable_vpc_endpoints = false
|
||
enable_nat_gateway = false
|
||
single_nat_gateway = true
|
||
cost_optimization_mode = true
|
||
|
||
# Jenkins-managed tags
|
||
common_tags = {
|
||
Terraform = "true"
|
||
Project = "${PROJECT_NAME}"
|
||
Environment = "${ENVIRONMENT}"
|
||
ManagedBy = "jenkins"
|
||
Pipeline = "foundation-layer"
|
||
BuildNumber = "${BUILD_NUMBER}"
|
||
GitCommit = "${GIT_COMMIT:-unknown}"
|
||
}
|
||
EOF
|
||
fi
|
||
|
||
echo "Current terraform.tfvars:"
|
||
cat terraform.tfvars
|
||
|
||
# Initialize Terraform (uses Jenkins credentials)
|
||
terraform init -upgrade
|
||
|
||
# Validate configuration
|
||
terraform validate
|
||
|
||
# Format check
|
||
terraform fmt -check=true || {
|
||
echo "⚠️ Terraform files need formatting"
|
||
terraform fmt -diff=true
|
||
}
|
||
|
||
echo "✅ Terraform initialized and validated"
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('📊 Terraform Plan') {
|
||
when {
|
||
expression { params.ACTION in ['plan', 'apply'] }
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
script {
|
||
sh '''
|
||
echo "=== Terraform Plan ==="
|
||
|
||
terraform plan \
|
||
-var="project_name=${PROJECT_NAME}" \
|
||
-var="environment=${ENVIRONMENT}" \
|
||
-var="aws_region=${AWS_REGION}" \
|
||
-out=tfplan \
|
||
-detailed-exitcode || PLAN_EXIT_CODE=$?
|
||
|
||
# Handle plan exit codes
|
||
case ${PLAN_EXIT_CODE:-0} in
|
||
0)
|
||
echo "✅ No changes needed - infrastructure is up to date"
|
||
;;
|
||
1)
|
||
echo "❌ Terraform plan failed"
|
||
exit 1
|
||
;;
|
||
2)
|
||
echo "📝 Changes detected - plan saved to tfplan"
|
||
|
||
# Show plan summary
|
||
echo "=== Plan Summary ==="
|
||
terraform show -no-color tfplan | grep -E "(Plan:|No changes|Error:)" || echo "Plan generated successfully"
|
||
;;
|
||
esac
|
||
'''
|
||
|
||
// Archive the plan for audit
|
||
archiveArtifacts artifacts: 'tfplan', allowEmptyArchive: true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('🚦 Deployment Approval') {
|
||
when {
|
||
allOf {
|
||
expression { params.ACTION == 'apply' }
|
||
expression { !params.AUTO_APPROVE }
|
||
}
|
||
}
|
||
steps {
|
||
script {
|
||
def planSummary = ""
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
planSummary = sh(
|
||
script: 'terraform show -no-color tfplan | grep "Plan:" || echo "No plan summary available"',
|
||
returnStdout: true
|
||
).trim()
|
||
}
|
||
|
||
echo "=== Manual Approval Required ==="
|
||
echo "Environment: ${params.ENVIRONMENT}"
|
||
echo "Region: ${params.AWS_REGION}"
|
||
echo "Plan Summary: ${planSummary}"
|
||
|
||
def approvalData = input(
|
||
id: 'ProceedApply',
|
||
message: """
|
||
🔍 Review the Terraform plan output above carefully.
|
||
|
||
Environment: ${params.ENVIRONMENT}
|
||
Region: ${params.AWS_REGION}
|
||
Plan: ${planSummary}
|
||
|
||
Proceed with deployment?
|
||
""",
|
||
parameters: [
|
||
choice(
|
||
name: 'PROCEED',
|
||
choices: ['No', 'Yes, deploy infrastructure'],
|
||
description: 'Deployment decision'
|
||
),
|
||
string(
|
||
name: 'APPROVER',
|
||
defaultValue: env.BUILD_USER ?: 'jenkins-user',
|
||
description: 'Your name for audit trail'
|
||
)
|
||
]
|
||
)
|
||
|
||
if (approvalData.PROCEED != 'Yes, deploy infrastructure') {
|
||
error "Deployment cancelled by ${approvalData.APPROVER}"
|
||
}
|
||
|
||
echo "✅ Deployment approved by: ${approvalData.APPROVER}"
|
||
env.DEPLOYMENT_APPROVER = approvalData.APPROVER
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('🚀 Terraform Apply') {
|
||
when {
|
||
expression { params.ACTION == 'apply' }
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
script {
|
||
echo "=== Terraform Apply ==="
|
||
if (env.DEPLOYMENT_APPROVER) {
|
||
echo "✅ Approved by: ${env.DEPLOYMENT_APPROVER}"
|
||
}
|
||
|
||
sh '''
|
||
terraform apply -auto-approve tfplan
|
||
|
||
echo "=== Deployment Outputs ==="
|
||
terraform output
|
||
|
||
# Save outputs for other stages/jobs
|
||
terraform output -json > terraform-outputs.json
|
||
terraform output > terraform-outputs.txt
|
||
'''
|
||
|
||
// Archive outputs
|
||
archiveArtifacts artifacts: 'terraform-outputs.json,terraform-outputs.txt', allowEmptyArchive: true
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('💥 Terraform Destroy') {
|
||
when {
|
||
expression { params.ACTION == 'destroy' }
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
script {
|
||
def destroyApproval = input(
|
||
id: 'ProceedDestroy',
|
||
message: """
|
||
⚠️ DESTRUCTIVE ACTION WARNING ⚠️
|
||
|
||
This will permanently delete ALL infrastructure in:
|
||
• Environment: ${params.ENVIRONMENT}
|
||
• Project: ${params.PROJECT_NAME}
|
||
|
||
This action CANNOT be undone!
|
||
|
||
Type 'DESTROY' exactly to confirm:
|
||
""",
|
||
parameters: [
|
||
string(
|
||
name: 'CONFIRMATION',
|
||
defaultValue: '',
|
||
description: 'Type DESTROY to confirm deletion'
|
||
),
|
||
string(
|
||
name: 'DESTROYER',
|
||
defaultValue: env.BUILD_USER ?: 'jenkins-user',
|
||
description: 'Your name for audit trail'
|
||
)
|
||
]
|
||
)
|
||
|
||
if (destroyApproval.CONFIRMATION != 'DESTROY') {
|
||
error "Destroy cancelled - confirmation text did not match 'DESTROY'"
|
||
}
|
||
|
||
echo "💀 DESTROY operation confirmed by: ${destroyApproval.DESTROYER}"
|
||
echo "💀 Destroying infrastructure in 10 seconds..."
|
||
echo "💀 Last chance to cancel with Ctrl+C..."
|
||
sleep(10)
|
||
|
||
sh '''
|
||
terraform destroy -auto-approve \
|
||
-var="project_name=${PROJECT_NAME}" \
|
||
-var="environment=${ENVIRONMENT}" \
|
||
-var="aws_region=${AWS_REGION}"
|
||
'''
|
||
|
||
echo "💀 Infrastructure destroyed by: ${destroyApproval.DESTROYER}"
|
||
echo "💀 Next step: Run with ACTION=cleanup to remove bootstrap resources"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('🧹 Cleanup Bootstrap') {
|
||
when {
|
||
expression { params.ACTION == 'cleanup' }
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
script {
|
||
echo "=== Cleanup Bootstrap Resources ==="
|
||
|
||
sh '''
|
||
# Set environment variables for cleanup script
|
||
export PROJECT_NAME="${PROJECT_NAME}"
|
||
export ENVIRONMENT="${ENVIRONMENT}"
|
||
export AWS_REGION="${AWS_REGION}"
|
||
|
||
# Run cleanup script (uses Jenkins credentials)
|
||
./cleanup.bash
|
||
|
||
echo "✅ Bootstrap cleanup completed"
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
stage('📈 Post-Deployment Validation') {
|
||
when {
|
||
expression { params.ACTION == 'apply' }
|
||
}
|
||
steps {
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
withCredentials([
|
||
aws(credentialsId: "${params.AWS_CREDENTIALS_ID}"),
|
||
string(credentialsId: "${params.AWS_REGION_ID}", variable: 'AWS_REGION')
|
||
]) {
|
||
script {
|
||
sh '''
|
||
echo "=== Post-Deployment Validation ==="
|
||
|
||
# Validate VPC
|
||
VPC_ID=$(terraform output -raw vpc_id 2>/dev/null)
|
||
if [ -n "$VPC_ID" ] && [ "$VPC_ID" != "null" ]; then
|
||
echo "✅ VPC created successfully: $VPC_ID"
|
||
|
||
# Get VPC details
|
||
aws ec2 describe-vpcs --vpc-ids $VPC_ID --region ${AWS_REGION} \
|
||
--query 'Vpcs[0].{VpcId:VpcId,State:State,CidrBlock:CidrBlock}' \
|
||
--output table
|
||
|
||
# Count resources
|
||
SUBNET_COUNT=$(aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPC_ID" \
|
||
--query 'length(Subnets)' --output text --region ${AWS_REGION})
|
||
echo "✅ Subnets created: $SUBNET_COUNT"
|
||
|
||
SG_COUNT=$(aws ec2 describe-security-groups --filters "Name=vpc-id,Values=$VPC_ID" \
|
||
--query 'length(SecurityGroups)' --output text --region ${AWS_REGION})
|
||
echo "✅ Security groups: $SG_COUNT"
|
||
else
|
||
echo "❌ VPC validation failed"
|
||
exit 1
|
||
fi
|
||
|
||
# Validate backend resources
|
||
BUCKET_NAME=$(terraform output -raw terraform_state_bucket_name 2>/dev/null)
|
||
TABLE_NAME=$(terraform output -raw terraform_locks_table_name 2>/dev/null)
|
||
|
||
if [ -n "$BUCKET_NAME" ] && [ "$BUCKET_NAME" != "null" ]; then
|
||
echo "✅ S3 backend bucket: $BUCKET_NAME"
|
||
aws s3 ls s3://$BUCKET_NAME --region ${AWS_REGION}
|
||
fi
|
||
|
||
if [ -n "$TABLE_NAME" ] && [ "$TABLE_NAME" != "null" ]; then
|
||
echo "✅ DynamoDB locks table: $TABLE_NAME"
|
||
aws dynamodb describe-table --table-name $TABLE_NAME --region ${AWS_REGION} \
|
||
--query 'Table.{TableName:TableName,Status:TableStatus}' --output table
|
||
fi
|
||
|
||
# Cost analysis
|
||
echo "=== Cost Analysis ==="
|
||
echo "✅ Current configuration: ~$0/month (free tier optimized)"
|
||
echo "✅ No NAT Gateways (saves ~$32/month)"
|
||
echo "✅ No VPC Endpoints (saves ~$14/month)"
|
||
echo "✅ Using public subnets only for cost optimization"
|
||
echo "✅ Using Jenkins credential store (enterprise standard)"
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
post {
|
||
always {
|
||
script {
|
||
echo "=== Pipeline Execution Summary ==="
|
||
echo "🔹 Build: #${BUILD_NUMBER}"
|
||
echo "🔹 Action: ${params.ACTION}"
|
||
echo "🔹 Environment: ${params.ENVIRONMENT}"
|
||
echo "🔹 Duration: ${currentBuild.durationString}"
|
||
echo "🔹 Result: ${currentBuild.result ?: 'SUCCESS'}"
|
||
|
||
// Archive all important artifacts
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
archiveArtifacts artifacts: '*.tf,terraform.tfvars,*.tfplan,terraform-outputs.*,sonar-project.properties,backend.tf', allowEmptyArchive: true
|
||
}
|
||
}
|
||
}
|
||
|
||
success {
|
||
script {
|
||
echo "✅ Foundation Layer pipeline completed successfully!"
|
||
|
||
if (params.ACTION == 'apply') {
|
||
echo """
|
||
🎉 Foundation Layer Deployment Complete!
|
||
|
||
📊 Deployment Details:
|
||
- Environment: ${params.ENVIRONMENT}
|
||
- Region: ${params.AWS_REGION}
|
||
- Project: ${params.PROJECT_NAME}
|
||
- Build: #${BUILD_NUMBER}
|
||
- Duration: ${currentBuild.durationString}"""
|
||
|
||
if (env.DEPLOYMENT_APPROVER) {
|
||
echo "- Approved by: ${env.DEPLOYMENT_APPROVER}"
|
||
}
|
||
|
||
echo """
|
||
🏗️ Infrastructure Created:
|
||
• VPC with multi-AZ public subnets
|
||
• Security groups for ALB and ECS
|
||
• S3 bucket for Terraform state
|
||
• DynamoDB table for state locking
|
||
• Internet Gateway and routing
|
||
|
||
💰 Cost: ~\$0/month (free tier optimized)
|
||
|
||
🚀 Next Steps:
|
||
• Phase 2: Deploy Shared Services (ECR, ALB, IAM)
|
||
• Phase 3: Deploy Application Layer (ECS Fargate)
|
||
• Phase 4: Setup application CI/CD pipeline
|
||
|
||
📋 Outputs: Check archived artifacts for resource details"""
|
||
}
|
||
}
|
||
}
|
||
|
||
failure {
|
||
script {
|
||
echo "❌ Foundation Layer pipeline failed!"
|
||
|
||
// Archive debug information
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
sh '''
|
||
echo "=== Debug Information ===" > debug-info.txt
|
||
echo "Build: ${BUILD_NUMBER}" >> debug-info.txt
|
||
echo "Action: ${ACTION}" >> debug-info.txt
|
||
echo "Environment: ${ENVIRONMENT}" >> debug-info.txt
|
||
echo "Region: ${AWS_REGION}" >> debug-info.txt
|
||
echo "" >> debug-info.txt
|
||
echo "Terraform version:" >> debug-info.txt
|
||
terraform version >> debug-info.txt 2>&1 || echo "Terraform not available" >> debug-info.txt
|
||
echo "" >> debug-info.txt
|
||
echo "AWS CLI version:" >> debug-info.txt
|
||
aws --version >> debug-info.txt 2>&1 || echo "AWS CLI not available" >> debug-info.txt
|
||
echo "" >> debug-info.txt
|
||
echo "Working directory:" >> debug-info.txt
|
||
pwd >> debug-info.txt
|
||
ls -la >> debug-info.txt 2>&1
|
||
echo "" >> debug-info.txt
|
||
echo "Terraform state:" >> debug-info.txt
|
||
terraform state list >> debug-info.txt 2>&1 || echo "No state available" >> debug-info.txt
|
||
'''
|
||
archiveArtifacts artifacts: 'debug-info.txt', allowEmptyArchive: true
|
||
}
|
||
}
|
||
}
|
||
|
||
cleanup {
|
||
// Clean sensitive data but preserve artifacts
|
||
dir("${env.TF_WORKING_DIR}") {
|
||
sh '''
|
||
rm -f .terraform.lock.hcl 2>/dev/null || true
|
||
rm -rf .terraform/ 2>/dev/null || true
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
} |