Jenkins shared library and CDK constructs for AWS infrastructure. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
200 lines
6.5 KiB
Groovy
200 lines
6.5 KiB
Groovy
/**
|
|
* Spicy ECS Cluster Pipeline
|
|
*
|
|
* Deploys an ECS cluster using AWS CDK with:
|
|
* - EC2 Capacity Provider with managed scaling
|
|
* - Optional Fargate capacity providers (FARGATE + FARGATE_SPOT)
|
|
* - Mixed instances policy for Spot support
|
|
* - Instance draining on termination
|
|
* - Optional internal/external ALBs
|
|
*
|
|
* Usage in Jenkinsfile:
|
|
* ```groovy
|
|
* @Library(["spicy-automation@main"]) _
|
|
*
|
|
* spicyECSCluster(
|
|
* jenkinsAwsCredentialsId: "aws-credentials",
|
|
* region: "ca-central-1",
|
|
* stackName: "spicy-ecs-cluster",
|
|
* vpcStackName: "production-vpc",
|
|
* ownerTag: "SpicyTeam",
|
|
* productTag: "spicy",
|
|
* componentTag: "ecs-cluster",
|
|
* environment: "dev",
|
|
* instanceType: "m5a.large",
|
|
* minClusterSize: 2,
|
|
* maxClusterSize: 4,
|
|
* spotEnabled: true,
|
|
* onDemandPercentage: 20,
|
|
* createExternalLoadBalancer: true,
|
|
* createInternalLoadBalancer: true,
|
|
* certificateArn: "arn:aws:acm:ca-central-1:123456789:certificate/xxx",
|
|
* )
|
|
* ```
|
|
*/
|
|
|
|
def call(Map args) {
|
|
args = spicyDefaults(args)
|
|
|
|
timeout(time: 1, unit: "DAYS") {
|
|
timestamps {
|
|
node("docker") {
|
|
properties(args.pipelineProperties)
|
|
ansiColor("xterm") {
|
|
|
|
stage("Checkout") {
|
|
checkout scm
|
|
giteaUtils.setSuccess("checkout")
|
|
}
|
|
|
|
stage("Setup") {
|
|
cdkUtils.install()
|
|
giteaUtils.setSuccess("setup")
|
|
}
|
|
|
|
if (args.onPreDeploy) {
|
|
spicyUtils.stageWithFailure("PreDeploy") {
|
|
args.onPreDeploy.call(args, [:])
|
|
}
|
|
}
|
|
|
|
stage("Deploy ECS Cluster") {
|
|
if (gitUtils.isMain()) {
|
|
try {
|
|
def context = buildEcsClusterContext(args)
|
|
def account = awsUtils.buildAccountConfig(args)
|
|
|
|
// Show diff first
|
|
if (args.showDiff != false) {
|
|
echo "Showing CDK diff..."
|
|
cdkUtils.diff(
|
|
account: account,
|
|
stackName: args.stackName,
|
|
stackType: "ecs-cluster",
|
|
context: context
|
|
)
|
|
}
|
|
|
|
// Manual approval for production
|
|
if (args.environment == 'prod' || args.environment == 'production') {
|
|
manualApproval(
|
|
message: "Deploy ECS cluster ${args.stackName} to production?",
|
|
submitter: args.approvers ?: ''
|
|
)
|
|
}
|
|
|
|
// Deploy the stack
|
|
echo "Deploying ECS cluster stack: ${args.stackName}"
|
|
cdkUtils.deploy(
|
|
account: account,
|
|
stackName: args.stackName,
|
|
stackType: "ecs-cluster",
|
|
context: context
|
|
)
|
|
|
|
giteaUtils.setSuccess("deploy")
|
|
|
|
if (args.onPostDeploy) {
|
|
spicyUtils.stageWithFailure("PostDeploy") {
|
|
args.onPostDeploy.call(args, [
|
|
stackName: args.stackName
|
|
])
|
|
}
|
|
}
|
|
|
|
} catch (err) {
|
|
giteaUtils.setFailed("deploy")
|
|
throw err
|
|
}
|
|
} else {
|
|
echo "Skipping deployment - not on main branch"
|
|
|
|
// Show diff on non-main branches for PR review
|
|
if (args.showDiffOnPR != false) {
|
|
def context = buildEcsClusterContext(args)
|
|
def account = awsUtils.buildAccountConfig(args)
|
|
|
|
echo "Showing CDK diff for PR review..."
|
|
try {
|
|
cdkUtils.diff(
|
|
account: account,
|
|
stackName: args.stackName,
|
|
stackType: "ecs-cluster",
|
|
context: context
|
|
)
|
|
} catch (err) {
|
|
echo "Diff failed (stack may not exist yet): ${err.message}"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
giteaUtils.setSuccess("pipeline")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Build CDK context map from pipeline arguments
|
|
*/
|
|
def buildEcsClusterContext(Map args) {
|
|
def context = [:]
|
|
|
|
// Required: VPC stack name (all VPC details imported from VPC stack exports)
|
|
context.vpcStackName = args.vpcStackName
|
|
if (!args.numberOfAzs) {
|
|
error("numberOfAzs is required for ECS cluster; pass the same value used for the VPC stack (2-4).")
|
|
}
|
|
context.numberOfAzs = args.numberOfAzs.toString()
|
|
|
|
// Required tags
|
|
context.ownerTag = args.ownerTag
|
|
context.productTag = args.productTag
|
|
context.componentTag = args.componentTag ?: 'ecs-cluster'
|
|
context.environment = args.environment ?: 'dev'
|
|
context.build = args.build ?: gitUtils.getShortSHA()
|
|
|
|
// Instance configuration
|
|
if (args.instanceType) context.instanceType = args.instanceType
|
|
if (args.additionalInstanceTypes) context.additionalInstanceTypes = args.additionalInstanceTypes
|
|
if (args.keyName) context.keyName = args.keyName
|
|
if (args.ebsVolumeSize) context.ebsVolumeSize = args.ebsVolumeSize.toString()
|
|
|
|
// Container Insights
|
|
if (args.containerInsights != null) context.containerInsights = args.containerInsights.toString()
|
|
|
|
// Scaling configuration
|
|
if (args.minClusterSize) context.minClusterSize = args.minClusterSize.toString()
|
|
if (args.maxClusterSize) context.maxClusterSize = args.maxClusterSize.toString()
|
|
if (args.targetCapacityPercent) context.targetCapacityPercent = args.targetCapacityPercent.toString()
|
|
|
|
// Spot configuration
|
|
if (args.spotEnabled != null) context.spotEnabled = args.spotEnabled.toString()
|
|
if (args.onDemandPercentage != null) context.onDemandPercentage = args.onDemandPercentage.toString()
|
|
if (args.spotAllocationStrategy) context.spotAllocationStrategy = args.spotAllocationStrategy
|
|
|
|
// Load balancer configuration
|
|
if (args.createExternalLoadBalancer != null) {
|
|
context.createExternalLoadBalancer = args.createExternalLoadBalancer.toString()
|
|
}
|
|
if (args.createInternalLoadBalancer != null) {
|
|
context.createInternalLoadBalancer = args.createInternalLoadBalancer.toString()
|
|
}
|
|
if (args.certificateArn) context.certificateArn = args.certificateArn
|
|
|
|
// Fargate configuration (enables both FARGATE and FARGATE_SPOT capacity providers)
|
|
if (args.enableFargate != null) context.enableFargate = args.enableFargate.toString()
|
|
|
|
// Timeouts
|
|
if (args.drainingTimeout) context.drainingTimeout = args.drainingTimeout.toString()
|
|
if (args.maxInstanceLifetime) context.maxInstanceLifetime = args.maxInstanceLifetime.toString()
|
|
|
|
return context
|
|
}
|
|
|
|
return this
|
|
|