Skip to content

Commit

Permalink
Added step templates to support AMI Blue/Green deployments (#1581)
Browse files Browse the repository at this point in the history
* Added step templates to support AMI Blue/Green deployments

* Added parameter help text

* Added another step

* Deal with a null value

* Show status in logs
  • Loading branch information
mcasperson authored Jan 10, 2025
1 parent a3102d5 commit 93e93bc
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 0 deletions.
80 changes: 80 additions & 0 deletions step-templates/aws-find-blue-green-asg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
{
"Id": "6b72995e-500c-4b4b-9121-88f3a988ec71",
"Name": "AWS - Find Blue-Green ASG",
"Description": "Return the name of the online and offline blue and green Auto Scaling Groups",
"ActionType": "Octopus.AwsRunScript",
"Version": 1,
"CommunityActionTemplateId": null,
"Packages": [],
"GitDependencies": [],
"Properties": {
"OctopusUseBundledTooling": "False",
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "Bash",
"Octopus.Action.Aws.AssumeRole": "False",
"Octopus.Action.AwsAccount.UseInstanceRole": "False",
"Octopus.Action.AwsAccount.Variable": "#{AWSBlueGreen.AWS.Account}",
"Octopus.Action.Script.ScriptBody": "#!/bin/bash\n\nINACTIVECOLOR=${1:-'#{AWSBlueGreen.InactiveColor | Trim}'}\nGREENASG=${2:-'#{AWSBlueGreen.AWS.GreenASG | Trim}'}\nBLUEASG=${3:-'#{AWSBlueGreen.AWS.BlueASG | Trim}'}\n\nechoerror() { echo \"$@\" 1>&2; }\n\nif [[ -z \"${INACTIVECOLOR}\" ]]\nthen\n echoerror \"Please provide the color of the inactive Auto Scaling group (Green or Blue) as the first argument\"\n exit 1\nfi\n\nif [[ -z \"${GREENASG}\" ]]\nthen\n echoerror \"Please provide the name of the Green Auto Scaling group as the second argument\"\n exit 1\nfi\n\nif [[ -z \"${BLUEASG}\" ]]\nthen\n echoerror \"Please provide the name of the Blue Auto Scaling group as the third argument\"\n exit 1\nfi\n\nif [[ \"${INACTIVECOLOR^^}\" == \"GREEN\" ]]\nthen\n set_octopusvariable \"ActiveGroup\" \"${BLUEASG}\"\n set_octopusvariable \"InactiveGroup\" \"${GREENASG}\"\n echo \"Active group is Blue (${BLUEASG}), inactive group is Green (${GREENASG})\"\nelse\n set_octopusvariable \"ActiveGroup\" \"${GREENASG}\"\n set_octopusvariable \"InactiveGroup\" \"${BLUEASG}\"\n echo \"Active group is Green (${GREENASG}), inactive group is Blue (${BLUEASG})\"\nfi",
"Octopus.Action.Aws.Region": "#{AWSBlueGreen.AWS.Region}"
},
"Parameters": [
{
"Id": "f8522014-f1ba-4e4a-a06d-59ebdba6f276",
"Name": "AWSBlueGreen.InactiveColor",
"Label": "Inactive Color",
"HelpText": "The color of the inactive group (Green or Blue). This value is usually found from the \"AWS - Find Blue-Green Target Group\" step.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "8753e6ed-0ae6-4a4c-ae5b-155139037633",
"Name": "AWSBlueGreen.AWS.GreenASG",
"Label": "Green ASG Name",
"HelpText": "The name of the green auto scaler group. See https://docs.aws.amazon.com/autoscaling/ec2/userguide/auto-scaling-groups.html for more details.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "bf9187f5-62e9-4ad9-b53f-459466b84994",
"Name": "AWSBlueGreen.AWS.BlueASG",
"Label": "Blue ASG Name",
"HelpText": "The name of the blue auto scaler group. See https://docs.aws.amazon.com/autoscaling/ec2/userguide/auto-scaling-groups.html for more details.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "eb02bbf2-e05a-4469-9359-c77d77d87dd2",
"Name": "AWSBlueGreen.AWS.Region",
"Label": "Region",
"HelpText": "The AWS region. See https://aws.amazon.com/about-aws/global-infrastructure/regions_az/ for more information.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "99a74afc-ee67-4295-8281-3bb1c6e83d06",
"Name": "AWSBlueGreen.AWS.Account",
"Label": "Account",
"HelpText": null,
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "AmazonWebServicesAccount"
}
}
],
"StepPackageId": "Octopus.AwsRunScript",
"$Meta": {
"ExportedAt": "2025-01-10T03:42:14.665Z",
"OctopusVersion": "2025.1.5319",
"Type": "ActionTemplate"
},
"LastModifiedBy": "mcasperson",
"Category": "aws"
}
90 changes: 90 additions & 0 deletions step-templates/aws-find-blue-green-target-group.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"Id": "2f5f8b7b-5deb-45a9-966b-bf52c6e7976c",
"Name": "AWS - Find Blue-Green Target Group",
"Description": "Find the online and offline target groups for a blue-green deployment",
"ActionType": "Octopus.AwsRunScript",
"Version": 1,
"CommunityActionTemplateId": null,
"Packages": [],
"GitDependencies": [],
"Properties": {
"OctopusUseBundledTooling": "False",
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "Bash",
"Octopus.Action.Aws.AssumeRole": "False",
"Octopus.Action.AwsAccount.UseInstanceRole": "False",
"Octopus.Action.AwsAccount.Variable": "#{AWSBlueGreen.AWS.Account}",
"Octopus.Action.Aws.Region": "#{AWSBlueGreen.AWS.Region}",
"Octopus.Action.Script.ScriptBody": "#!/bin/bash\n\nLISTENER=${1:-'#{AWSBlueGreen.AWS.ListenerARN | Trim}'}\nRULE=${2:-'#{AWSBlueGreen.AWS.RuleArn | Trim}'}\nGREENTARGETGROUP=${3:-'#{AWSBlueGreen.AWS.GreenTargetGroup | Trim}'}\nBLUETARGETGROUP=${4:-'#{AWSBlueGreen.AWS.BlueTargetGroup | Trim}'}\n\nechoerror() { echo \"$@\" 1>&2; }\n\nif ! command -v \"aws\" &> /dev/null; then\n echoerror \"You must have the AWS CLI installed for this step. Consider using a Container Image - https://octopus.com/docs/projects/steps/execution-containers-for-workers#how-to-use-execution-containers-for-workers\"\n exit 1\nfi\n\nif ! command -v \"jq\" &> /dev/null; then\n echoerror \"You must have jq installed for this step. Consider using a Container Image - https://octopus.com/docs/projects/steps/execution-containers-for-workers#how-to-use-execution-containers-for-workers\"\n exit 1\nfi\n\n# Validate the arguments\n\nif [[ -z \"${LISTENER}\" ]]; then\n echoerror \"Please provide the ARN of the listener as the first argument\"\n exit 1\nfi\n\nif [[ -z \"${RULE}\" ]]; then\n echoerror \"Please provide the ARN of the listener rule as the second argument\"\n exit 1\nfi\n\nif [[ -z \"${GREENTARGETGROUP}\" ]]; then\n echoerror \"Please provide the ARN of the green target group as the third argument\"\n exit 1\nfi\n\nif [[ -z \"${BLUETARGETGROUP}\" ]]; then\n echoerror \"Please provide the ARN of the blue target group as the fourth argument\"\n exit 1\nfi\n\n# Get the JSON representation of the listener rules\n\nRULES=$(aws elbv2 describe-rules \\\n --listener-arn \"${LISTENER}\" \\\n --output json)\n\nwrite_verbose \"${RULES}\"\n\n# Find the weight assigned to each of the target groups.\n\nGREENWEIGHT=$(jq -r \".Rules[] | select(.RuleArn == \\\"${RULE}\\\") | .Actions[] | select(.Type == \\\"forward\\\") | .ForwardConfig | .TargetGroups[] | select(.TargetGroupArn == \\\"${GREENTARGETGROUP}\\\") | .Weight\" <<< \"${RULES}\")\nBLUEWEIGHT=$(jq -r \".Rules[] | select(.RuleArn == \\\"${RULE}\\\") | .Actions[] | select(.Type == \\\"forward\\\") | .ForwardConfig | .TargetGroups[] | select(.TargetGroupArn == \\\"${BLUETARGETGROUP}\\\") | .Weight\" <<< \"${RULES}\")\n\n# Validation that we found the green and blue target groups.\n\nif [[ -z \"${GREENWEIGHT}\" ]]; then\n echoerror \"Failed to find the target group ${GREENTARGETGROUP} in the listener rule ${RULE}\"\n echoerror \"Double check that the target group exists and has been associated with the load balancer\"\n exit 1\nfi\n\nif [[ -z \"${BLUEWEIGHT}\" ]]; then\n echoerror \"Failed to find the target group ${BLUETARGETGROUP} in the listener rule ${RULE}\"\n echoerror \"Double check that the target group exists and has been associated with the load balancer\"\n exit 1\nfi\n\necho \"Green weight: ${GREENWEIGHT}\"\necho \"Blue weight: ${BLUEWEIGHT}\"\n\n# Set the output variables identifying which target group is active and which is inactive.\n# Note that we assume the target groups are either active or inactive (i.e. all traffic and no traffic).\n# Load balancers support more complex routing rules, but we assume a simple blue-green deployment.\n# If the green target group has traffic, it is considered active, and the blue target group is considered inactive.\n# If the green target group has no traffic, it is considered inactive, and the blue target group is considered active.\n\nif [ \"${GREENWEIGHT}\" != \"0\" ]; then\n echo \"Green target group is active, blue target group is inactive\"\n set_octopusvariable \"ActiveGroupArn\" \"${GREENTARGETGROUP}\"\n set_octopusvariable \"ActiveGroupColor\" \"Green\"\n set_octopusvariable \"InactiveGroupArn\" \"${BLUETARGETGROUP}\"\n set_octopusvariable \"InactiveGroupColor\" \"Blue\"\nelse\n echo \"Blue target group is active, green target group is inactive\"\n set_octopusvariable \"ActiveGroupArn\" \"${BLUETARGETGROUP}\"\n set_octopusvariable \"ActiveGroupColor\" \"Blue\"\n set_octopusvariable \"InactiveGroupArn\" \"${GREENTARGETGROUP}\"\n set_octopusvariable \"InactiveGroupColor\" \"Green\"\nfi"
},
"Parameters": [
{
"Id": "29cdfb7d-47fa-4c8a-837b-c58bb0d90c26",
"Name": "AWSBlueGreen.AWS.Region",
"Label": "Region",
"HelpText": "The AWS region. See https://aws.amazon.com/about-aws/global-infrastructure/regions_az/ for more information.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "58a1ebcd-fd13-48e2-b8b9-fdfe4df8c35e",
"Name": "AWSBlueGreen.AWS.Account",
"Label": "Account",
"HelpText": null,
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "AmazonWebServicesAccount"
}
},
{
"Id": "80642a7b-ef3e-4db4-b969-d0148a1baa90",
"Name": "AWSBlueGreen.AWS.ListenerARN",
"Label": "Listener ARN",
"HelpText": null,
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "2cf29ab4-61a1-4a80-942f-6f1dd035f634",
"Name": "AWSBlueGreen.AWS.BlueTargetGroup",
"Label": "Blue Target Group ARN",
"HelpText": "The ARN of the blue target group. See https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html for more details.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "b7d105c6-1640-48c4-9f01-ad9ece8d3588",
"Name": "AWSBlueGreen.AWS.GreenTargetGroup",
"Label": "Green Target Group ARN",
"HelpText": "The ARN of the green target group. See https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-target-groups.html for more details.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "ff055e9f-f223-453a-9a1f-a0238e6cdfd6",
"Name": "AWSBlueGreen.AWS.RuleArn",
"Label": "Rule ARN",
"HelpText": "The ARN of the listener rule to update. See https://docs.aws.amazon.com/elasticloadbalancing/latest/application/listener-update-rules.html for more information.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
}
],
"StepPackageId": "Octopus.AwsRunScript",
"$Meta": {
"ExportedAt": "2025-01-10T03:41:11.780Z",
"OctopusVersion": "2025.1.5319",
"Type": "ActionTemplate"
},
"LastModifiedBy": "mcasperson",
"Category": "aws"
}
61 changes: 61 additions & 0 deletions step-templates/aws-initiate-instance-refresh.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"Id": "150c46d1-f33f-493b-a8c6-f5bd22f540f3",
"Name": "AWS - Initiate Instance Refresh",
"Description": "Initiates an instance refresh for an Auto Scaling group and waits for it to complete.",
"ActionType": "Octopus.AwsRunScript",
"Version": 1,
"CommunityActionTemplateId": null,
"Packages": [],
"GitDependencies": [],
"Properties": {
"OctopusUseBundledTooling": "False",
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "Bash",
"Octopus.Action.Aws.AssumeRole": "False",
"Octopus.Action.AwsAccount.UseInstanceRole": "False",
"Octopus.Action.RunOnServer": "true",
"Octopus.Action.AwsAccount.Variable": "#{AWSBlueGreen.AWS.Account}",
"Octopus.Action.Aws.Region": "#{AWSBlueGreen.AWS.Region}",
"Octopus.Action.Script.ScriptBody": "#!/bin/bash\n\nASG=${1:-'#{AWSBlueGreen.AWS.ASG | Trim}'}\n\nechoerror() { echo \"$@\" 1>&2; }\n\nif ! command -v \"aws\" &> /dev/null; then\n echoerror \"You must have the AWS CLI installed for this step. Consider using a Container Image - https://octopus.com/docs/projects/steps/execution-containers-for-workers#how-to-use-execution-containers-for-workers\"\n exit 1\nfi\n\nif ! command -v \"jq\" &> /dev/null; then\n echoerror \"You must have jq installed for this step. Consider using a Container Image - https://octopus.com/docs/projects/steps/execution-containers-for-workers#how-to-use-execution-containers-for-workers\"\n exit 1\nfi\n\nif [[ -z \"${ASG}\" ]]; then\n echoerror \"Please provide the name of the Auto Scaling group as the first argument\"\n exit 1\nfi\n\nfor i in {1..30}; do\n EXISTINGREFRESHES=$(aws autoscaling describe-instance-refreshes --auto-scaling-group-name \"${ASG}\")\n NOTSUCCESSFUL=$(jq '.InstanceRefreshes[] | select(.Status == \"Pending\" or .Status == \"InProgress\" or .Status == \"Cancelling\" or .Status == \"RollbackInProgress\" or .Status == \"Baking\")' <<< \"${EXISTINGREFRESHES}\")\n if [[ -z \"${NOTSUCCESSFUL}\" ]];\n then\n break\n fi\n echo \"Waiting for existing Auto Scaling group ${ASG} refresh to complete...\"\n sleep 12\ndone\n\nREFRESH=$(aws autoscaling start-instance-refresh --auto-scaling-group-name \"${ASG}\")\n\nif [[ $? -ne 0 ]];\nthen\n echoerror \"Failed to start instance refresh for Auto Scaling group ${ASG}\"\n exit 1\nfi\n\nREFRESHTOKEN=$(jq -r '.InstanceRefreshId' <<< \"${REFRESH}\")\n\necho \"Refreshing instances in Auto Scaling group ${ASG}...\"\n\nwrite_verbose \"${REFRESH}\"\n\n# Wait for all instances to be healthy\nfor i in {1..30}; do\n REFRESHSTATUS=$(aws autoscaling describe-instance-refreshes --auto-scaling-group-name \"${ASG}\" --instance-refresh-ids \"${REFRESHTOKEN}\")\n STATUS=$(jq -r '.InstanceRefreshes[0].Status' <<< \"${REFRESHSTATUS}\")\n PERCENTCOMPLETE=$(jq -r '.InstanceRefreshes[0].PercentageComplete' <<< \"${REFRESHSTATUS}\")\n\n # Treat a null percentage as 0\n if [[ \"${PERCENTCOMPLETE}\" == \"null\" ]]\n then\n PERCENTCOMPLETE=0\n fi\n\n write_verbose \"${REFRESHSTATUS}\"\n\n if [[ \"${STATUS}\" == \"Successful\" ]]\n then\n echo \"Instance refresh succeeded\"\n break\n elif [[ \"${STATUS}\" == \"Failed\" ]];\n then\n echo \"Instance refresh failed!\"\n exit 1\n fi\n echo \"Waiting for Auto Scaling group ${ASG} refresh to complete (${STATUS} ${PERCENTCOMPLETE}%)...\"\n sleep 12\ndone"
},
"Parameters": [
{
"Id": "2fe001f5-39ee-40d9-b104-24817759ac6f",
"Name": "AWSBlueGreen.AWS.Region",
"Label": "AWS Region",
"HelpText": "The AWS region. See https://aws.amazon.com/about-aws/global-infrastructure/regions_az/ for more information.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "f3630bb5-ab07-46b4-b764-19f1c3b2ec5f",
"Name": "AWSBlueGreen.AWS.Account",
"Label": "Account",
"HelpText": null,
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "AmazonWebServicesAccount"
}
},
{
"Id": "bdfdedee-fdeb-4292-96fd-e41c64b1e523",
"Name": "AWSBlueGreen.AWS.ASG",
"Label": "ASG Name",
"HelpText": "The name of the auto scaler group to refresh. See https://docs.aws.amazon.com/autoscaling/ec2/userguide/auto-scaling-groups.html for more details.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
}
],
"StepPackageId": "Octopus.AwsRunScript",
"$Meta": {
"ExportedAt": "2025-01-10T04:12:22.681Z",
"OctopusVersion": "2025.1.5319",
"Type": "ActionTemplate"
},
"LastModifiedBy": "mcasperson",
"Category": "aws"
}
Loading

0 comments on commit 93e93bc

Please sign in to comment.