diff --git a/javav2/example_code/ssm/pom.xml b/javav2/example_code/ssm/pom.xml index 92529ae80a2..6f9a35d21fd 100644 --- a/javav2/example_code/ssm/pom.xml +++ b/javav2/example_code/ssm/pom.xml @@ -89,6 +89,10 @@ slf4j-api 2.0.13 + + software.amazon.awssdk + cloudformation + org.apache.logging.log4j log4j-slf4j2-impl diff --git a/javav2/example_code/ssm/src/main/java/com/example/scenario/CloudFormationHelper.java b/javav2/example_code/ssm/src/main/java/com/example/scenario/CloudFormationHelper.java new file mode 100644 index 00000000000..91a83befabf --- /dev/null +++ b/javav2/example_code/ssm/src/main/java/com/example/scenario/CloudFormationHelper.java @@ -0,0 +1,165 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + +package com.example.scenario; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; +import software.amazon.awssdk.core.retry.RetryMode; +import software.amazon.awssdk.http.async.SdkAsyncHttpClient; +import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.cloudformation.CloudFormationAsyncClient; +import software.amazon.awssdk.services.cloudformation.model.Capability; +import software.amazon.awssdk.services.cloudformation.model.CloudFormationException; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksRequest; +import software.amazon.awssdk.services.cloudformation.model.DescribeStacksResponse; +import software.amazon.awssdk.services.cloudformation.model.Output; +import software.amazon.awssdk.services.cloudformation.model.Stack; +import software.amazon.awssdk.services.cloudformation.waiters.CloudFormationAsyncWaiter; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class CloudFormationHelper { + private static final String CFN_TEMPLATE = "ec2-stack.yaml"; + private static final Logger logger = LoggerFactory.getLogger(CloudFormationHelper.class); + + private static CloudFormationAsyncClient cloudFormationClient; + + private static CloudFormationAsyncClient getCloudFormationClient() { + if (cloudFormationClient == null) { + SdkAsyncHttpClient httpClient = NettyNioAsyncHttpClient.builder() + .maxConcurrency(100) + .connectionTimeout(Duration.ofSeconds(60)) + .readTimeout(Duration.ofSeconds(60)) + .writeTimeout(Duration.ofSeconds(60)) + .build(); + + ClientOverrideConfiguration overrideConfig = ClientOverrideConfiguration.builder() + .apiCallTimeout(Duration.ofMinutes(2)) + .apiCallAttemptTimeout(Duration.ofSeconds(90)) + .retryStrategy(RetryMode.STANDARD) + .build(); + + cloudFormationClient = CloudFormationAsyncClient.builder() + .httpClient(httpClient) + .region(Region.US_WEST_2) + .overrideConfiguration(overrideConfig) + .build(); + } + return cloudFormationClient; + } + + public static void deployCloudFormationStack(String stackName) { + String templateBody; + boolean doesExist = describeStack(stackName); + if (!doesExist) { + try { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + Path filePath = Paths.get(classLoader.getResource(CFN_TEMPLATE).toURI()); + templateBody = Files.readString(filePath); + } catch (IOException | URISyntaxException e) { + throw new RuntimeException(e); + } + + getCloudFormationClient().createStack(b -> b.stackName(stackName) + .templateBody(templateBody) + .capabilities(Capability.CAPABILITY_IAM)) + .whenComplete((csr, t) -> { + if (csr != null) { + System.out.println("Stack creation requested, ARN is " + csr.stackId()); + try (CloudFormationAsyncWaiter waiter = getCloudFormationClient().waiter()) { + waiter.waitUntilStackCreateComplete(request -> request.stackName(stackName)) + .whenComplete((dsr, th) -> { + if (th != null) { + System.out.println("Error waiting for stack creation: " + th.getMessage()); + } else { + dsr.matched().response().orElseThrow(() -> new RuntimeException("Failed to deploy")); + System.out.println("Stack created successfully"); + } + }).join(); + } + } else { + System.out.format("Error creating stack: " + t.getMessage(), t); + throw new RuntimeException(t.getCause().getMessage(), t); + } + }).join(); + } else { + logger.info("{} stack already exists", CFN_TEMPLATE); + } + } + + // Check to see if the Stack exists before deploying it + public static Boolean describeStack(String stackName) { + try { + CompletableFuture future = getCloudFormationClient().describeStacks(); + DescribeStacksResponse stacksResponse = (DescribeStacksResponse) future.join(); + List stacks = stacksResponse.stacks(); + for (Stack myStack : stacks) { + if (myStack.stackName().compareTo(stackName) == 0) { + return true; + } + } + } catch (CloudFormationException e) { + System.err.println(e.getMessage()); + } + return false; + } + + public static void destroyCloudFormationStack(String stackName) { + getCloudFormationClient().deleteStack(b -> b.stackName(stackName)) + .whenComplete((dsr, t) -> { + if (dsr != null) { + System.out.println("Delete stack requested ...."); + try (CloudFormationAsyncWaiter waiter = getCloudFormationClient().waiter()) { + waiter.waitUntilStackDeleteComplete(request -> request.stackName(stackName)) + .whenComplete((waiterResponse, throwable) -> + System.out.println("Stack deleted successfully.")) + .join(); + } + } else { + System.out.format("Error deleting stack: " + t.getMessage(), t); + throw new RuntimeException(t.getCause().getMessage(), t); + } + }).join(); + } + + public static CompletableFuture> getStackOutputsAsync(String stackName) { + CloudFormationAsyncClient cloudFormationAsyncClient = getCloudFormationClient(); + + DescribeStacksRequest describeStacksRequest = DescribeStacksRequest.builder() + .stackName(stackName) + .build(); + + return cloudFormationAsyncClient.describeStacks(describeStacksRequest) + .handle((describeStacksResponse, throwable) -> { + if (throwable != null) { + throw new RuntimeException("Failed to get stack outputs for: " + stackName, throwable); + } + + // Process the result + if (describeStacksResponse.stacks().isEmpty()) { + throw new RuntimeException("Stack not found: " + stackName); + } + + Stack stack = describeStacksResponse.stacks().get(0); + Map outputs = new HashMap<>(); + for (Output output : stack.outputs()) { + outputs.put(output.outputKey(), output.outputValue()); + } + + return outputs; + }); + } +} + diff --git a/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMActions.java b/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMActions.java index b585c2cb46b..dca0db676ff 100644 --- a/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMActions.java +++ b/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMActions.java @@ -80,7 +80,7 @@ private static SsmAsyncClient getAsyncClient() { .build(); ssmAsyncClient = SsmAsyncClient.builder() - .region(Region.US_EAST_1) + .region(Region.US_WEST_2) .httpClient(httpClient) .overrideConfiguration(overrideConfig) .build(); diff --git a/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMScenario.java b/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMScenario.java index d5553600a59..3a8107cc3f0 100644 --- a/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMScenario.java +++ b/javav2/example_code/ssm/src/main/java/com/example/scenario/SSMScenario.java @@ -7,33 +7,34 @@ import software.amazon.awssdk.services.ssm.model.DocumentAlreadyExistsException; import software.amazon.awssdk.services.ssm.model.SsmException; +import java.util.Map; import java.util.Scanner; public class SSMScenario { public static final String DASHES = new String(new char[80]).replace("\0", "-"); + private static final String ROLES_STACK = "SsmStack3`1"; public static void main(String[] args) { String usage = """ Usage: - <source> <category> <severity> + <title> <source> <category> <severity> Where: - instanceId - The Amazon EC2 Linux/UNIX instance Id that AWS Systems Manager uses (ie, i-0149338494ed95f06). title - The title of the parameter (default is Disk Space Alert). source - The source of the parameter (default is EC2). category - The category of the parameter. Valid values are 'Availability', 'Cost', 'Performance', 'Recovery', 'Security' (default is Performance). severity - The severity of the parameter. Severity should be a number from 1 to 4 (default is 2). """; - if (args.length != 1) { - System.out.println(usage); - System.exit(1); - } - Scanner scanner = new Scanner(System.in); SSMActions actions = new SSMActions(); String documentName; String windowName; - String instanceId = args[0]; + + System.out.println("Use AWS CloudFormation to create the EC2 instance that is required for this scenario."); + CloudFormationHelper.deployCloudFormationStack(ROLES_STACK); + Map<String, String> stackOutputs = CloudFormationHelper.getStackOutputsAsync(ROLES_STACK).join(); + String instanceId = stackOutputs.get("InstanceId"); + System.out.println("The Instance ID: " + instanceId +" was created."); String title = "Disk Space Alert" ; String source = "EC2" ; String category = "Availability" ; @@ -230,7 +231,7 @@ This Java program demonstrates how to interact with AWS Systems Manager using th System.out.println("The AWS Systems Manager resources will not be deleted"); } System.out.println(DASHES); - + CloudFormationHelper.destroyCloudFormationStack(ROLES_STACK); System.out.println("This concludes the AWS Systems Manager SDK Basics scenario."); System.out.println(DASHES); } diff --git a/javav2/example_code/ssm/src/main/resources/ec2-stack.yaml b/javav2/example_code/ssm/src/main/resources/ec2-stack.yaml new file mode 100644 index 00000000000..2836666cbe0 --- /dev/null +++ b/javav2/example_code/ssm/src/main/resources/ec2-stack.yaml @@ -0,0 +1,89 @@ +Resources: + SSMEC2Role116353F9: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Statement: + - Action: sts:AssumeRole + Effect: Allow + Principal: + Service: ec2.amazonaws.com + Version: "2012-10-17" + ManagedPolicyArns: + - Fn::Join: + - "" + - - "arn:" + - Ref: AWS::Partition + - :iam::aws:policy/AmazonSSMManagedInstanceCore + Metadata: + aws:cdk:path: SsmStack3/SSMEC2Role/Resource + EC2SecurityGroup05DEE054: + Type: AWS::EC2::SecurityGroup + Properties: + GroupDescription: Allow SSH and SSM access + SecurityGroupEgress: + - CidrIp: 0.0.0.0/0 + Description: Allow all outbound traffic by default + IpProtocol: "-1" + SecurityGroupIngress: + - CidrIp: 0.0.0.0/0 + Description: Allow SSH Access + FromPort: 22 + IpProtocol: tcp + ToPort: 22 + VpcId: vpc-573b5f2f + Metadata: + aws:cdk:path: SsmStack3/EC2SecurityGroup/Resource + SSMInstanceInstanceProfileCEDAF98B: + Type: AWS::IAM::InstanceProfile + Properties: + Roles: + - Ref: SSMEC2Role116353F9 + Metadata: + aws:cdk:path: SsmStack3/SSMInstance/InstanceProfile + SSMInstance0FC4E7D0: + Type: AWS::EC2::Instance + Properties: + AvailabilityZone: us-west-2a + IamInstanceProfile: + Ref: SSMInstanceInstanceProfileCEDAF98B + ImageId: + Ref: SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter + InstanceType: t2.micro + SecurityGroupIds: + - Fn::GetAtt: + - EC2SecurityGroup05DEE054 + - GroupId + SubnetId: subnet-206a9c58 + Tags: + - Key: Name + Value: SsmStack3/SSMInstance + UserData: + Fn::Base64: |- + #!/bin/bash + sudo systemctl enable amazon-ssm-agent + sudo systemctl start amazon-ssm-agent + DependsOn: + - SSMEC2Role116353F9 + Metadata: + aws:cdk:path: SsmStack3/SSMInstance/Resource + CDKMetadata: + Type: AWS::CDK::Metadata + Properties: + Analytics: v2:deflate64:H4sIAAAAAAAA/22QTWvDMAyGf0t9HG7WFjZGbtkGI7u0tLuFMlRH7bQ6cuaPlmLy30e+WAs7GFkv0qtHWiTzp4dkNoGzm6ryONW0S+LGgzrKNToTrMJC3An5z9tKOLvPSFAlcW00FlGAc6HC8vki0ihqS6yoBp0pZQJ7kXaNfU3bkClPhju5kaIChgOWK6NJETqRFvFGu2R2qN02W/my59aijTk7D6xwZc2eNDYS1SKJG1TBkr+8WRPqIopTrUYArc0503oZ/M4ELkXqbUApSnTKUn3FFAWUZc4Hi86tg8aOSQHnrImxV/peZZixW8cNUwLTT8C8HK2a7mQ9+Q2bHPmLKGj4flxqHHwqUF/EmFdwGKW/VazRo+iuPUcGh/YVPAxnuz5W07TZCixU6NG2yTL4OvhGvsMJ7hfzZJY8Tr4d0dQG9lRhsu7jLxfV5p8zAgAA + Metadata: + aws:cdk:path: SsmStack3/CDKMetadata/Default +Parameters: + SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter: + Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id> + Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2 + BootstrapVersion: + Type: AWS::SSM::Parameter::Value<String> + Default: /cdk-bootstrap/hnb659fds/version + Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip] +Outputs: + InstanceId: + Description: EC2 Instance ID (SSM-ready, safe to delete stack) + Value: + Ref: SSMInstance0FC4E7D0 + diff --git a/javav2/example_code/ssm/src/test/java/AWSSSMTest.java b/javav2/example_code/ssm/src/test/java/AWSSSMTest.java index fe479eefcdb..4ae97f4fa56 100644 --- a/javav2/example_code/ssm/src/test/java/AWSSSMTest.java +++ b/javav2/example_code/ssm/src/test/java/AWSSSMTest.java @@ -1,6 +1,7 @@ // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 +import com.example.scenario.CloudFormationHelper; import com.example.scenario.SSMActions; import com.example.scenario.SSMScenario; import com.example.ssm.*; @@ -18,6 +19,7 @@ import java.text.SimpleDateFormat; import java.util.Date; +import java.util.Map; /** * To run these integration tests, you must set the required values @@ -36,12 +38,12 @@ public class AWSSSMTest { private static String category = ""; private static String severity = ""; private static String opsItemId = ""; - + private static final String ROLES_STACK = "SsmStack3"; private static String account = ""; @BeforeAll public static void setUp() { - Region region = Region.US_EAST_1; + Region region = Region.US_WEST_2; ssmClient = SsmClient.builder() .region(region) .build(); @@ -56,8 +58,12 @@ public static void setUp() { source = values.getSource(); category = values.getCategory(); account = values.getAccount(); - instance = values.getInstanceId(); severity = values.getSeverity(); + + CloudFormationHelper.deployCloudFormationStack(ROLES_STACK); + Map<String, String> stackOutputs = CloudFormationHelper.getStackOutputsAsync(ROLES_STACK).join(); + instance = stackOutputs.get("InstanceId"); + logger.info("The Instance ID: " + instance +" was created."); } @Test @@ -68,17 +74,10 @@ public void testHelloSSM() { logger.info("Integration Test 1 passed"); } - @Test - @Tag("IntegrationTest") - @Order(2) - public void testGetParameter() { - assertDoesNotThrow(() -> GetParameter.getParaValue(ssmClient, paraName)); - logger.info("Integration Test 2 passed"); - } @Test @Tag("IntegrationTest") - @Order(3) + @Order(2) public void testInvokeScenario() { SSMActions actions = new SSMActions(); String currentDateTime = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); @@ -104,7 +103,8 @@ public void testInvokeScenario() { assertDoesNotThrow(() -> actions.deleteDoc(documentName)); assertDoesNotThrow(() -> actions.deleteMaintenanceWindow(maintenanceWindowId)); - logger.info("Test 3 passed"); + CloudFormationHelper.destroyCloudFormationStack(ROLES_STACK); + logger.info("Test 2 passed"); } private static String getSecretValues() { diff --git a/scenarios/basics/ssm/README.md b/scenarios/basics/ssm/README.md index 521dbd23580..06e68e216ae 100644 --- a/scenarios/basics/ssm/README.md +++ b/scenarios/basics/ssm/README.md @@ -4,7 +4,9 @@ This AWS Systems Manager getting started scenario demonstrates how to interact with the AWS Systems Manager service using an AWS SDK. The scenario covers various operations such as creating a maintenance window, creating an SSM document, sending a command to a managed node, creating an OpsItem, updating an OpsItem, and deleting SSM resources. ## Setting up Resources -AWS Systems Manager Agent is Amazon software that runs on Amazon Elastic Compute Cloud (Amazon EC2) instances, edge devices, on-premises servers, and virtual machines (VMs). A Systems Manager Agent makes it possible for Systems Manager to update, manage, and configure these resources. To successfully run this getting started scenario, the EC2 instance must have a Systems Manager Agent. For more information, see [Working with SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html). +AWS Systems Manager Agent is Amazon software that runs on Amazon Elastic Compute Cloud (Amazon EC2) instances, edge devices, on-premises servers, and virtual machines (VMs). A Systems Manager Agent makes it possible for Systems Manager to update, manage, and configure these resources. To successfully run this getting started scenario, the EC2 instance must have a Systems Manager Agent. The CDK script that is executed during the Scenario creates an EC2 instance that has a Systems Manager Agent. + +For more information, see [Working with SSM Agent](https://docs.aws.amazon.com/systems-manager/latest/userguide/ssm-agent.html). ## Service Operations Invoked The program performs the following tasks: