diff --git a/Jenkinsfile b/Jenkinsfile index 3c1e927..ab38168 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,6 +12,11 @@ pipeline { defaultValue: false, description: 'Skip SonarQube quality gates (use with caution)' ) + booleanParam( + name: 'DESTROY_INFRASTRUCTURE', + defaultValue: false, + description: 'Destroy all infrastructure (use with extreme caution)' + ) } environment { @@ -111,6 +116,14 @@ pipeline { steps { checkout scm script { + // Check for infrastructure destruction first + if (params.DESTROY_INFRASTRUCTURE) { + env.DEPLOYMENT_TYPE = "DESTROY" + currentBuild.displayName = "DESTROY-${BUILD_NUMBER}" + echo "🚨 DESTROY MODE: Infrastructure destruction requested" + return + } + def infrastructureFiles = sh( script: ''' if git rev-parse HEAD~1 >/dev/null 2>&1; then @@ -130,33 +143,17 @@ pipeline { echo "✅ Deployment type set to: INFRASTRUCTURE (forced)" } else if (infrastructureFiles == "initial") { env.DEPLOYMENT_TYPE = "INFRASTRUCTURE" + currentBuild.displayName = "INFRASTRUCTURE-INITIAL-${BUILD_NUMBER}" echo "✅ First run detected. Deploying infrastructure." } else if (infrastructureFiles != "none") { env.DEPLOYMENT_TYPE = "INFRASTRUCTURE" + currentBuild.displayName = "INFRASTRUCTURE-CHANGED-${BUILD_NUMBER}" echo "🚨 SECURITY NOTICE: Infrastructure changes detected - elevated permissions required" echo " Changed files: ${infrastructureFiles}" } else { - // Check if infrastructure actually exists in AWS - def clusterExists = false - try { - withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: env.AWS_CRED_ID]]) { - def clusterCheck = sh( - script: "aws ecs describe-clusters --clusters ${TF_VAR_cluster_name} --region ${AWS_REGION} --query 'clusters[0].status' --output text 2>/dev/null || echo 'NOTFOUND'", - returnStdout: true - ).trim() - clusterExists = (clusterCheck == "ACTIVE") - } - } catch (Exception e) { - echo "⚠️ Could not check cluster status: ${e.getMessage()}" - } - - if (!clusterExists) { - env.DEPLOYMENT_TYPE = "INFRASTRUCTURE" - echo "🚨 CLEAN AWS DETECTED: No existing infrastructure found - deploying from scratch" - } else { - env.DEPLOYMENT_TYPE = "APPLICATION" - echo "✅ SECURITY: Application-only deployment - using restricted permissions" - } + env.DEPLOYMENT_TYPE = "APPLICATION" + currentBuild.displayName = "APPLICATION-${BUILD_NUMBER}" + echo "✅ SECURITY: Application-only deployment - using restricted permissions" } def gitCommit = sh(script: 'git rev-parse HEAD', returnStdout: true).trim() @@ -239,6 +236,9 @@ pipeline { } stage('Secure Container Build & Registry') { + when { + not { expression { env.DEPLOYMENT_TYPE == "DESTROY" } } + } steps { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: env.AWS_CRED_ID]]) { script { @@ -276,6 +276,9 @@ pipeline { } stage('Infrastructure Readiness Check') { + when { + not { expression { env.DEPLOYMENT_TYPE == "DESTROY" } } + } steps { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: env.AWS_CRED_ID]]) { script { @@ -301,13 +304,14 @@ pipeline { echo "🔍 Container Instances: ${instanceCount}" if (serviceExists == "false" || instanceCount == "0" || instanceCount == "null") { - echo "🚨 SECURITY NOTICE: Infrastructure not ready - forcing deployment" + echo "🚨 SECURITY NOTICE: Infrastructure not ready - forcing infrastructure deployment" env.DEPLOYMENT_TYPE = "INFRASTRUCTURE" + currentBuild.displayName = "INFRASTRUCTURE-AUTO-${BUILD_NUMBER}" currentBuild.description = "INFRASTRUCTURE (auto-detected) | ${env.IMAGE_TAG}" - echo "✅ Changed deployment type to: ${env.DEPLOYMENT_TYPE}" + echo "✅ Changed deployment type to: INFRASTRUCTURE" } } else { - echo "✅ Infrastructure deployment already forced - skipping readiness check" + echo "✅ Infrastructure deployment already scheduled - skipping readiness check" } echo "📋 SECURITY: Infrastructure readiness assessment completed" @@ -317,6 +321,50 @@ pipeline { } } + stage('Destroy Infrastructure') { + when { + expression { env.DEPLOYMENT_TYPE == "DESTROY" } + } + steps { + withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: env.AWS_CRED_ID]]) { + dir('terraform') { + script { + echo "🚨 DESTRUCTION: Destroying infrastructure..." + sh """ + echo "🔄 Initializing Terraform with remote backend..." + terraform init \\ + -backend-config="bucket=${TF_BACKEND_BUCKET}" \\ + -backend-config="key=${TF_BACKEND_PREFIX}" \\ + -backend-config="region=${AWS_REGION}" \\ + -backend-config="dynamodb_table=${TF_DDB_TABLE}" + + echo "🔄 Planning infrastructure destruction..." + terraform plan -destroy \\ + -var="cluster_name=${TF_VAR_cluster_name}" \\ + -var="vpc_cidr=${TF_VAR_vpc_cidr}" \\ + -var="public_subnets=${TF_VAR_public_subnets}" \\ + -var="instance_type=${TF_VAR_instance_type}" \\ + -var="key_pair_name=${TF_VAR_key_pair_name}" \\ + -var="jenkins_ip_cidr=${TF_VAR_jenkins_ip_cidr}" \\ + -var="aws_region=${TF_VAR_aws_region}" + + echo "🔄 Destroying infrastructure..." + terraform destroy -auto-approve \\ + -var="cluster_name=${TF_VAR_cluster_name}" \\ + -var="vpc_cidr=${TF_VAR_vpc_cidr}" \\ + -var="public_subnets=${TF_VAR_public_subnets}" \\ + -var="instance_type=${TF_VAR_instance_type}" \\ + -var="key_pair_name=${TF_VAR_key_pair_name}" \\ + -var="jenkins_ip_cidr=${TF_VAR_jenkins_ip_cidr}" \\ + -var="aws_region=${TF_VAR_aws_region}" + """ + echo "✅ Infrastructure destruction completed" + } + } + } + } + } + stage('Deploy Infrastructure') { when { anyOf { @@ -334,35 +382,44 @@ pipeline { echo "🏗️ ARCHITECTURE: Deploying ECS Cluster with SSM access (secure, keyless)" echo "🔐 In production: This would require infrastructure-admin role" echo "🚀 Attempting infrastructure deployment..." - sh """ - echo "🔄 Initializing Terraform with remote backend..." - terraform init \\ - -backend-config="bucket=${TF_BACKEND_BUCKET}" \\ - -backend-config="key=${TF_BACKEND_PREFIX}" \\ - -backend-config="region=${AWS_REGION}" \\ - -backend-config="dynamodb_table=${TF_DDB_TABLE}" - - echo "🔄 Planning infrastructure changes..." - terraform plan \\ - -var="cluster_name=${TF_VAR_cluster_name}" \\ - -var="vpc_cidr=${TF_VAR_vpc_cidr}" \\ - -var="public_subnets=${TF_VAR_public_subnets}" \\ - -var="instance_type=${TF_VAR_instance_type}" \\ - -var="key_pair_name=${TF_VAR_key_pair_name}" \\ - -var="jenkins_ip_cidr=${TF_VAR_jenkins_ip_cidr}" \\ - -var="aws_region=${TF_VAR_aws_region}" - - echo "🔄 Applying infrastructure changes..." - terraform apply -auto-approve \\ - -var="cluster_name=${TF_VAR_cluster_name}" \\ - -var="vpc_cidr=${TF_VAR_vpc_cidr}" \\ - -var="public_subnets=${TF_VAR_public_subnets}" \\ - -var="instance_type=${TF_VAR_instance_type}" \\ - -var="key_pair_name=${TF_VAR_key_pair_name}" \\ - -var="jenkins_ip_cidr=${TF_VAR_jenkins_ip_cidr}" \\ - -var="aws_region=${TF_VAR_aws_region}" - """ - echo "✅ SECURITY: Infrastructure deployment completed with compliance verification" + + // Add error handling for Terraform operations + try { + sh """ + echo "🔄 Initializing Terraform with remote backend..." + terraform init \\ + -backend-config="bucket=${TF_BACKEND_BUCKET}" \\ + -backend-config="key=${TF_BACKEND_PREFIX}" \\ + -backend-config="region=${AWS_REGION}" \\ + -backend-config="dynamodb_table=${TF_DDB_TABLE}" + + echo "🔄 Planning infrastructure changes..." + terraform plan \\ + -var="cluster_name=${TF_VAR_cluster_name}" \\ + -var="vpc_cidr=${TF_VAR_vpc_cidr}" \\ + -var="public_subnets=${TF_VAR_public_subnets}" \\ + -var="instance_type=${TF_VAR_instance_type}" \\ + -var="key_pair_name=${TF_VAR_key_pair_name}" \\ + -var="jenkins_ip_cidr=${TF_VAR_jenkins_ip_cidr}" \\ + -var="aws_region=${TF_VAR_aws_region}" + + echo "🔄 Applying infrastructure changes..." + terraform apply -auto-approve \\ + -var="cluster_name=${TF_VAR_cluster_name}" \\ + -var="vpc_cidr=${TF_VAR_vpc_cidr}" \\ + -var="public_subnets=${TF_VAR_public_subnets}" \\ + -var="instance_type=${TF_VAR_instance_type}" \\ + -var="key_pair_name=${TF_VAR_key_pair_name}" \\ + -var="jenkins_ip_cidr=${TF_VAR_jenkins_ip_cidr}" \\ + -var="aws_region=${TF_VAR_aws_region}" + """ + echo "✅ SECURITY: Infrastructure deployment completed successfully" + } catch (Exception e) { + echo "❌ Infrastructure deployment failed: ${e.getMessage()}" + echo "📋 Checking current Terraform state..." + sh "terraform show || echo 'No state found'" + throw e + } } } } @@ -417,6 +474,9 @@ pipeline { } stage('Configure & Deploy Application') { + when { + not { expression { env.DEPLOYMENT_TYPE == "DESTROY" } } + } parallel { stage('Configure EC2 Instance via SSM') { when { @@ -609,6 +669,9 @@ pipeline { } stage('Verify Deployment') { + when { + not { expression { env.DEPLOYMENT_TYPE == "DESTROY" } } + } steps { withCredentials([[$class: 'AmazonWebServicesCredentialsBinding', credentialsId: env.AWS_CRED_ID]]) { script { @@ -706,22 +769,26 @@ pipeline { success { script { - echo "🎉 SUCCESS: Deployment completed successfully!" - echo " Version ${IMAGE_TAG} deployed to ECS cluster ${TF_VAR_cluster_name}" - - // Get application URL for success message - def appUrl = "" - try { - appUrl = sh( - script: "cd terraform && terraform output -raw ecs_instance_public_ip 2>/dev/null || echo 'unknown'", - returnStdout: true - ).trim() - if (appUrl != "unknown" && appUrl != "") { - echo "🌐 Application available at: http://${appUrl}:8080" - echo "🏥 Health check: http://${appUrl}:8080/health" + if (env.DEPLOYMENT_TYPE == "DESTROY") { + echo "🎉 SUCCESS: Infrastructure destroyed successfully!" + } else { + echo "🎉 SUCCESS: Deployment completed successfully!" + echo " Version ${IMAGE_TAG} deployed to ECS cluster ${TF_VAR_cluster_name}" + + // Get application URL for success message + def appUrl = "" + try { + appUrl = sh( + script: "cd terraform && terraform output -raw ecs_instance_public_ip 2>/dev/null || echo 'unknown'", + returnStdout: true + ).trim() + if (appUrl != "unknown" && appUrl != "") { + echo "🌐 Application available at: http://${appUrl}:8080" + echo "🏥 Health check: http://${appUrl}:8080/health" + } + } catch (Exception e) { + echo "⚠️ Could not determine application URL" } - } catch (Exception e) { - echo "⚠️ Could not determine application URL" } } }