Initial commit: Spicy CDK automation framework
Jenkins shared library and CDK constructs for AWS infrastructure. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
199
vars/spicyECSCluster.groovy
Normal file
199
vars/spicyECSCluster.groovy
Normal file
@@ -0,0 +1,199 @@
|
||||
/**
|
||||
* 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
|
||||
|
||||
Reference in New Issue
Block a user