Elastic Load Balancing - Version 2 examples using SDK for JavaScript (v3) (original) (raw)
Run the interactive scenario at a command prompt.
#!/usr/bin/env node
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import {
Scenario,
parseScenarioArgs,
} from "@aws-doc-sdk-examples/lib/scenario/index.js";
/**
* The workflow steps are split into three stages:
* - deploy
* - demo
* - destroy
*
* Each of these stages has a corresponding file prefixed with steps-*.
*/
import { deploySteps } from "./steps-deploy.js";
import { demoSteps } from "./steps-demo.js";
import { destroySteps } from "./steps-destroy.js";
/**
* The context is passed to every scenario. Scenario steps
* will modify the context.
*/
const context = {};
/**
* Three Scenarios are created for the workflow. A Scenario is an orchestration class
* that simplifies running a series of steps.
*/
export const scenarios = {
// Deploys all resources necessary for the workflow.
deploy: new Scenario("Resilient Workflow - Deploy", deploySteps, context),
// Demonstrates how a fragile web service can be made more resilient.
demo: new Scenario("Resilient Workflow - Demo", demoSteps, context),
// Destroys the resources created for the workflow.
destroy: new Scenario("Resilient Workflow - Destroy", destroySteps, context),
};
// Call function if run directly
import { fileURLToPath } from "node:url";
if (process.argv[1] === fileURLToPath(import.meta.url)) {
parseScenarioArgs(scenarios, {
name: "Resilient Workflow",
synopsis:
"node index.js --scenario <deploy | demo | destroy> [-h|--help] [-y|--yes] [-v|--verbose]",
description: "Deploy and interact with scalable EC2 instances.",
});
}
Create steps to deploy all of the resources.
import { join } from "node:path";
import { readFileSync, writeFileSync } from "node:fs";
import axios from "axios";
import {
BatchWriteItemCommand,
CreateTableCommand,
DynamoDBClient,
waitUntilTableExists,
} from "@aws-sdk/client-dynamodb";
import {
EC2Client,
CreateKeyPairCommand,
CreateLaunchTemplateCommand,
DescribeAvailabilityZonesCommand,
DescribeVpcsCommand,
DescribeSubnetsCommand,
DescribeSecurityGroupsCommand,
AuthorizeSecurityGroupIngressCommand,
} from "@aws-sdk/client-ec2";
import {
IAMClient,
CreatePolicyCommand,
CreateRoleCommand,
CreateInstanceProfileCommand,
AddRoleToInstanceProfileCommand,
AttachRolePolicyCommand,
waitUntilInstanceProfileExists,
} from "@aws-sdk/client-iam";
import { SSMClient, GetParameterCommand } from "@aws-sdk/client-ssm";
import {
CreateAutoScalingGroupCommand,
AutoScalingClient,
AttachLoadBalancerTargetGroupsCommand,
} from "@aws-sdk/client-auto-scaling";
import {
CreateListenerCommand,
CreateLoadBalancerCommand,
CreateTargetGroupCommand,
ElasticLoadBalancingV2Client,
waitUntilLoadBalancerAvailable,
} from "@aws-sdk/client-elastic-load-balancing-v2";
import {
ScenarioOutput,
ScenarioInput,
ScenarioAction,
} from "@aws-doc-sdk-examples/lib/scenario/index.js";
import { saveState } from "@aws-doc-sdk-examples/lib/scenario/steps-common.js";
import { retry } from "@aws-doc-sdk-examples/lib/utils/util-timers.js";
import { MESSAGES, NAMES, RESOURCES_PATH, ROOT } from "./constants.js";
import { initParamsSteps } from "./steps-reset-params.js";
/**
* @type {import('@aws-doc-sdk-examples/lib/scenario.js').Step[]}
*/
export const deploySteps = [
new ScenarioOutput("introduction", MESSAGES.introduction, { header: true }),
new ScenarioInput("confirmDeployment", MESSAGES.confirmDeployment, {
type: "confirm",
}),
new ScenarioAction(
"handleConfirmDeployment",
(c) => c.confirmDeployment === false && process.exit(),
),
new ScenarioOutput(
"creatingTable",
MESSAGES.creatingTable.replace("${TABLE_NAME}", NAMES.tableName),
),
new ScenarioAction("createTable", async () => {
const client = new DynamoDBClient({});
await client.send(
new CreateTableCommand({
TableName: NAMES.tableName,
ProvisionedThroughput: {
ReadCapacityUnits: 5,
WriteCapacityUnits: 5,
},
AttributeDefinitions: [
{
AttributeName: "MediaType",
AttributeType: "S",
},
{
AttributeName: "ItemId",
AttributeType: "N",
},
],
KeySchema: [
{
AttributeName: "MediaType",
KeyType: "HASH",
},
{
AttributeName: "ItemId",
KeyType: "RANGE",
},
],
}),
);
await waitUntilTableExists({ client }, { TableName: NAMES.tableName });
}),
new ScenarioOutput(
"createdTable",
MESSAGES.createdTable.replace("${TABLE_NAME}", NAMES.tableName),
),
new ScenarioOutput(
"populatingTable",
MESSAGES.populatingTable.replace("${TABLE_NAME}", NAMES.tableName),
),
new ScenarioAction("populateTable", () => {
const client = new DynamoDBClient({});
/**
* @type {{ default: import("@aws-sdk/client-dynamodb").PutRequest['Item'][] }}
*/
const recommendations = JSON.parse(
readFileSync(join(RESOURCES_PATH, "recommendations.json")),
);
return client.send(
new BatchWriteItemCommand({
RequestItems: {
[NAMES.tableName]: recommendations.map((item) => ({
PutRequest: { Item: item },
})),
},
}),
);
}),
new ScenarioOutput(
"populatedTable",
MESSAGES.populatedTable.replace("${TABLE_NAME}", NAMES.tableName),
),
new ScenarioOutput(
"creatingKeyPair",
MESSAGES.creatingKeyPair.replace("${KEY_PAIR_NAME}", NAMES.keyPairName),
),
new ScenarioAction("createKeyPair", async () => {
const client = new EC2Client({});
const { KeyMaterial } = await client.send(
new CreateKeyPairCommand({
KeyName: NAMES.keyPairName,
}),
);
writeFileSync(`${NAMES.keyPairName}.pem`, KeyMaterial, { mode: 0o600 });
}),
new ScenarioOutput(
"createdKeyPair",
MESSAGES.createdKeyPair.replace("${KEY_PAIR_NAME}", NAMES.keyPairName),
),
new ScenarioOutput(
"creatingInstancePolicy",
MESSAGES.creatingInstancePolicy.replace(
"${INSTANCE_POLICY_NAME}",
NAMES.instancePolicyName,
),
),
new ScenarioAction("createInstancePolicy", async (state) => {
const client = new IAMClient({});
const {
Policy: { Arn },
} = await client.send(
new CreatePolicyCommand({
PolicyName: NAMES.instancePolicyName,
PolicyDocument: readFileSync(
join(RESOURCES_PATH, "instance_policy.json"),
),
}),
);
state.instancePolicyArn = Arn;
}),
new ScenarioOutput("createdInstancePolicy", (state) =>
MESSAGES.createdInstancePolicy
.replace("${INSTANCE_POLICY_NAME}", NAMES.instancePolicyName)
.replace("${INSTANCE_POLICY_ARN}", state.instancePolicyArn),
),
new ScenarioOutput(
"creatingInstanceRole",
MESSAGES.creatingInstanceRole.replace(
"${INSTANCE_ROLE_NAME}",
NAMES.instanceRoleName,
),
),
new ScenarioAction("createInstanceRole", () => {
const client = new IAMClient({});
return client.send(
new CreateRoleCommand({
RoleName: NAMES.instanceRoleName,
AssumeRolePolicyDocument: readFileSync(
join(ROOT, "assume-role-policy.json"),
),
}),
);
}),
new ScenarioOutput(
"createdInstanceRole",
MESSAGES.createdInstanceRole.replace(
"${INSTANCE_ROLE_NAME}",
NAMES.instanceRoleName,
),
),
new ScenarioOutput(
"attachingPolicyToRole",
MESSAGES.attachingPolicyToRole
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName)
.replace("${INSTANCE_POLICY_NAME}", NAMES.instancePolicyName),
),
new ScenarioAction("attachPolicyToRole", async (state) => {
const client = new IAMClient({});
await client.send(
new AttachRolePolicyCommand({
RoleName: NAMES.instanceRoleName,
PolicyArn: state.instancePolicyArn,
}),
);
}),
new ScenarioOutput(
"attachedPolicyToRole",
MESSAGES.attachedPolicyToRole
.replace("${INSTANCE_POLICY_NAME}", NAMES.instancePolicyName)
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName),
),
new ScenarioOutput(
"creatingInstanceProfile",
MESSAGES.creatingInstanceProfile.replace(
"${INSTANCE_PROFILE_NAME}",
NAMES.instanceProfileName,
),
),
new ScenarioAction("createInstanceProfile", async (state) => {
const client = new IAMClient({});
const {
InstanceProfile: { Arn },
} = await client.send(
new CreateInstanceProfileCommand({
InstanceProfileName: NAMES.instanceProfileName,
}),
);
state.instanceProfileArn = Arn;
await waitUntilInstanceProfileExists(
{ client },
{ InstanceProfileName: NAMES.instanceProfileName },
);
}),
new ScenarioOutput("createdInstanceProfile", (state) =>
MESSAGES.createdInstanceProfile
.replace("${INSTANCE_PROFILE_NAME}", NAMES.instanceProfileName)
.replace("${INSTANCE_PROFILE_ARN}", state.instanceProfileArn),
),
new ScenarioOutput(
"addingRoleToInstanceProfile",
MESSAGES.addingRoleToInstanceProfile
.replace("${INSTANCE_PROFILE_NAME}", NAMES.instanceProfileName)
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName),
),
new ScenarioAction("addRoleToInstanceProfile", () => {
const client = new IAMClient({});
return client.send(
new AddRoleToInstanceProfileCommand({
RoleName: NAMES.instanceRoleName,
InstanceProfileName: NAMES.instanceProfileName,
}),
);
}),
new ScenarioOutput(
"addedRoleToInstanceProfile",
MESSAGES.addedRoleToInstanceProfile
.replace("${INSTANCE_PROFILE_NAME}", NAMES.instanceProfileName)
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName),
),
...initParamsSteps,
new ScenarioOutput("creatingLaunchTemplate", MESSAGES.creatingLaunchTemplate),
new ScenarioAction("createLaunchTemplate", async () => {
const ssmClient = new SSMClient({});
const { Parameter } = await ssmClient.send(
new GetParameterCommand({
Name: "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2",
}),
);
const ec2Client = new EC2Client({});
await ec2Client.send(
new CreateLaunchTemplateCommand({
LaunchTemplateName: NAMES.launchTemplateName,
LaunchTemplateData: {
InstanceType: "t3.micro",
ImageId: Parameter.Value,
IamInstanceProfile: { Name: NAMES.instanceProfileName },
UserData: readFileSync(
join(RESOURCES_PATH, "server_startup_script.sh"),
).toString("base64"),
KeyName: NAMES.keyPairName,
},
}),
);
}),
new ScenarioOutput(
"createdLaunchTemplate",
MESSAGES.createdLaunchTemplate.replace(
"${LAUNCH_TEMPLATE_NAME}",
NAMES.launchTemplateName,
),
),
new ScenarioOutput(
"creatingAutoScalingGroup",
MESSAGES.creatingAutoScalingGroup.replace(
"${AUTO_SCALING_GROUP_NAME}",
NAMES.autoScalingGroupName,
),
),
new ScenarioAction("createAutoScalingGroup", async (state) => {
const ec2Client = new EC2Client({});
const { AvailabilityZones } = await ec2Client.send(
new DescribeAvailabilityZonesCommand({}),
);
state.availabilityZoneNames = AvailabilityZones.map((az) => az.ZoneName);
const autoScalingClient = new AutoScalingClient({});
await retry({ intervalInMs: 1000, maxRetries: 30 }, () =>
autoScalingClient.send(
new CreateAutoScalingGroupCommand({
AvailabilityZones: state.availabilityZoneNames,
AutoScalingGroupName: NAMES.autoScalingGroupName,
LaunchTemplate: {
LaunchTemplateName: NAMES.launchTemplateName,
Version: "$Default",
},
MinSize: 3,
MaxSize: 3,
}),
),
);
}),
new ScenarioOutput(
"createdAutoScalingGroup",
/**
* @param {{ availabilityZoneNames: string[] }} state
*/
(state) =>
MESSAGES.createdAutoScalingGroup
.replace("${AUTO_SCALING_GROUP_NAME}", NAMES.autoScalingGroupName)
.replace(
"${AVAILABILITY_ZONE_NAMES}",
state.availabilityZoneNames.join(", "),
),
),
new ScenarioInput("confirmContinue", MESSAGES.confirmContinue, {
type: "confirm",
}),
new ScenarioOutput("loadBalancer", MESSAGES.loadBalancer),
new ScenarioOutput("gettingVpc", MESSAGES.gettingVpc),
new ScenarioAction("getVpc", async (state) => {
const client = new EC2Client({});
const { Vpcs } = await client.send(
new DescribeVpcsCommand({
Filters: [{ Name: "is-default", Values: ["true"] }],
}),
);
state.defaultVpc = Vpcs[0].VpcId;
}),
new ScenarioOutput("gotVpc", (state) =>
MESSAGES.gotVpc.replace("${VPC_ID}", state.defaultVpc),
),
new ScenarioOutput("gettingSubnets", MESSAGES.gettingSubnets),
new ScenarioAction("getSubnets", async (state) => {
const client = new EC2Client({});
const { Subnets } = await client.send(
new DescribeSubnetsCommand({
Filters: [
{ Name: "vpc-id", Values: [state.defaultVpc] },
{ Name: "availability-zone", Values: state.availabilityZoneNames },
{ Name: "default-for-az", Values: ["true"] },
],
}),
);
state.subnets = Subnets.map((subnet) => subnet.SubnetId);
}),
new ScenarioOutput(
"gotSubnets",
/**
* @param {{ subnets: string[] }} state
*/
(state) =>
MESSAGES.gotSubnets.replace("${SUBNETS}", state.subnets.join(", ")),
),
new ScenarioOutput(
"creatingLoadBalancerTargetGroup",
MESSAGES.creatingLoadBalancerTargetGroup.replace(
"${TARGET_GROUP_NAME}",
NAMES.loadBalancerTargetGroupName,
),
),
new ScenarioAction("createLoadBalancerTargetGroup", async (state) => {
const client = new ElasticLoadBalancingV2Client({});
const { TargetGroups } = await client.send(
new CreateTargetGroupCommand({
Name: NAMES.loadBalancerTargetGroupName,
Protocol: "HTTP",
Port: 80,
HealthCheckPath: "/healthcheck",
HealthCheckIntervalSeconds: 10,
HealthCheckTimeoutSeconds: 5,
HealthyThresholdCount: 2,
UnhealthyThresholdCount: 2,
VpcId: state.defaultVpc,
}),
);
const targetGroup = TargetGroups[0];
state.targetGroupArn = targetGroup.TargetGroupArn;
state.targetGroupProtocol = targetGroup.Protocol;
state.targetGroupPort = targetGroup.Port;
}),
new ScenarioOutput(
"createdLoadBalancerTargetGroup",
MESSAGES.createdLoadBalancerTargetGroup.replace(
"${TARGET_GROUP_NAME}",
NAMES.loadBalancerTargetGroupName,
),
),
new ScenarioOutput(
"creatingLoadBalancer",
MESSAGES.creatingLoadBalancer.replace("${LB_NAME}", NAMES.loadBalancerName),
),
new ScenarioAction("createLoadBalancer", async (state) => {
const client = new ElasticLoadBalancingV2Client({});
const { LoadBalancers } = await client.send(
new CreateLoadBalancerCommand({
Name: NAMES.loadBalancerName,
Subnets: state.subnets,
}),
);
state.loadBalancerDns = LoadBalancers[0].DNSName;
state.loadBalancerArn = LoadBalancers[0].LoadBalancerArn;
await waitUntilLoadBalancerAvailable(
{ client },
{ Names: [NAMES.loadBalancerName] },
);
}),
new ScenarioOutput("createdLoadBalancer", (state) =>
MESSAGES.createdLoadBalancer
.replace("${LB_NAME}", NAMES.loadBalancerName)
.replace("${DNS_NAME}", state.loadBalancerDns),
),
new ScenarioOutput(
"creatingListener",
MESSAGES.creatingLoadBalancerListener
.replace("${LB_NAME}", NAMES.loadBalancerName)
.replace("${TARGET_GROUP_NAME}", NAMES.loadBalancerTargetGroupName),
),
new ScenarioAction("createListener", async (state) => {
const client = new ElasticLoadBalancingV2Client({});
const { Listeners } = await client.send(
new CreateListenerCommand({
LoadBalancerArn: state.loadBalancerArn,
Protocol: state.targetGroupProtocol,
Port: state.targetGroupPort,
DefaultActions: [
{ Type: "forward", TargetGroupArn: state.targetGroupArn },
],
}),
);
const listener = Listeners[0];
state.loadBalancerListenerArn = listener.ListenerArn;
}),
new ScenarioOutput("createdListener", (state) =>
MESSAGES.createdLoadBalancerListener.replace(
"${LB_LISTENER_ARN}",
state.loadBalancerListenerArn,
),
),
new ScenarioOutput(
"attachingLoadBalancerTargetGroup",
MESSAGES.attachingLoadBalancerTargetGroup
.replace("${TARGET_GROUP_NAME}", NAMES.loadBalancerTargetGroupName)
.replace("${AUTO_SCALING_GROUP_NAME}", NAMES.autoScalingGroupName),
),
new ScenarioAction("attachLoadBalancerTargetGroup", async (state) => {
const client = new AutoScalingClient({});
await client.send(
new AttachLoadBalancerTargetGroupsCommand({
AutoScalingGroupName: NAMES.autoScalingGroupName,
TargetGroupARNs: [state.targetGroupArn],
}),
);
}),
new ScenarioOutput(
"attachedLoadBalancerTargetGroup",
MESSAGES.attachedLoadBalancerTargetGroup,
),
new ScenarioOutput("verifyingInboundPort", MESSAGES.verifyingInboundPort),
new ScenarioAction(
"verifyInboundPort",
/**
*
* @param {{ defaultSecurityGroup: import('@aws-sdk/client-ec2').SecurityGroup}} state
*/
async (state) => {
const client = new EC2Client({});
const { SecurityGroups } = await client.send(
new DescribeSecurityGroupsCommand({
Filters: [{ Name: "group-name", Values: ["default"] }],
}),
);
if (!SecurityGroups) {
state.verifyInboundPortError = new Error(MESSAGES.noSecurityGroups);
}
state.defaultSecurityGroup = SecurityGroups[0];
/**
* @type {string}
*/
const ipResponse = (await axios.get("http://checkip.amazonaws.com")).data;
state.myIp = ipResponse.trim();
const myIpRules = state.defaultSecurityGroup.IpPermissions.filter(
({ IpRanges }) =>
IpRanges.some(
({ CidrIp }) =>
CidrIp.startsWith(state.myIp) || CidrIp === "0.0.0.0/0",
),
)
.filter(({ IpProtocol }) => IpProtocol === "tcp")
.filter(({ FromPort }) => FromPort === 80);
state.myIpRules = myIpRules;
},
),
new ScenarioOutput(
"verifiedInboundPort",
/**
* @param {{ myIpRules: any[] }} state
*/
(state) => {
if (state.myIpRules.length > 0) {
return MESSAGES.foundIpRules.replace(
"${IP_RULES}",
JSON.stringify(state.myIpRules, null, 2),
);
}
return MESSAGES.noIpRules;
},
),
new ScenarioInput(
"shouldAddInboundRule",
/**
* @param {{ myIpRules: any[] }} state
*/
(state) => {
if (state.myIpRules.length > 0) {
return false;
}
return MESSAGES.noIpRules;
},
{ type: "confirm" },
),
new ScenarioAction(
"addInboundRule",
/**
* @param {{ defaultSecurityGroup: import('@aws-sdk/client-ec2').SecurityGroup }} state
*/
async (state) => {
if (!state.shouldAddInboundRule) {
return;
}
const client = new EC2Client({});
await client.send(
new AuthorizeSecurityGroupIngressCommand({
GroupId: state.defaultSecurityGroup.GroupId,
CidrIp: `${state.myIp}/32`,
FromPort: 80,
ToPort: 80,
IpProtocol: "tcp",
}),
);
},
),
new ScenarioOutput("addedInboundRule", (state) => {
if (state.shouldAddInboundRule) {
return MESSAGES.addedInboundRule.replace("${IP_ADDRESS}", state.myIp);
}
return false;
}),
new ScenarioOutput("verifyingEndpoint", (state) =>
MESSAGES.verifyingEndpoint.replace("${DNS_NAME}", state.loadBalancerDns),
),
new ScenarioAction("verifyEndpoint", async (state) => {
try {
const response = await retry({ intervalInMs: 2000, maxRetries: 30 }, () =>
axios.get(`http://${state.loadBalancerDns}`),
);
state.endpointResponse = JSON.stringify(response.data, null, 2);
} catch (e) {
state.verifyEndpointError = e;
}
}),
new ScenarioOutput("verifiedEndpoint", (state) => {
if (state.verifyEndpointError) {
console.error(state.verifyEndpointError);
} else {
return MESSAGES.verifiedEndpoint.replace(
"${ENDPOINT_RESPONSE}",
state.endpointResponse,
);
}
}),
saveState,
];
Create steps to run the demo.
import { readFileSync } from "node:fs";
import { join } from "node:path";
import axios from "axios";
import {
DescribeTargetGroupsCommand,
DescribeTargetHealthCommand,
ElasticLoadBalancingV2Client,
} from "@aws-sdk/client-elastic-load-balancing-v2";
import {
DescribeInstanceInformationCommand,
PutParameterCommand,
SSMClient,
SendCommandCommand,
} from "@aws-sdk/client-ssm";
import {
IAMClient,
CreatePolicyCommand,
CreateRoleCommand,
AttachRolePolicyCommand,
CreateInstanceProfileCommand,
AddRoleToInstanceProfileCommand,
waitUntilInstanceProfileExists,
} from "@aws-sdk/client-iam";
import {
AutoScalingClient,
DescribeAutoScalingGroupsCommand,
TerminateInstanceInAutoScalingGroupCommand,
} from "@aws-sdk/client-auto-scaling";
import {
DescribeIamInstanceProfileAssociationsCommand,
EC2Client,
RebootInstancesCommand,
ReplaceIamInstanceProfileAssociationCommand,
} from "@aws-sdk/client-ec2";
import {
ScenarioAction,
ScenarioInput,
ScenarioOutput,
} from "@aws-doc-sdk-examples/lib/scenario/scenario.js";
import { retry } from "@aws-doc-sdk-examples/lib/utils/util-timers.js";
import { MESSAGES, NAMES, RESOURCES_PATH } from "./constants.js";
import { findLoadBalancer } from "./shared.js";
const getRecommendation = new ScenarioAction(
"getRecommendation",
async (state) => {
const loadBalancer = await findLoadBalancer(NAMES.loadBalancerName);
if (loadBalancer) {
state.loadBalancerDnsName = loadBalancer.DNSName;
try {
state.recommendation = (
await axios.get(`http://${state.loadBalancerDnsName}`)
).data;
} catch (e) {
state.recommendation = e instanceof Error ? e.message : e;
}
} else {
throw new Error(MESSAGES.demoFindLoadBalancerError);
}
},
);
const getRecommendationResult = new ScenarioOutput(
"getRecommendationResult",
(state) =>
`Recommendation:\n${JSON.stringify(state.recommendation, null, 2)}`,
{ preformatted: true },
);
const getHealthCheck = new ScenarioAction("getHealthCheck", async (state) => {
const client = new ElasticLoadBalancingV2Client({});
const { TargetGroups } = await client.send(
new DescribeTargetGroupsCommand({
Names: [NAMES.loadBalancerTargetGroupName],
}),
);
const { TargetHealthDescriptions } = await client.send(
new DescribeTargetHealthCommand({
TargetGroupArn: TargetGroups[0].TargetGroupArn,
}),
);
state.targetHealthDescriptions = TargetHealthDescriptions;
});
const getHealthCheckResult = new ScenarioOutput(
"getHealthCheckResult",
/**
* @param {{ targetHealthDescriptions: import('@aws-sdk/client-elastic-load-balancing-v2').TargetHealthDescription[]}} state
*/
(state) => {
const status = state.targetHealthDescriptions
.map((th) => `${th.Target.Id}: ${th.TargetHealth.State}`)
.join("\n");
return `Health check:\n${status}`;
},
{ preformatted: true },
);
const loadBalancerLoop = new ScenarioAction(
"loadBalancerLoop",
getRecommendation.action,
{
whileConfig: {
whileFn: ({ loadBalancerCheck }) => loadBalancerCheck,
input: new ScenarioInput(
"loadBalancerCheck",
MESSAGES.demoLoadBalancerCheck,
{
type: "confirm",
},
),
output: getRecommendationResult,
},
},
);
const healthCheckLoop = new ScenarioAction(
"healthCheckLoop",
getHealthCheck.action,
{
whileConfig: {
whileFn: ({ healthCheck }) => healthCheck,
input: new ScenarioInput("healthCheck", MESSAGES.demoHealthCheck, {
type: "confirm",
}),
output: getHealthCheckResult,
},
},
);
const statusSteps = [
getRecommendation,
getRecommendationResult,
getHealthCheck,
getHealthCheckResult,
];
/**
* @type {import('@aws-doc-sdk-examples/lib/scenario.js').Step[]}
*/
export const demoSteps = [
new ScenarioOutput("header", MESSAGES.demoHeader, { header: true }),
new ScenarioOutput("sanityCheck", MESSAGES.demoSanityCheck),
...statusSteps,
new ScenarioInput(
"brokenDependencyConfirmation",
MESSAGES.demoBrokenDependencyConfirmation,
{ type: "confirm" },
),
new ScenarioAction("brokenDependency", async (state) => {
if (!state.brokenDependencyConfirmation) {
process.exit();
} else {
const client = new SSMClient({});
state.badTableName = `fake-table-${Date.now()}`;
await client.send(
new PutParameterCommand({
Name: NAMES.ssmTableNameKey,
Value: state.badTableName,
Overwrite: true,
Type: "String",
}),
);
}
}),
new ScenarioOutput("testBrokenDependency", (state) =>
MESSAGES.demoTestBrokenDependency.replace(
"${TABLE_NAME}",
state.badTableName,
),
),
...statusSteps,
new ScenarioInput(
"staticResponseConfirmation",
MESSAGES.demoStaticResponseConfirmation,
{ type: "confirm" },
),
new ScenarioAction("staticResponse", async (state) => {
if (!state.staticResponseConfirmation) {
process.exit();
} else {
const client = new SSMClient({});
await client.send(
new PutParameterCommand({
Name: NAMES.ssmFailureResponseKey,
Value: "static",
Overwrite: true,
Type: "String",
}),
);
}
}),
new ScenarioOutput("testStaticResponse", MESSAGES.demoTestStaticResponse),
...statusSteps,
new ScenarioInput(
"badCredentialsConfirmation",
MESSAGES.demoBadCredentialsConfirmation,
{ type: "confirm" },
),
new ScenarioAction("badCredentialsExit", (state) => {
if (!state.badCredentialsConfirmation) {
process.exit();
}
}),
new ScenarioAction("fixDynamoDBName", async () => {
const client = new SSMClient({});
await client.send(
new PutParameterCommand({
Name: NAMES.ssmTableNameKey,
Value: NAMES.tableName,
Overwrite: true,
Type: "String",
}),
);
}),
new ScenarioAction(
"badCredentials",
/**
* @param {{ targetInstance: import('@aws-sdk/client-auto-scaling').Instance }} state
*/
async (state) => {
await createSsmOnlyInstanceProfile();
const autoScalingClient = new AutoScalingClient({});
const { AutoScalingGroups } = await autoScalingClient.send(
new DescribeAutoScalingGroupsCommand({
AutoScalingGroupNames: [NAMES.autoScalingGroupName],
}),
);
state.targetInstance = AutoScalingGroups[0].Instances[0];
const ec2Client = new EC2Client({});
const { IamInstanceProfileAssociations } = await ec2Client.send(
new DescribeIamInstanceProfileAssociationsCommand({
Filters: [
{ Name: "instance-id", Values: [state.targetInstance.InstanceId] },
],
}),
);
state.instanceProfileAssociationId =
IamInstanceProfileAssociations[0].AssociationId;
await retry({ intervalInMs: 1000, maxRetries: 30 }, () =>
ec2Client.send(
new ReplaceIamInstanceProfileAssociationCommand({
AssociationId: state.instanceProfileAssociationId,
IamInstanceProfile: { Name: NAMES.ssmOnlyInstanceProfileName },
}),
),
);
await ec2Client.send(
new RebootInstancesCommand({
InstanceIds: [state.targetInstance.InstanceId],
}),
);
const ssmClient = new SSMClient({});
await retry({ intervalInMs: 20000, maxRetries: 15 }, async () => {
const { InstanceInformationList } = await ssmClient.send(
new DescribeInstanceInformationCommand({}),
);
const instance = InstanceInformationList.find(
(info) => info.InstanceId === state.targetInstance.InstanceId,
);
if (!instance) {
throw new Error("Instance not found.");
}
});
await ssmClient.send(
new SendCommandCommand({
InstanceIds: [state.targetInstance.InstanceId],
DocumentName: "AWS-RunShellScript",
Parameters: { commands: ["cd / && sudo python3 server.py 80"] },
}),
);
},
),
new ScenarioOutput(
"testBadCredentials",
/**
* @param {{ targetInstance: import('@aws-sdk/client-ssm').InstanceInformation}} state
*/
(state) =>
MESSAGES.demoTestBadCredentials.replace(
"${INSTANCE_ID}",
state.targetInstance.InstanceId,
),
),
loadBalancerLoop,
new ScenarioInput(
"deepHealthCheckConfirmation",
MESSAGES.demoDeepHealthCheckConfirmation,
{ type: "confirm" },
),
new ScenarioAction("deepHealthCheckExit", (state) => {
if (!state.deepHealthCheckConfirmation) {
process.exit();
}
}),
new ScenarioAction("deepHealthCheck", async () => {
const client = new SSMClient({});
await client.send(
new PutParameterCommand({
Name: NAMES.ssmHealthCheckKey,
Value: "deep",
Overwrite: true,
Type: "String",
}),
);
}),
new ScenarioOutput("testDeepHealthCheck", MESSAGES.demoTestDeepHealthCheck),
healthCheckLoop,
loadBalancerLoop,
new ScenarioInput(
"killInstanceConfirmation",
/**
* @param {{ targetInstance: import('@aws-sdk/client-ssm').InstanceInformation }} state
*/
(state) =>
MESSAGES.demoKillInstanceConfirmation.replace(
"${INSTANCE_ID}",
state.targetInstance.InstanceId,
),
{ type: "confirm" },
),
new ScenarioAction("killInstanceExit", (state) => {
if (!state.killInstanceConfirmation) {
process.exit();
}
}),
new ScenarioAction(
"killInstance",
/**
* @param {{ targetInstance: import('@aws-sdk/client-ssm').InstanceInformation }} state
*/
async (state) => {
const client = new AutoScalingClient({});
await client.send(
new TerminateInstanceInAutoScalingGroupCommand({
InstanceId: state.targetInstance.InstanceId,
ShouldDecrementDesiredCapacity: false,
}),
);
},
),
new ScenarioOutput("testKillInstance", MESSAGES.demoTestKillInstance),
healthCheckLoop,
loadBalancerLoop,
new ScenarioInput("failOpenConfirmation", MESSAGES.demoFailOpenConfirmation, {
type: "confirm",
}),
new ScenarioAction("failOpenExit", (state) => {
if (!state.failOpenConfirmation) {
process.exit();
}
}),
new ScenarioAction("failOpen", () => {
const client = new SSMClient({});
return client.send(
new PutParameterCommand({
Name: NAMES.ssmTableNameKey,
Value: `fake-table-${Date.now()}`,
Overwrite: true,
Type: "String",
}),
);
}),
new ScenarioOutput("testFailOpen", MESSAGES.demoFailOpenTest),
healthCheckLoop,
loadBalancerLoop,
new ScenarioInput(
"resetTableConfirmation",
MESSAGES.demoResetTableConfirmation,
{ type: "confirm" },
),
new ScenarioAction("resetTableExit", (state) => {
if (!state.resetTableConfirmation) {
process.exit();
}
}),
new ScenarioAction("resetTable", async () => {
const client = new SSMClient({});
await client.send(
new PutParameterCommand({
Name: NAMES.ssmTableNameKey,
Value: NAMES.tableName,
Overwrite: true,
Type: "String",
}),
);
}),
new ScenarioOutput("testResetTable", MESSAGES.demoTestResetTable),
healthCheckLoop,
loadBalancerLoop,
];
async function createSsmOnlyInstanceProfile() {
const iamClient = new IAMClient({});
const { Policy } = await iamClient.send(
new CreatePolicyCommand({
PolicyName: NAMES.ssmOnlyPolicyName,
PolicyDocument: readFileSync(
join(RESOURCES_PATH, "ssm_only_policy.json"),
),
}),
);
await iamClient.send(
new CreateRoleCommand({
RoleName: NAMES.ssmOnlyRoleName,
AssumeRolePolicyDocument: JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: { Service: "ec2.amazonaws.com" },
Action: "sts:AssumeRole",
},
],
}),
}),
);
await iamClient.send(
new AttachRolePolicyCommand({
RoleName: NAMES.ssmOnlyRoleName,
PolicyArn: Policy.Arn,
}),
);
await iamClient.send(
new AttachRolePolicyCommand({
RoleName: NAMES.ssmOnlyRoleName,
PolicyArn: "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
}),
);
const { InstanceProfile } = await iamClient.send(
new CreateInstanceProfileCommand({
InstanceProfileName: NAMES.ssmOnlyInstanceProfileName,
}),
);
await waitUntilInstanceProfileExists(
{ client: iamClient },
{ InstanceProfileName: NAMES.ssmOnlyInstanceProfileName },
);
await iamClient.send(
new AddRoleToInstanceProfileCommand({
InstanceProfileName: NAMES.ssmOnlyInstanceProfileName,
RoleName: NAMES.ssmOnlyRoleName,
}),
);
return InstanceProfile;
}
Create steps to destroy all of the resources.
import { unlinkSync } from "node:fs";
import { DynamoDBClient, DeleteTableCommand } from "@aws-sdk/client-dynamodb";
import {
EC2Client,
DeleteKeyPairCommand,
DeleteLaunchTemplateCommand,
RevokeSecurityGroupIngressCommand,
} from "@aws-sdk/client-ec2";
import {
IAMClient,
DeleteInstanceProfileCommand,
RemoveRoleFromInstanceProfileCommand,
DeletePolicyCommand,
DeleteRoleCommand,
DetachRolePolicyCommand,
paginateListPolicies,
} from "@aws-sdk/client-iam";
import {
AutoScalingClient,
DeleteAutoScalingGroupCommand,
TerminateInstanceInAutoScalingGroupCommand,
UpdateAutoScalingGroupCommand,
paginateDescribeAutoScalingGroups,
} from "@aws-sdk/client-auto-scaling";
import {
DeleteLoadBalancerCommand,
DeleteTargetGroupCommand,
DescribeTargetGroupsCommand,
ElasticLoadBalancingV2Client,
} from "@aws-sdk/client-elastic-load-balancing-v2";
import {
ScenarioOutput,
ScenarioInput,
ScenarioAction,
} from "@aws-doc-sdk-examples/lib/scenario/index.js";
import { loadState } from "@aws-doc-sdk-examples/lib/scenario/steps-common.js";
import { retry } from "@aws-doc-sdk-examples/lib/utils/util-timers.js";
import { MESSAGES, NAMES } from "./constants.js";
import { findLoadBalancer } from "./shared.js";
/**
* @type {import('@aws-doc-sdk-examples/lib/scenario.js').Step[]}
*/
export const destroySteps = [
loadState,
new ScenarioInput("destroy", MESSAGES.destroy, { type: "confirm" }),
new ScenarioAction(
"abort",
(state) => state.destroy === false && process.exit(),
),
new ScenarioAction("deleteTable", async (c) => {
try {
const client = new DynamoDBClient({});
await client.send(new DeleteTableCommand({ TableName: NAMES.tableName }));
} catch (e) {
c.deleteTableError = e;
}
}),
new ScenarioOutput("deleteTableResult", (state) => {
if (state.deleteTableError) {
console.error(state.deleteTableError);
return MESSAGES.deleteTableError.replace(
"${TABLE_NAME}",
NAMES.tableName,
);
}
return MESSAGES.deletedTable.replace("${TABLE_NAME}", NAMES.tableName);
}),
new ScenarioAction("deleteKeyPair", async (state) => {
try {
const client = new EC2Client({});
await client.send(
new DeleteKeyPairCommand({ KeyName: NAMES.keyPairName }),
);
unlinkSync(`${NAMES.keyPairName}.pem`);
} catch (e) {
state.deleteKeyPairError = e;
}
}),
new ScenarioOutput("deleteKeyPairResult", (state) => {
if (state.deleteKeyPairError) {
console.error(state.deleteKeyPairError);
return MESSAGES.deleteKeyPairError.replace(
"${KEY_PAIR_NAME}",
NAMES.keyPairName,
);
}
return MESSAGES.deletedKeyPair.replace(
"${KEY_PAIR_NAME}",
NAMES.keyPairName,
);
}),
new ScenarioAction("detachPolicyFromRole", async (state) => {
try {
const client = new IAMClient({});
const policy = await findPolicy(NAMES.instancePolicyName);
if (!policy) {
state.detachPolicyFromRoleError = new Error(
`Policy ${NAMES.instancePolicyName} not found.`,
);
} else {
await client.send(
new DetachRolePolicyCommand({
RoleName: NAMES.instanceRoleName,
PolicyArn: policy.Arn,
}),
);
}
} catch (e) {
state.detachPolicyFromRoleError = e;
}
}),
new ScenarioOutput("detachedPolicyFromRole", (state) => {
if (state.detachPolicyFromRoleError) {
console.error(state.detachPolicyFromRoleError);
return MESSAGES.detachPolicyFromRoleError
.replace("${INSTANCE_POLICY_NAME}", NAMES.instancePolicyName)
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName);
}
return MESSAGES.detachedPolicyFromRole
.replace("${INSTANCE_POLICY_NAME}", NAMES.instancePolicyName)
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName);
}),
new ScenarioAction("deleteInstancePolicy", async (state) => {
const client = new IAMClient({});
const policy = await findPolicy(NAMES.instancePolicyName);
if (!policy) {
state.deletePolicyError = new Error(
`Policy ${NAMES.instancePolicyName} not found.`,
);
} else {
return client.send(
new DeletePolicyCommand({
PolicyArn: policy.Arn,
}),
);
}
}),
new ScenarioOutput("deletePolicyResult", (state) => {
if (state.deletePolicyError) {
console.error(state.deletePolicyError);
return MESSAGES.deletePolicyError.replace(
"${INSTANCE_POLICY_NAME}",
NAMES.instancePolicyName,
);
}
return MESSAGES.deletedPolicy.replace(
"${INSTANCE_POLICY_NAME}",
NAMES.instancePolicyName,
);
}),
new ScenarioAction("removeRoleFromInstanceProfile", async (state) => {
try {
const client = new IAMClient({});
await client.send(
new RemoveRoleFromInstanceProfileCommand({
RoleName: NAMES.instanceRoleName,
InstanceProfileName: NAMES.instanceProfileName,
}),
);
} catch (e) {
state.removeRoleFromInstanceProfileError = e;
}
}),
new ScenarioOutput("removeRoleFromInstanceProfileResult", (state) => {
if (state.removeRoleFromInstanceProfile) {
console.error(state.removeRoleFromInstanceProfileError);
return MESSAGES.removeRoleFromInstanceProfileError
.replace("${INSTANCE_PROFILE_NAME}", NAMES.instanceProfileName)
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName);
}
return MESSAGES.removedRoleFromInstanceProfile
.replace("${INSTANCE_PROFILE_NAME}", NAMES.instanceProfileName)
.replace("${INSTANCE_ROLE_NAME}", NAMES.instanceRoleName);
}),
new ScenarioAction("deleteInstanceRole", async (state) => {
try {
const client = new IAMClient({});
await client.send(
new DeleteRoleCommand({
RoleName: NAMES.instanceRoleName,
}),
);
} catch (e) {
state.deleteInstanceRoleError = e;
}
}),
new ScenarioOutput("deleteInstanceRoleResult", (state) => {
if (state.deleteInstanceRoleError) {
console.error(state.deleteInstanceRoleError);
return MESSAGES.deleteInstanceRoleError.replace(
"${INSTANCE_ROLE_NAME}",
NAMES.instanceRoleName,
);
}
return MESSAGES.deletedInstanceRole.replace(
"${INSTANCE_ROLE_NAME}",
NAMES.instanceRoleName,
);
}),
new ScenarioAction("deleteInstanceProfile", async (state) => {
try {
const client = new IAMClient({});
await client.send(
new DeleteInstanceProfileCommand({
InstanceProfileName: NAMES.instanceProfileName,
}),
);
} catch (e) {
state.deleteInstanceProfileError = e;
}
}),
new ScenarioOutput("deleteInstanceProfileResult", (state) => {
if (state.deleteInstanceProfileError) {
console.error(state.deleteInstanceProfileError);
return MESSAGES.deleteInstanceProfileError.replace(
"${INSTANCE_PROFILE_NAME}",
NAMES.instanceProfileName,
);
}
return MESSAGES.deletedInstanceProfile.replace(
"${INSTANCE_PROFILE_NAME}",
NAMES.instanceProfileName,
);
}),
new ScenarioAction("deleteAutoScalingGroup", async (state) => {
try {
await terminateGroupInstances(NAMES.autoScalingGroupName);
await retry({ intervalInMs: 60000, maxRetries: 60 }, async () => {
await deleteAutoScalingGroup(NAMES.autoScalingGroupName);
});
} catch (e) {
state.deleteAutoScalingGroupError = e;
}
}),
new ScenarioOutput("deleteAutoScalingGroupResult", (state) => {
if (state.deleteAutoScalingGroupError) {
console.error(state.deleteAutoScalingGroupError);
return MESSAGES.deleteAutoScalingGroupError.replace(
"${AUTO_SCALING_GROUP_NAME}",
NAMES.autoScalingGroupName,
);
}
return MESSAGES.deletedAutoScalingGroup.replace(
"${AUTO_SCALING_GROUP_NAME}",
NAMES.autoScalingGroupName,
);
}),
new ScenarioAction("deleteLaunchTemplate", async (state) => {
const client = new EC2Client({});
try {
await client.send(
new DeleteLaunchTemplateCommand({
LaunchTemplateName: NAMES.launchTemplateName,
}),
);
} catch (e) {
state.deleteLaunchTemplateError = e;
}
}),
new ScenarioOutput("deleteLaunchTemplateResult", (state) => {
if (state.deleteLaunchTemplateError) {
console.error(state.deleteLaunchTemplateError);
return MESSAGES.deleteLaunchTemplateError.replace(
"${LAUNCH_TEMPLATE_NAME}",
NAMES.launchTemplateName,
);
}
return MESSAGES.deletedLaunchTemplate.replace(
"${LAUNCH_TEMPLATE_NAME}",
NAMES.launchTemplateName,
);
}),
new ScenarioAction("deleteLoadBalancer", async (state) => {
try {
const client = new ElasticLoadBalancingV2Client({});
const loadBalancer = await findLoadBalancer(NAMES.loadBalancerName);
await client.send(
new DeleteLoadBalancerCommand({
LoadBalancerArn: loadBalancer.LoadBalancerArn,
}),
);
await retry({ intervalInMs: 1000, maxRetries: 60 }, async () => {
const lb = await findLoadBalancer(NAMES.loadBalancerName);
if (lb) {
throw new Error("Load balancer still exists.");
}
});
} catch (e) {
state.deleteLoadBalancerError = e;
}
}),
new ScenarioOutput("deleteLoadBalancerResult", (state) => {
if (state.deleteLoadBalancerError) {
console.error(state.deleteLoadBalancerError);
return MESSAGES.deleteLoadBalancerError.replace(
"${LB_NAME}",
NAMES.loadBalancerName,
);
}
return MESSAGES.deletedLoadBalancer.replace(
"${LB_NAME}",
NAMES.loadBalancerName,
);
}),
new ScenarioAction("deleteLoadBalancerTargetGroup", async (state) => {
const client = new ElasticLoadBalancingV2Client({});
try {
const { TargetGroups } = await client.send(
new DescribeTargetGroupsCommand({
Names: [NAMES.loadBalancerTargetGroupName],
}),
);
await retry({ intervalInMs: 1000, maxRetries: 30 }, () =>
client.send(
new DeleteTargetGroupCommand({
TargetGroupArn: TargetGroups[0].TargetGroupArn,
}),
),
);
} catch (e) {
state.deleteLoadBalancerTargetGroupError = e;
}
}),
new ScenarioOutput("deleteLoadBalancerTargetGroupResult", (state) => {
if (state.deleteLoadBalancerTargetGroupError) {
console.error(state.deleteLoadBalancerTargetGroupError);
return MESSAGES.deleteLoadBalancerTargetGroupError.replace(
"${TARGET_GROUP_NAME}",
NAMES.loadBalancerTargetGroupName,
);
}
return MESSAGES.deletedLoadBalancerTargetGroup.replace(
"${TARGET_GROUP_NAME}",
NAMES.loadBalancerTargetGroupName,
);
}),
new ScenarioAction("detachSsmOnlyRoleFromProfile", async (state) => {
try {
const client = new IAMClient({});
await client.send(
new RemoveRoleFromInstanceProfileCommand({
InstanceProfileName: NAMES.ssmOnlyInstanceProfileName,
RoleName: NAMES.ssmOnlyRoleName,
}),
);
} catch (e) {
state.detachSsmOnlyRoleFromProfileError = e;
}
}),
new ScenarioOutput("detachSsmOnlyRoleFromProfileResult", (state) => {
if (state.detachSsmOnlyRoleFromProfileError) {
console.error(state.detachSsmOnlyRoleFromProfileError);
return MESSAGES.detachSsmOnlyRoleFromProfileError
.replace("${ROLE_NAME}", NAMES.ssmOnlyRoleName)
.replace("${PROFILE_NAME}", NAMES.ssmOnlyInstanceProfileName);
}
return MESSAGES.detachedSsmOnlyRoleFromProfile
.replace("${ROLE_NAME}", NAMES.ssmOnlyRoleName)
.replace("${PROFILE_NAME}", NAMES.ssmOnlyInstanceProfileName);
}),
new ScenarioAction("detachSsmOnlyCustomRolePolicy", async (state) => {
try {
const iamClient = new IAMClient({});
const ssmOnlyPolicy = await findPolicy(NAMES.ssmOnlyPolicyName);
await iamClient.send(
new DetachRolePolicyCommand({
RoleName: NAMES.ssmOnlyRoleName,
PolicyArn: ssmOnlyPolicy.Arn,
}),
);
} catch (e) {
state.detachSsmOnlyCustomRolePolicyError = e;
}
}),
new ScenarioOutput("detachSsmOnlyCustomRolePolicyResult", (state) => {
if (state.detachSsmOnlyCustomRolePolicyError) {
console.error(state.detachSsmOnlyCustomRolePolicyError);
return MESSAGES.detachSsmOnlyCustomRolePolicyError
.replace("${ROLE_NAME}", NAMES.ssmOnlyRoleName)
.replace("${POLICY_NAME}", NAMES.ssmOnlyPolicyName);
}
return MESSAGES.detachedSsmOnlyCustomRolePolicy
.replace("${ROLE_NAME}", NAMES.ssmOnlyRoleName)
.replace("${POLICY_NAME}", NAMES.ssmOnlyPolicyName);
}),
new ScenarioAction("detachSsmOnlyAWSRolePolicy", async (state) => {
try {
const iamClient = new IAMClient({});
await iamClient.send(
new DetachRolePolicyCommand({
RoleName: NAMES.ssmOnlyRoleName,
PolicyArn: "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore",
}),
);
} catch (e) {
state.detachSsmOnlyAWSRolePolicyError = e;
}
}),
new ScenarioOutput("detachSsmOnlyAWSRolePolicyResult", (state) => {
if (state.detachSsmOnlyAWSRolePolicyError) {
console.error(state.detachSsmOnlyAWSRolePolicyError);
return MESSAGES.detachSsmOnlyAWSRolePolicyError
.replace("${ROLE_NAME}", NAMES.ssmOnlyRoleName)
.replace("${POLICY_NAME}", "AmazonSSMManagedInstanceCore");
}
return MESSAGES.detachedSsmOnlyAWSRolePolicy
.replace("${ROLE_NAME}", NAMES.ssmOnlyRoleName)
.replace("${POLICY_NAME}", "AmazonSSMManagedInstanceCore");
}),
new ScenarioAction("deleteSsmOnlyInstanceProfile", async (state) => {
try {
const iamClient = new IAMClient({});
await iamClient.send(
new DeleteInstanceProfileCommand({
InstanceProfileName: NAMES.ssmOnlyInstanceProfileName,
}),
);
} catch (e) {
state.deleteSsmOnlyInstanceProfileError = e;
}
}),
new ScenarioOutput("deleteSsmOnlyInstanceProfileResult", (state) => {
if (state.deleteSsmOnlyInstanceProfileError) {
console.error(state.deleteSsmOnlyInstanceProfileError);
return MESSAGES.deleteSsmOnlyInstanceProfileError.replace(
"${INSTANCE_PROFILE_NAME}",
NAMES.ssmOnlyInstanceProfileName,
);
}
return MESSAGES.deletedSsmOnlyInstanceProfile.replace(
"${INSTANCE_PROFILE_NAME}",
NAMES.ssmOnlyInstanceProfileName,
);
}),
new ScenarioAction("deleteSsmOnlyPolicy", async (state) => {
try {
const iamClient = new IAMClient({});
const ssmOnlyPolicy = await findPolicy(NAMES.ssmOnlyPolicyName);
await iamClient.send(
new DeletePolicyCommand({
PolicyArn: ssmOnlyPolicy.Arn,
}),
);
} catch (e) {
state.deleteSsmOnlyPolicyError = e;
}
}),
new ScenarioOutput("deleteSsmOnlyPolicyResult", (state) => {
if (state.deleteSsmOnlyPolicyError) {
console.error(state.deleteSsmOnlyPolicyError);
return MESSAGES.deleteSsmOnlyPolicyError.replace(
"${POLICY_NAME}",
NAMES.ssmOnlyPolicyName,
);
}
return MESSAGES.deletedSsmOnlyPolicy.replace(
"${POLICY_NAME}",
NAMES.ssmOnlyPolicyName,
);
}),
new ScenarioAction("deleteSsmOnlyRole", async (state) => {
try {
const iamClient = new IAMClient({});
await iamClient.send(
new DeleteRoleCommand({
RoleName: NAMES.ssmOnlyRoleName,
}),
);
} catch (e) {
state.deleteSsmOnlyRoleError = e;
}
}),
new ScenarioOutput("deleteSsmOnlyRoleResult", (state) => {
if (state.deleteSsmOnlyRoleError) {
console.error(state.deleteSsmOnlyRoleError);
return MESSAGES.deleteSsmOnlyRoleError.replace(
"${ROLE_NAME}",
NAMES.ssmOnlyRoleName,
);
}
return MESSAGES.deletedSsmOnlyRole.replace(
"${ROLE_NAME}",
NAMES.ssmOnlyRoleName,
);
}),
new ScenarioAction(
"revokeSecurityGroupIngress",
async (
/** @type {{ myIp: string, defaultSecurityGroup: { GroupId: string } }} */ state,
) => {
const ec2Client = new EC2Client({});
try {
await ec2Client.send(
new RevokeSecurityGroupIngressCommand({
GroupId: state.defaultSecurityGroup.GroupId,
CidrIp: `${state.myIp}/32`,
FromPort: 80,
ToPort: 80,
IpProtocol: "tcp",
}),
);
} catch (e) {
state.revokeSecurityGroupIngressError = e;
}
},
),
new ScenarioOutput("revokeSecurityGroupIngressResult", (state) => {
if (state.revokeSecurityGroupIngressError) {
console.error(state.revokeSecurityGroupIngressError);
return MESSAGES.revokeSecurityGroupIngressError.replace(
"${IP}",
state.myIp,
);
}
return MESSAGES.revokedSecurityGroupIngress.replace("${IP}", state.myIp);
}),
];
/**
* @param {string} policyName
*/
async function findPolicy(policyName) {
const client = new IAMClient({});
const paginatedPolicies = paginateListPolicies({ client }, {});
for await (const page of paginatedPolicies) {
const policy = page.Policies.find((p) => p.PolicyName === policyName);
if (policy) {
return policy;
}
}
}
/**
* @param {string} groupName
*/
async function deleteAutoScalingGroup(groupName) {
const client = new AutoScalingClient({});
try {
await client.send(
new DeleteAutoScalingGroupCommand({
AutoScalingGroupName: groupName,
}),
);
} catch (err) {
if (!(err instanceof Error)) {
throw err;
}
console.log(err.name);
throw err;
}
}
/**
* @param {string} groupName
*/
async function terminateGroupInstances(groupName) {
const autoScalingClient = new AutoScalingClient({});
const group = await findAutoScalingGroup(groupName);
await autoScalingClient.send(
new UpdateAutoScalingGroupCommand({
AutoScalingGroupName: group.AutoScalingGroupName,
MinSize: 0,
}),
);
for (const i of group.Instances) {
await retry({ intervalInMs: 1000, maxRetries: 30 }, () =>
autoScalingClient.send(
new TerminateInstanceInAutoScalingGroupCommand({
InstanceId: i.InstanceId,
ShouldDecrementDesiredCapacity: true,
}),
),
);
}
}
async function findAutoScalingGroup(groupName) {
const client = new AutoScalingClient({});
const paginatedGroups = paginateDescribeAutoScalingGroups({ client }, {});
for await (const page of paginatedGroups) {
const group = page.AutoScalingGroups.find(
(g) => g.AutoScalingGroupName === groupName,
);
if (group) {
return group;
}
}
throw new Error(`Auto scaling group ${groupName} not found.`);
}
- For API details, see the following topics in AWS SDK for JavaScript API Reference.
- AttachLoadBalancerTargetGroups
- CreateAutoScalingGroup
- CreateInstanceProfile
- CreateLaunchTemplate
- CreateListener
- CreateLoadBalancer
- CreateTargetGroup
- DeleteAutoScalingGroup
- DeleteInstanceProfile
- DeleteLaunchTemplate
- DeleteLoadBalancer
- DeleteTargetGroup
- DescribeAutoScalingGroups
- DescribeAvailabilityZones
- DescribeIamInstanceProfileAssociations
- DescribeInstances
- DescribeLoadBalancers
- DescribeSubnets
- DescribeTargetGroups
- DescribeTargetHealth
- DescribeVpcs
- RebootInstances
- ReplaceIamInstanceProfileAssociation
- TerminateInstanceInAutoScalingGroup
- UpdateAutoScalingGroup