Skip to content

Commit a5e723f

Browse files
committed
added a CDK Script so SSM Scenario has no dependency of a existing EC2 instance
1 parent be8209c commit a5e723f

File tree

6 files changed

+281
-22
lines changed

6 files changed

+281
-22
lines changed

javav2/example_code/ssm/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@
8989
<artifactId>slf4j-api</artifactId>
9090
<version>2.0.13</version>
9191
</dependency>
92+
<dependency>
93+
<groupId>software.amazon.awssdk</groupId>
94+
<artifactId>cloudformation</artifactId>
95+
</dependency>
9296
<dependency>
9397
<groupId>org.apache.logging.log4j</groupId>
9498
<artifactId>log4j-slf4j2-impl</artifactId>
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package com.example.scenario;
5+
6+
import org.slf4j.Logger;
7+
import org.slf4j.LoggerFactory;
8+
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
9+
import software.amazon.awssdk.core.retry.RetryMode;
10+
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
11+
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
12+
import software.amazon.awssdk.regions.Region;
13+
import software.amazon.awssdk.services.cloudformation.CloudFormationAsyncClient;
14+
import software.amazon.awssdk.services.cloudformation.model.Capability;
15+
import software.amazon.awssdk.services.cloudformation.model.CloudFormationException;
16+
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest;
17+
import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse;
18+
import software.amazon.awssdk.services.cloudformation.model.Output;
19+
import software.amazon.awssdk.services.cloudformation.model.Stack;
20+
import software.amazon.awssdk.services.cloudformation.waiters.CloudFormationAsyncWaiter;
21+
22+
import java.io.IOException;
23+
import java.net.URISyntaxException;
24+
import java.nio.file.Files;
25+
import java.nio.file.Path;
26+
import java.nio.file.Paths;
27+
import java.time.Duration;
28+
import java.util.HashMap;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.concurrent.CompletableFuture;
32+
33+
public class CloudFormationHelper {
34+
private static final String CFN_TEMPLATE = "ec2-stack.yaml";
35+
private static final Logger logger = LoggerFactory.getLogger(CloudFormationHelper.class);
36+
37+
private static CloudFormationAsyncClient cloudFormationClient;
38+
39+
private static CloudFormationAsyncClient getCloudFormationClient() {
40+
if (cloudFormationClient == null) {
41+
SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder()
42+
.maxConcurrency(100)
43+
.connectionTimeout(Duration.ofSeconds(60))
44+
.readTimeout(Duration.ofSeconds(60))
45+
.writeTimeout(Duration.ofSeconds(60))
46+
.build();
47+
48+
ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder()
49+
.apiCallTimeout(Duration.ofMinutes(2))
50+
.apiCallAttemptTimeout(Duration.ofSeconds(90))
51+
.retryStrategy(RetryMode.STANDARD)
52+
.build();
53+
54+
cloudFormationClient = CloudFormationAsyncClient.builder()
55+
.httpClient(httpClient)
56+
.region(Region.US_WEST_2)
57+
.overrideConfiguration(overrideConfig)
58+
.build();
59+
}
60+
return cloudFormationClient;
61+
}
62+
63+
public static void deployCloudFormationStack(String stackName) {
64+
String templateBody;
65+
boolean doesExist = describeStack(stackName);
66+
if (!doesExist) {
67+
try {
68+
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
69+
Path filePath = Paths.get(classLoader.getResource(CFN_TEMPLATE).toURI());
70+
templateBody = Files.readString(filePath);
71+
} catch (IOException | URISyntaxException e) {
72+
throw new RuntimeException(e);
73+
}
74+
75+
getCloudFormationClient().createStack(b -> b.stackName(stackName)
76+
.templateBody(templateBody)
77+
.capabilities(Capability.CAPABILITY_IAM))
78+
.whenComplete((csr, t) -> {
79+
if (csr != null) {
80+
System.out.println("Stack creation requested, ARN is " + csr.stackId());
81+
try (CloudFormationAsyncWaiter waiter = getCloudFormationClient().waiter()) {
82+
waiter.waitUntilStackCreateComplete(request -> request.stackName(stackName))
83+
.whenComplete((dsr, th) -> {
84+
if (th != null) {
85+
System.out.println("Error waiting for stack creation: " + th.getMessage());
86+
} else {
87+
dsr.matched().response().orElseThrow(() -> new RuntimeException("Failed to deploy"));
88+
System.out.println("Stack created successfully");
89+
}
90+
}).join();
91+
}
92+
} else {
93+
System.out.format("Error creating stack: " + t.getMessage(), t);
94+
throw new RuntimeException(t.getCause().getMessage(), t);
95+
}
96+
}).join();
97+
} else {
98+
logger.info("{} stack already exists", CFN_TEMPLATE);
99+
}
100+
}
101+
102+
// Check to see if the Stack exists before deploying it
103+
public static Boolean describeStack(String stackName) {
104+
try {
105+
CompletableFuture<?> future = getCloudFormationClient().describeStacks();
106+
DescribeStacksResponse stacksResponse = (DescribeStacksResponse) future.join();
107+
List<Stack> stacks = stacksResponse.stacks();
108+
for (Stack myStack : stacks) {
109+
if (myStack.stackName().compareTo(stackName) == 0) {
110+
return true;
111+
}
112+
}
113+
} catch (CloudFormationException e) {
114+
System.err.println(e.getMessage());
115+
}
116+
return false;
117+
}
118+
119+
public static void destroyCloudFormationStack(String stackName) {
120+
getCloudFormationClient().deleteStack(b -> b.stackName(stackName))
121+
.whenComplete((dsr, t) -> {
122+
if (dsr != null) {
123+
System.out.println("Delete stack requested ....");
124+
try (CloudFormationAsyncWaiter waiter = getCloudFormationClient().waiter()) {
125+
waiter.waitUntilStackDeleteComplete(request -> request.stackName(stackName))
126+
.whenComplete((waiterResponse, throwable) ->
127+
System.out.println("Stack deleted successfully."))
128+
.join();
129+
}
130+
} else {
131+
System.out.format("Error deleting stack: " + t.getMessage(), t);
132+
throw new RuntimeException(t.getCause().getMessage(), t);
133+
}
134+
}).join();
135+
}
136+
137+
public static CompletableFuture<Map<String, String>> getStackOutputsAsync(String stackName) {
138+
CloudFormationAsyncClient cloudFormationAsyncClient = getCloudFormationClient();
139+
140+
DescribeStacksRequest describeStacksRequest = DescribeStacksRequest.builder()
141+
.stackName(stackName)
142+
.build();
143+
144+
return cloudFormationAsyncClient.describeStacks(describeStacksRequest)
145+
.handle((describeStacksResponse, throwable) -> {
146+
if (throwable != null) {
147+
throw new RuntimeException("Failed to get stack outputs for: " + stackName, throwable);
148+
}
149+
150+
// Process the result
151+
if (describeStacksResponse.stacks().isEmpty()) {
152+
throw new RuntimeException("Stack not found: " + stackName);
153+
}
154+
155+
Stack stack = describeStacksResponse.stacks().get(0);
156+
Map<String, String> outputs = new HashMap<>();
157+
for (Output output : stack.outputs()) {
158+
outputs.put(output.outputKey(), output.outputValue());
159+
}
160+
161+
return outputs;
162+
});
163+
}
164+
}
165+

javav2/example_code/ssm/src/main/java/com/example/scenario/SSMActions.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ private static SsmAsyncClient getAsyncClient() {
8080
.build();
8181

8282
ssmAsyncClient = SsmAsyncClient.builder()
83-
.region(Region.US_EAST_1)
83+
.region(Region.US_WEST_2)
8484
.httpClient(httpClient)
8585
.overrideConfiguration(overrideConfig)
8686
.build();

javav2/example_code/ssm/src/main/java/com/example/scenario/SSMScenario.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,33 +7,34 @@
77
import software.amazon.awssdk.services.ssm.model.DocumentAlreadyExistsException;
88
import software.amazon.awssdk.services.ssm.model.SsmException;
99

10+
import java.util.Map;
1011
import java.util.Scanner;
1112
public class SSMScenario {
1213
public static final String DASHES = new String(new char[80]).replace("\0", "-");
14+
private static final String ROLES_STACK = "SsmStack3`1";
1315

1416
public static void main(String[] args) {
1517
String usage = """
1618
Usage:
17-
<instanceId> <title> <source> <category> <severity>
19+
<title> <source> <category> <severity>
1820
1921
Where:
20-
instanceId - The Amazon EC2 Linux/UNIX instance Id that AWS Systems Manager uses (ie, i-0149338494ed95f06).
2122
title - The title of the parameter (default is Disk Space Alert).
2223
source - The source of the parameter (default is EC2).
2324
category - The category of the parameter. Valid values are 'Availability', 'Cost', 'Performance', 'Recovery', 'Security' (default is Performance).
2425
severity - The severity of the parameter. Severity should be a number from 1 to 4 (default is 2).
2526
""";
2627

27-
if (args.length != 1) {
28-
System.out.println(usage);
29-
System.exit(1);
30-
}
31-
3228
Scanner scanner = new Scanner(System.in);
3329
SSMActions actions = new SSMActions();
3430
String documentName;
3531
String windowName;
36-
String instanceId = args[0];
32+
33+
System.out.println("Use AWS CloudFormation to create the EC2 instance that is required for this scenario.");
34+
CloudFormationHelper.deployCloudFormationStack(ROLES_STACK);
35+
Map<String, String> stackOutputs = CloudFormationHelper.getStackOutputsAsync(ROLES_STACK).join();
36+
String instanceId = stackOutputs.get("InstanceId");
37+
System.out.println("The Instance ID: " + instanceId +" was created.");
3738
String title = "Disk Space Alert" ;
3839
String source = "EC2" ;
3940
String category = "Availability" ;
@@ -230,7 +231,7 @@ This Java program demonstrates how to interact with AWS Systems Manager using th
230231
System.out.println("The AWS Systems Manager resources will not be deleted");
231232
}
232233
System.out.println(DASHES);
233-
234+
CloudFormationHelper.destroyCloudFormationStack(ROLES_STACK);
234235
System.out.println("This concludes the AWS Systems Manager SDK Basics scenario.");
235236
System.out.println(DASHES);
236237
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
Resources:
2+
SSMEC2Role116353F9:
3+
Type: AWS::IAM::Role
4+
Properties:
5+
AssumeRolePolicyDocument:
6+
Statement:
7+
- Action: sts:AssumeRole
8+
Effect: Allow
9+
Principal:
10+
Service: ec2.amazonaws.com
11+
Version: "2012-10-17"
12+
ManagedPolicyArns:
13+
- Fn::Join:
14+
- ""
15+
- - "arn:"
16+
- Ref: AWS::Partition
17+
- :iam::aws:policy/AmazonSSMManagedInstanceCore
18+
Metadata:
19+
aws:cdk:path: SsmStack3/SSMEC2Role/Resource
20+
EC2SecurityGroup05DEE054:
21+
Type: AWS::EC2::SecurityGroup
22+
Properties:
23+
GroupDescription: Allow SSH and SSM access
24+
SecurityGroupEgress:
25+
- CidrIp: 0.0.0.0/0
26+
Description: Allow all outbound traffic by default
27+
IpProtocol: "-1"
28+
SecurityGroupIngress:
29+
- CidrIp: 0.0.0.0/0
30+
Description: Allow SSH Access
31+
FromPort: 22
32+
IpProtocol: tcp
33+
ToPort: 22
34+
VpcId: vpc-573b5f2f
35+
Metadata:
36+
aws:cdk:path: SsmStack3/EC2SecurityGroup/Resource
37+
SSMInstanceInstanceProfileCEDAF98B:
38+
Type: AWS::IAM::InstanceProfile
39+
Properties:
40+
Roles:
41+
- Ref: SSMEC2Role116353F9
42+
Metadata:
43+
aws:cdk:path: SsmStack3/SSMInstance/InstanceProfile
44+
SSMInstance0FC4E7D0:
45+
Type: AWS::EC2::Instance
46+
Properties:
47+
AvailabilityZone: us-west-2a
48+
IamInstanceProfile:
49+
Ref: SSMInstanceInstanceProfileCEDAF98B
50+
ImageId:
51+
Ref: SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter
52+
InstanceType: t2.micro
53+
SecurityGroupIds:
54+
- Fn::GetAtt:
55+
- EC2SecurityGroup05DEE054
56+
- GroupId
57+
SubnetId: subnet-206a9c58
58+
Tags:
59+
- Key: Name
60+
Value: SsmStack3/SSMInstance
61+
UserData:
62+
Fn::Base64: |-
63+
#!/bin/bash
64+
sudo systemctl enable amazon-ssm-agent
65+
sudo systemctl start amazon-ssm-agent
66+
DependsOn:
67+
- SSMEC2Role116353F9
68+
Metadata:
69+
aws:cdk:path: SsmStack3/SSMInstance/Resource
70+
CDKMetadata:
71+
Type: AWS::CDK::Metadata
72+
Properties:
73+
Analytics: v2:deflate64:H4sIAAAAAAAA/22QTWvDMAyGf0t9HG7WFjZGbtkGI7u0tLuFMlRH7bQ6cuaPlmLy30e+WAs7GFkv0qtHWiTzp4dkNoGzm6ryONW0S+LGgzrKNToTrMJC3An5z9tKOLvPSFAlcW00FlGAc6HC8vki0ihqS6yoBp0pZQJ7kXaNfU3bkClPhju5kaIChgOWK6NJETqRFvFGu2R2qN02W/my59aijTk7D6xwZc2eNDYS1SKJG1TBkr+8WRPqIopTrUYArc0503oZ/M4ELkXqbUApSnTKUn3FFAWUZc4Hi86tg8aOSQHnrImxV/peZZixW8cNUwLTT8C8HK2a7mQ9+Q2bHPmLKGj4flxqHHwqUF/EmFdwGKW/VazRo+iuPUcGh/YVPAxnuz5W07TZCixU6NG2yTL4OvhGvsMJ7hfzZJY8Tr4d0dQG9lRhsu7jLxfV5p8zAgAA
74+
Metadata:
75+
aws:cdk:path: SsmStack3/CDKMetadata/Default
76+
Parameters:
77+
SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter:
78+
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
79+
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
80+
BootstrapVersion:
81+
Type: AWS::SSM::Parameter::Value<String>
82+
Default: /cdk-bootstrap/hnb659fds/version
83+
Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
84+
Outputs:
85+
InstanceId:
86+
Description: EC2 Instance ID (SSM-ready, safe to delete stack)
87+
Value:
88+
Ref: SSMInstance0FC4E7D0
89+

0 commit comments

Comments
 (0)