Skip to content

Commit 66e130e

Browse files
authored
Merge pull request #1 from tmshkr/dev
v3
2 parents ea46339 + f2cc762 commit 66e130e

18 files changed

+177
-73
lines changed

.github/workflows/build.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ on:
1010

1111
jobs:
1212
build:
13-
name: Build and save dist file
1413
runs-on: ubuntu-latest
1514
permissions:
1615
contents: write
@@ -21,8 +20,10 @@ jobs:
2120
run: |
2221
npm ci
2322
npm run build
24-
- uses: EndBug/add-and-commit@v9
25-
with:
26-
default_author: github_actions
27-
add: dist
28-
push: true
23+
- name: Save dist file
24+
run: |
25+
git config --global user.name "${{ github.actor }}"
26+
git config --global user.email "${{ github.actor }}@users.noreply.github.com"
27+
git add dist
28+
git commit -m "Update dist file" || true
29+
git push

.github/workflows/latest.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
name: Update latest tag
2+
on:
3+
workflow_dispatch:
4+
release:
5+
types: [published]
6+
7+
jobs:
8+
tag:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: write
12+
steps:
13+
- uses: actions/checkout@v4
14+
- name: Update latest tag
15+
run: |
16+
git push origin :refs/tags/latest
17+
git tag latest
18+
git push origin --tags

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ jobs:
6565
- name: Generate source bundle
6666
run: echo ${{ github.ref_name }} > ENVIRONMENT && zip -r bundle.zip . -x '*.git*'
6767
- name: Deploy
68-
uses: tmshkr/blue-green-beanstalk@v2.1
68+
uses: tmshkr/blue-green-beanstalk@latest
6969
with:
7070
app_name: "test-app"
7171
aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
7272
aws_region: ${{ vars.AWS_REGION }}
7373
aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
7474
blue_env: "my-blue-env"
75+
deploy: true
7576
green_env: "my-green-env"
7677
platform_branch_name: "Docker running on 64bit Amazon Linux 2023"
7778
production_cname: "blue-green-beanstalk-prod" # must be available

action.yml

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,68 @@
11
name: "Blue/Green Beanstalk"
2-
description: "GitHub Action to automate deployment to Blue/Green environments on AWS Elastic Beanstalk."
2+
description: "Deploy to blue/green environments on AWS Elastic Beanstalk."
33
inputs:
44
app_name:
55
description: "Name of the Elastic Beanstalk application. If it doesn't exist, it will be created."
66
required: true
77
aws_access_key_id:
88
description: "AWS Access Key ID. May instead be specified via the AWS_ACCESS_KEY_ID environment variable."
9-
required: false
109
aws_region:
1110
description: "AWS region. May instead be specified via the AWS_REGION environment variable."
12-
required: false
1311
aws_secret_access_key:
1412
description: "AWS Secret Access Key. May instead be specified via the AWS_SECRET_ACCESS_KEY environment variable."
15-
required: false
1613
aws_session_token:
1714
description: "AWS session token for using temporary credentials. May instead be specified via the AWS_SESSION_TOKEN environment variable."
18-
required: false
1915
blue_env:
20-
description: "Name of the Blue environment."
16+
description: "Name of the blue environment."
2117
required: true
2218
deploy:
2319
description: "Whether to deploy the new application version to the target environment."
24-
required: true
25-
default: true
20+
default: false
2621
green_env:
27-
description: "Name of the Green environment."
22+
description: "Name of the green environment."
2823
required: true
2924
option_settings:
3025
description: "Path to a JSON file of option settings to use when creating a new environment. When using custom option settings, the default option settings will be ignored, but the SharedLoadBalancer will still be provided when using the `shared_alb` strategy."
31-
required: false
3226
platform_branch_name:
3327
description: "Name of the platform branch to use. When creating a new environment, it will be launched with the latest version of the specified platform branch. To see the list of available platform branches, run the `aws elasticbeanstalk list-platform-branches` command."
34-
required: true
28+
prep:
29+
description: "When prep is set to true, the action will exit if the target environment is healthy, or create a new environment if it is not healthy. This is useful for preparing the target environment, e.g. with the sample application, before deploying a new application version in a later step."
30+
default: false
3531
ports:
3632
description: "Comma-separated list of ALB listener ports for use with the `shared_alb` strategy. When using the action to create an ALB, HTTP listeners will be created for each port. To enable HTTPS, port 443 must be configured on the ALB with ACM. When using an existing ALB, the action will update each specified port's default listener rule to point to the target environment if `promote` is set to true."
37-
required: true
3833
default: "80"
3934
production_cname:
4035
description: "CNAME prefix for the domain that serves production traffic. Only required for the `swap_cnames` strategy."
41-
required: false
4236
promote:
4337
description: "Whether to promote the target environment to production. When using the `swap_cnames` strategy, the action will swap the CNAMEs of the blue and green environments to redirect production traffic to the target environment. When using the `shared_alb` strategy, the action will update the shared Application Load Balancer's default listener rule to point to the target environment, replacing the other environment."
44-
required: true
45-
default: true
38+
default: false
4639
staging_cname:
4740
description: "CNAME prefix for the staging environment when using the `swap_cnames` strategy."
48-
required: false
4941
source_bundle:
5042
description: "Path to the source bundle to deploy. If not specified, the sample application will be used."
51-
required: false
5243
strategy:
5344
description: "Deployment strategy to use: `swap_cnames` or `shared_alb`. See the `promote` input description for more details."
54-
required: true
5545
default: "swap_cnames"
5646
template_name:
5747
description: "Name of an Elastic Beanstalk configuration template to use when creating a new environment."
58-
required: false
5948
terminate_unhealthy_environment:
6049
description: "Whether to terminate an unhealthy target environment. If set to false, the action will exit with an error when the target environment is unhealthy."
61-
required: true
6250
default: true
6351
version_description:
6452
description: "Description to use for the new application version."
65-
required: false
6653
version_label:
6754
description: "Version label to use for the new application version."
68-
required: false
55+
wait_for_deployment:
56+
description: "Whether to wait for the deployment or environment creation to complete."
57+
default: true
6958
wait_for_environment:
70-
description: "Whether to wait for an environment to update or terminate. If set to false, the action will exit with a non-error code during deployment, or exit with an error when the environment is terminating or otherwise not ready."
71-
required: true
59+
description: "Whether to wait for the target environment to be ready before deployment."
60+
default: true
61+
wait_for_termination:
62+
description: "Whether to wait for an environment to be terminated."
7263
default: true
7364
use_default_option_settings:
7465
description: "Whether to use default option settings when creating a new environment."
75-
required: true
7666
default: true
7767
outputs:
7868
target_env_cname:

dist/index.js

Lines changed: 51 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -210905,7 +210905,9 @@ function getCredentials() {
210905210905
}
210906210906
const awsConfig = {
210907210907
credentials: getCredentials(),
210908-
region: process.env.INPUT_AWS_REGION || process.env.AWS_REGION,
210908+
region: process.env.INPUT_AWS_REGION ||
210909+
process.env.AWS_REGION ||
210910+
process.env.AWS_DEFAULT_REGION,
210909210911
};
210910210912
const asClient = new client_auto_scaling_dist_cjs.AutoScalingClient(awsConfig);
210911210913
const ebClient = new dist_cjs.ElasticBeanstalkClient(awsConfig);
@@ -210929,6 +210931,8 @@ const fs = __nccwpck_require__(57147);
210929210931

210930210932
function getApplicationVersion(inputs) {
210931210933
return __awaiter(this, void 0, void 0, function* () {
210934+
if (!inputs.versionLabel)
210935+
return null;
210932210936
const { ApplicationVersions } = yield ebClient.send(new dist_cjs.DescribeApplicationVersionsCommand({
210933210937
ApplicationName: inputs.appName,
210934210938
VersionLabels: [inputs.versionLabel],
@@ -210937,14 +210941,11 @@ function getApplicationVersion(inputs) {
210937210941
console.log(`Application version ${inputs.versionLabel} already exists.`);
210938210942
return ApplicationVersions[0];
210939210943
}
210940-
const newVersion = yield createApplicationVersion(inputs);
210941-
return newVersion;
210944+
return yield createApplicationVersion(inputs);
210942210945
});
210943210946
}
210944210947
function createApplicationVersion(inputs) {
210945210948
return __awaiter(this, void 0, void 0, function* () {
210946-
if (!inputs.versionLabel)
210947-
return null;
210948210949
let SourceBundle;
210949210950
if (inputs.sourceBundle) {
210950210951
const { S3Bucket } = yield ebClient.send(new dist_cjs.CreateStorageLocationCommand({}));
@@ -210987,20 +210988,21 @@ const inputs_fs = __nccwpck_require__(57147);
210987210988
function getInputs() {
210988210989
const inputs = {
210989210990
appName: core.getInput("app_name", { required: true }),
210990-
awsRegion: core.getInput("aws_region") || process.env.AWS_REGION,
210991+
awsRegion: core.getInput("aws_region") ||
210992+
process.env.AWS_REGION ||
210993+
process.env.AWS_DEFAULT_REGION,
210991210994
blueEnv: core.getInput("blue_env", { required: true }),
210992210995
deploy: core.getBooleanInput("deploy", { required: true }),
210993210996
greenEnv: core.getInput("green_env", { required: true }),
210994210997
optionSettings: core.getInput("option_settings")
210995210998
? JSON.parse(inputs_fs.readFileSync(core.getInput("option_settings")))
210996210999
: undefined,
210997-
platformBranchName: core.getInput("platform_branch_name", {
210998-
required: true,
210999-
}),
211000+
platformBranchName: core.getInput("platform_branch_name"),
211000211001
ports: core.getInput("ports", { required: true })
211001211002
.split(",")
211002211003
.map((str) => Number(str))
211003211004
.filter((num) => Boolean(num)),
211005+
prep: core.getBooleanInput("prep"),
211004211006
productionCNAME: core.getInput("production_cname") || undefined,
211005211007
promote: core.getBooleanInput("promote", { required: true }),
211006211008
sourceBundle: core.getInput("source_bundle") || undefined,
@@ -211010,9 +211012,15 @@ function getInputs() {
211010211012
terminateUnhealthyEnvironment: core.getBooleanInput("terminate_unhealthy_environment", { required: true }),
211011211013
versionDescription: core.getInput("version_description") || undefined,
211012211014
versionLabel: core.getInput("version_label") || undefined,
211015+
waitForDeployment: core.getBooleanInput("wait_for_deployment", {
211016+
required: true,
211017+
}),
211013211018
waitForEnvironment: core.getBooleanInput("wait_for_environment", {
211014211019
required: true,
211015211020
}),
211021+
waitForTermination: core.getBooleanInput("wait_for_termination", {
211022+
required: true,
211023+
}),
211016211024
useDefaultOptionSettings: core.getBooleanInput("use_default_option_settings", {
211017211025
required: true,
211018211026
}),
@@ -211048,6 +211056,9 @@ function checkInputs(inputs) {
211048211056
if (!inputs.productionCNAME) {
211049211057
throw new Error("production_cname is required when using the swap_cnames strategy");
211050211058
}
211059+
if (!inputs.stagingCNAME) {
211060+
throw new Error("staging_cname is required when using the swap_cnames strategy");
211061+
}
211051211062
if (inputs.productionCNAME === inputs.stagingCNAME) {
211052211063
throw new Error("production_cname and staging_cname must be different");
211053211064
}
@@ -211060,6 +211071,11 @@ function checkInputs(inputs) {
211060211071
throw new Error("the shared_alb strategy requires a port to be provided");
211061211072
}
211062211073
}
211074+
if (inputs.optionSettings) {
211075+
if (!Array.isArray(inputs.optionSettings)) {
211076+
throw new Error("option_settings must be an array");
211077+
}
211078+
}
211063211079
}
211064211080

211065211081
;// CONCATENATED MODULE: ./src/getEnvironments.ts
@@ -211098,9 +211114,10 @@ function getEnvironments(inputs) {
211098211114
};
211099211115
case DeploymentStrategy.SwapCNAMEs:
211100211116
const prodDomain = `${inputs.productionCNAME}.${inputs.awsRegion}.elasticbeanstalk.com`;
211117+
const stagingDomain = `${inputs.stagingCNAME}.${inputs.awsRegion}.elasticbeanstalk.com`;
211101211118
return {
211102211119
prodEnv: Environments.find(({ CNAME }) => CNAME === prodDomain),
211103-
stagingEnv: Environments.find(({ CNAME }) => CNAME !== prodDomain),
211120+
stagingEnv: Environments.find(({ CNAME }) => CNAME === stagingDomain),
211104211121
};
211105211122
default:
211106211123
throw new Error(`Unknown strategy: ${inputs.strategy}`);
@@ -211189,7 +211206,7 @@ function setDescribeEventsInterval(environmentId, startTime = new Date()) {
211189211206
startTime = Events[0].EventDate;
211190211207
for (const e of Events.reverse()) {
211191211208
console.log(`${printUTCTime(e.EventDate)} ${e.Severity} ${e.EnvironmentName}: ${e.Message}`);
211192-
if (e.Message === "Failed to launch environment.") {
211209+
if (e.Severity === "ERROR") {
211193211210
throw new Error(e.Message);
211194211211
}
211195211212
}
@@ -211227,13 +211244,13 @@ function terminateEnvironment(inputs, environmentId, environmentName) {
211227211244
yield ebClient.send(new dist_cjs.TerminateEnvironmentCommand({
211228211245
EnvironmentId: environmentId,
211229211246
}));
211230-
if (inputs.waitForEnvironment) {
211247+
if (inputs.waitForTermination) {
211231211248
const interval = setDescribeEventsInterval(environmentId, startTime);
211232211249
yield (0,dist_cjs.waitUntilEnvironmentTerminated)({ client: ebClient, maxWaitTime: 60 * 10, minDelay: 5, maxDelay: 30 }, { EnvironmentIds: [environmentId] });
211233211250
clearInterval(interval);
211234211251
}
211235211252
else
211236-
throw new Error("Target environment is terminating and wait_for_environment is set to false.");
211253+
throw new Error("Target environment is terminating and wait_for_termination is set to false.");
211237211254
});
211238211255
}
211239211256

@@ -211261,21 +211278,21 @@ function getTargetEnv(inputs) {
211261211278
return null;
211262211279
}
211263211280
if (targetEnv.Status === "Terminating") {
211264-
if (inputs.waitForEnvironment) {
211281+
if (inputs.waitForTermination) {
211265211282
console.log("Target environment is terminating. Waiting...");
211266211283
const interval = setDescribeEventsInterval(targetEnv.EnvironmentId);
211267211284
yield (0,dist_cjs.waitUntilEnvironmentTerminated)({ client: ebClient, maxWaitTime: 60 * 10, minDelay: 5, maxDelay: 30 }, { EnvironmentIds: [targetEnv.EnvironmentId] });
211268211285
clearInterval(interval);
211269211286
return null;
211270211287
}
211271211288
else
211272-
throw new Error("Target environment is terminating and wait_for_environment is set to false.");
211289+
throw new Error("Target environment is terminating and wait_for_termination is set to false.");
211273211290
}
211274211291
else if (targetEnv.Status !== "Ready") {
211275211292
if (inputs.waitForEnvironment) {
211276211293
console.log("Target environment is not ready. Waiting...");
211277211294
const interval = setDescribeEventsInterval(targetEnv.EnvironmentId);
211278-
yield (0,dist_cjs.waitUntilEnvironmentExists)({ client: ebClient, maxWaitTime: 60 * 10, minDelay: 5, maxDelay: 30 }, { EnvironmentIds: [targetEnv.EnvironmentId] });
211295+
yield (0,dist_cjs.waitUntilEnvironmentUpdated)({ client: ebClient, maxWaitTime: 60 * 10, minDelay: 5, maxDelay: 30 }, { EnvironmentIds: [targetEnv.EnvironmentId] });
211279211296
clearInterval(interval);
211280211297
return getTargetEnv(inputs);
211281211298
}
@@ -211378,6 +211395,9 @@ var createEnvironment_awaiter = (undefined && undefined.__awaiter) || function (
211378211395

211379211396
function getPlatformArn(platformBranchName) {
211380211397
return createEnvironment_awaiter(this, void 0, void 0, function* () {
211398+
if (!platformBranchName) {
211399+
throw new Error("platform_branch_name must be provided when creating a new environment");
211400+
}
211381211401
const { PlatformSummaryList } = yield ebClient.send(new dist_cjs.ListPlatformVersionsCommand({
211382211402
Filters: [
211383211403
{
@@ -211407,7 +211427,7 @@ function createEnvironment(inputs, applicationVersion) {
211407211427
throw new Error(`Invalid strategy: ${inputs.strategy}`);
211408211428
}
211409211429
console.log(`Creating environment ${newEnv.EnvironmentId} ${newEnv.EnvironmentName}...`);
211410-
if (!inputs.waitForEnvironment) {
211430+
if (!inputs.waitForDeployment) {
211411211431
return newEnv;
211412211432
}
211413211433
const interval = setDescribeEventsInterval(newEnv.EnvironmentId, startTime);
@@ -211538,9 +211558,11 @@ function deploy(inputs, targetEnv, applicationVersion) {
211538211558
const startTime = new Date();
211539211559
yield ebClient.send(new dist_cjs.UpdateEnvironmentCommand({
211540211560
EnvironmentId: targetEnv.EnvironmentId,
211561+
OptionSettings: inputs.optionSettings,
211562+
TemplateName: inputs.templateName,
211541211563
VersionLabel: applicationVersion === null || applicationVersion === void 0 ? void 0 : applicationVersion.VersionLabel,
211542211564
}));
211543-
if (!inputs.waitForEnvironment) {
211565+
if (!inputs.waitForDeployment) {
211544211566
return;
211545211567
}
211546211568
const interval = setDescribeEventsInterval(targetEnv.EnvironmentId, startTime);
@@ -211679,6 +211701,13 @@ function main(inputs) {
211679211701
try {
211680211702
const applicationVersion = yield getApplicationVersion(inputs);
211681211703
let targetEnv = yield getTargetEnv(inputs);
211704+
if (inputs.prep) {
211705+
if (!targetEnv) {
211706+
targetEnv = yield createEnvironment(inputs, applicationVersion);
211707+
}
211708+
yield setOutputs(targetEnv);
211709+
return;
211710+
}
211682211711
if (inputs.deploy) {
211683211712
if (targetEnv) {
211684211713
yield deploy(inputs, targetEnv, applicationVersion);
@@ -211688,6 +211717,7 @@ function main(inputs) {
211688211717
}
211689211718
}
211690211719
if (inputs.promote) {
211720+
console.log(`Promoting environment ${targetEnv.EnvironmentName} to production...`);
211691211721
if (!targetEnv) {
211692211722
throw new Error("No target environment to promote");
211693211723
}
@@ -211698,6 +211728,9 @@ function main(inputs) {
211698211728
if (Environments[0].Health !== "Green") {
211699211729
throw new Error(`Environment ${targetEnv.EnvironmentName} is not healthy. Aborting promotion.`);
211700211730
}
211731+
if (Environments[0].Status !== "Ready") {
211732+
throw new Error(`Environment ${targetEnv.EnvironmentName} is not ready. Aborting promotion.`);
211733+
}
211701211734
});
211702211735
switch (inputs.strategy) {
211703211736
case DeploymentStrategy.SharedALB:

src/clients.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ function getCredentials() {
2222

2323
const awsConfig = {
2424
credentials: getCredentials(),
25-
region: process.env.INPUT_AWS_REGION || process.env.AWS_REGION,
25+
region:
26+
process.env.INPUT_AWS_REGION ||
27+
process.env.AWS_REGION ||
28+
process.env.AWS_DEFAULT_REGION,
2629
};
2730

2831
export const asClient = new AutoScalingClient(awsConfig);

0 commit comments

Comments
 (0)