in src/main/java/software/awsutility/cloudformation/commandrunner/CreateHandler.java [54:373]
public ProgressEvent<ResourceModel, CallbackContext> handleRequest(
final AmazonWebServicesClientProxy proxy,
final ResourceHandlerRequest<ResourceModel> request,
final CallbackContext callbackContext,
final Logger logger) {
final ResourceModel model = request.getDesiredResourceState();
/*
INFO: 'model' has all the properties from the CFN resource i.e Triggers, Command, Role, and LogGroup
INFO: 'request' has information like region and AWS account ID
*/
if (callbackContext == null) {
AmazonCloudFormation stackbuilder = AmazonCloudFormationClientBuilder.standard()
.build();
Random random = new Random();
String generatedString = random.ints(97, 122 + 1)
.limit(10)
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
String stackName = "AWSUtility-CloudFormation-CommandRunner-"+generatedString;
try {
//Check if TerminateInstances is allowed
AWSSecurityTokenService stsClient = AWSSecurityTokenServiceClientBuilder.standard().build();
GetCallerIdentityRequest getCallerIdentityRequest = new GetCallerIdentityRequest();
GetCallerIdentityResult getCallerIdentityResult = proxy.injectCredentialsAndInvoke(getCallerIdentityRequest, stsClient::getCallerIdentity);
String roleArn = getCallerIdentityResult.getArn();
if (roleArn.contains("sts")) {
roleArn = roleArn.replace("sts", "iam");
}
if (roleArn.contains("assumed-role")) {
roleArn = roleArn.replace("assumed-role", "role");
}
if (StringUtils.countMatches(roleArn,"/") == 2) {
for (int i = roleArn.lastIndexOf("/"); i < roleArn.length(); i++)
roleArn = roleArn.replace(roleArn.substring(roleArn.lastIndexOf("/"), roleArn.length()), "");
}
AmazonIdentityManagement iamClient = AmazonIdentityManagementClientBuilder.standard().build();
SimulatePrincipalPolicyRequest simulatePrincipalPolicyRequest = new SimulatePrincipalPolicyRequest();
Collection<String> actions = new LinkedList<>();
actions.add("ec2:TerminateInstances");
simulatePrincipalPolicyRequest.setActionNames(actions);
simulatePrincipalPolicyRequest.setPolicySourceArn(roleArn);
SimulatePrincipalPolicyResult simulatePrincipalPolicyResult = proxy.injectCredentialsAndInvoke(simulatePrincipalPolicyRequest, iamClient::simulatePrincipalPolicy);
if ( simulatePrincipalPolicyResult.getEvaluationResults().get(0).getEvalDecision().contains("Deny") || simulatePrincipalPolicyResult.getEvaluationResults().get(0).getEvalDecision().equals("ExplicitDeny") || simulatePrincipalPolicyResult.getEvaluationResults().get(0).getEvalDecision().equals("ImplicitDeny") ) {
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.InvalidRequest)
.message("You do not have permissions to make the TerminateInstances API call. Please try again with the necessary permissions.")
.build();
}
InputStream in = CreateHandler.class.getResourceAsStream("/BaseTemplate.json");
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuilder out = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
out.append(line);
}
CreateStackRequest createRequest = new CreateStackRequest();
createRequest.setStackName(stackName);
createRequest.setTemplateBody(out.toString());
reader.close();
System.out.println("Creating a stack called " + createRequest.getStackName() + ".");
Collection<Parameter> parameters = new LinkedList<>();
Parameter AMIId = new Parameter();
AMIId.setParameterKey("AMIId");
//Dynamically get latest Amazon Linux 2 AMI for the region
AWSSimpleSystemsManagement simpleSystemsManagementClient = ((AWSSimpleSystemsManagementClientBuilder.standard())).build();
GetParameterRequest parameterRequest = new GetParameterRequest();
parameterRequest.withName("/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2").setWithDecryption(Boolean.valueOf(true));
GetParameterResult parameterResult = proxy.injectCredentialsAndInvoke(parameterRequest, simpleSystemsManagementClient::getParameter);
String parameterValue = parameterResult.getParameter().getValue();
AMIId.setParameterValue(parameterValue);
parameters.add(AMIId);
Parameter Command = new Parameter();
Command.setParameterKey("Command");
Command.setParameterValue(model.getCommand());
parameters.add(Command);
if (model.getRole() != null || model.getRole() == "") {
Parameter IamInstanceProfile = new Parameter();
IamInstanceProfile.setParameterKey("IamInstanceProfile");
IamInstanceProfile.setParameterValue(model.getRole());
parameters.add(IamInstanceProfile);
}
Parameter InstanceType = new Parameter();
InstanceType.setParameterKey("InstanceType");
//Note: HardCoded for now, will change in the future if the resource allows the customer to specify instance type.
InstanceType.setParameterValue(INSTANCE_TYPE);
parameters.add(InstanceType);
if (model.getLogGroup() != null || model.getLogGroup() == "") {
Parameter LogGroup = new Parameter();
LogGroup.setParameterKey("LogGroup");
LogGroup.setParameterValue(model.getLogGroup());
parameters.add(LogGroup);
}
//Dynamically gets both vpcId and subnetId
System.out.println(model.toString());
if ((model.getSubnetId() == null && model.getSecurityGroupId() == null) ||
(model.getSubnetId() == "" && model.getSecurityGroupId() == "")) { //Check if user provided the subnetId, if not get a default.
System.out.println("Inside dynamic creation workflow!");
Parameter SubnetId = new Parameter();
SubnetId.setParameterKey("SubnetId");
Parameter VpcId = new Parameter();
VpcId.setParameterKey("VpcId");
AmazonEC2 ec2 = AmazonEC2ClientBuilder.standard().build();
DescribeVpcsRequest describeVpcsRequest = new DescribeVpcsRequest();
describeVpcsRequest.withFilters(new Filter("isDefault").withValues("true"));
DescribeVpcsResult describeVpcsResult = proxy.<DescribeVpcsRequest,DescribeVpcsResult>injectCredentialsAndInvoke(describeVpcsRequest, ec2::describeVpcs);
String vpcId = describeVpcsResult.getVpcs().get(0).getVpcId();
if (vpcId == null || vpcId.isEmpty()) {
System.out.println("No default VPC found in this region, please specify a subnet using the NetworkConfiguration property.");
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.InvalidRequest)
.message("No default VPC found in this region, please specify a subnet using the NetworkConfiguration property.")
.build();
}
VpcId.setParameterValue(vpcId);
DescribeSubnetsRequest describeSubnetsRequest = new DescribeSubnetsRequest();
describeSubnetsRequest.withFilters(new Filter("vpc-id").withValues(vpcId));
DescribeSubnetsResult describeSubnetsResult = proxy.<DescribeSubnetsRequest,DescribeSubnetsResult>injectCredentialsAndInvoke(describeSubnetsRequest, ec2::describeSubnets);
String subnetId = describeSubnetsResult.getSubnets().get(describeSubnetsResult.getSubnets().size()-1).getSubnetId();
if (subnetId == null || subnetId.isEmpty()) {
System.out.println("Default VPC has no subnets. Please specify a subnet using the NetworkConfiguration property");
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.InvalidRequest)
.message("Default VPC has no subnets. Please specify a subnet using the NetworkConfiguration property.")
.build();
}
SubnetId.setParameterValue(subnetId);
System.out.println("SubnetId=" + SubnetId);
parameters.add(SubnetId);
parameters.add(VpcId);
}
//Both are provided
else if ((model.getSubnetId() != null && model.getSecurityGroupId() != null) ||
(model.getSubnetId() != "" && model.getSecurityGroupId() != "")) {
System.out.println("INSIDE BOTH ARE PROVIDED WORKFLOW.");
Parameter SubnetId = new Parameter();
SubnetId.setParameterKey("SubnetId");
SubnetId.setParameterValue(model.getSubnetId());
parameters.add(SubnetId);
Parameter SecurityGroupId = new Parameter();
SecurityGroupId.setParameterKey("SecurityGroupId");
//Note: HardCoded for now, will have to change in the future.
SecurityGroupId.setParameterValue(model.getSecurityGroupId());
parameters.add(SecurityGroupId);
}
//Subnet is provided, but not SecurityGroup. Infer VPC from Subnet and provide VPCId to CFN Stack
else if ((model.getSubnetId() != null && model.getSecurityGroupId() == null) ||
(model.getSubnetId() != "" && model.getSecurityGroupId() == "")) {
System.out.println("INSIDE SUBNET PROVIDED NO SECURITY GROUP WORKFLOW.");
Parameter SubnetId = new Parameter();
SubnetId.setParameterKey("SubnetId");
SubnetId.setParameterValue(model.getSubnetId());
parameters.add(SubnetId);
Parameter VpcId = new Parameter();
VpcId.setParameterKey("VpcId");
AmazonEC2 ec2 = AmazonEC2ClientBuilder.standard().build();
DescribeSubnetsRequest describeSubnetsRequest = new DescribeSubnetsRequest();
describeSubnetsRequest.withFilters(new Filter("subnet-id").withValues(model.getSubnetId()));
DescribeSubnetsResult describeSubnetsResult = proxy.<DescribeSubnetsRequest,DescribeSubnetsResult>injectCredentialsAndInvoke(describeSubnetsRequest, ec2::describeSubnets);
String vpcId = describeSubnetsResult.getSubnets().get(0).getVpcId();
VpcId.setParameterValue(vpcId);
parameters.add(VpcId);
}
else if ((model.getSubnetId() == null && model.getSecurityGroupId() != null) ||
(model.getSubnetId() == "") && model.getSecurityGroupId() != "") {
System.out.println("No SubnetId provided, when using SecurityGroupId, property SubnetId is required.");
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.InvalidRequest)
.message("No SubnetId provided, when using SecurityGroupId, property SubnetId is required.")
.build();
}
createRequest.setParameters(parameters);
System.out.println(createRequest.getParameters().toString());
//Inject creds and call instead
proxy.injectCredentialsAndInvoke(createRequest, stackbuilder::createStack);
//If CallbackContext coming in is null, always create stack and set OperationStatus.IN_PROGRESS
model.setId(generatedString);
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.resourceModel(model)
.callbackContext(CallbackContext.builder().stackName(stackName).stackId(generatedString).build())
.status(OperationStatus.IN_PROGRESS)
.callbackDelaySeconds(90)
.build();
} catch (AmazonServiceException ase) {
System.out.println("Caught an AmazonServiceException, which means your request made it "
+ "to AWS CloudFormation, but was rejected with an error response for some reason.");
System.out.println("Error Message: " + ase.getMessage());
System.out.println("HTTP Status Code: " + ase.getStatusCode());
System.out.println("AWS Error Code: " + ase.getErrorCode());
System.out.println("Error Type: " + ase.getErrorType());
System.out.println("Request ID: " + ase.getRequestId());
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.InternalFailure)
.message(ase.getMessage() + " " + ase.getStatusCode() + " " + ase.getErrorCode() + " " + ase.getErrorType() + " " + ase.getRequestId())
.build();
} catch (AmazonClientException ace) {
System.out.println("Caught an AmazonClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with AWS CloudFormation, "
+ "such as not being able to access the network.");
System.out.println("Error Message: " + ace.getMessage());
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.InternalFailure)
.message(ace.getMessage())
.build();
} catch (IOException e) {
e.printStackTrace();
}
} else {
AmazonCloudFormation stackbuilder = AmazonCloudFormationClientBuilder.standard()
.build();
//From context check the status of the stack by looking up the stackName property.
String stackName = callbackContext.getStackName();
DescribeStacksRequest wait = new DescribeStacksRequest();
wait.setStackName(stackName);
String stackStatus = "Unknown";
String stackReason = "";
List<Stack> stacks = proxy.<DescribeStacksRequest,DescribeStacksResult>injectCredentialsAndInvoke(wait, stackbuilder::describeStacks).getStacks();
if (
stacks.get(0).getStackStatus().equals(StackStatus.CREATE_COMPLETE.toString()) ||
stacks.get(0).getStackStatus().equals(StackStatus.CREATE_FAILED.toString()) ||
stacks.get(0).getStackStatus().equals(StackStatus.ROLLBACK_FAILED.toString()) ||
stacks.get(0).getStackStatus().equals(StackStatus.ROLLBACK_COMPLETE.toString()) ||
stacks.get(0).getStackStatus().equals(StackStatus.DELETE_FAILED.toString())
) {
stackStatus = stacks.get(0).getStackStatus();
stackReason = stacks.get(0).getStackStatusReason();
String returnString = stackStatus + " (" + stackReason + ")";
System.out.println(returnString);
if(stacks.get(0).getStackStatus().equals(StackStatus.CREATE_COMPLETE.toString())) {
model.setId(callbackContext.getStackId());
model.setOutput(stacks.get(0).getOutputs().get(0).getOutputValue());
//DELETE Stack and terminate EC2 instance.
AmazonCloudFormation stackBuilder = AmazonCloudFormationClientBuilder.standard()
.build();
DeleteStackRequest deleteRequest = new DeleteStackRequest();
deleteRequest.setStackName(stackName);
proxy.injectCredentialsAndInvoke(deleteRequest, stackBuilder::deleteStack);
//Make new SSM Parameter with Key=Id and Value=Output
AWSSimpleSystemsManagement simpleSystemsManagementClient = ((AWSSimpleSystemsManagementClientBuilder.standard())).build();
PutParameterRequest parameterRequest = new PutParameterRequest();
parameterRequest.setName(callbackContext.getStackId());
parameterRequest.setValue(stacks.get(0).getOutputs().get(0).getOutputValue());
parameterRequest.setType("SecureString");
if (model.getKeyId() != null || model.getKeyId() != "") {
parameterRequest.setKeyId(model.getKeyId());
}
PutParameterResult parameterResult = proxy.injectCredentialsAndInvoke(parameterRequest, simpleSystemsManagementClient::putParameter);
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.resourceModel(model)
.status(OperationStatus.SUCCESS)
.build();
}
if (stacks.get(0).getStackStatus().equals(StackStatus.CREATE_FAILED.toString()) ||
stacks.get(0).getStackStatus().equals(StackStatus.ROLLBACK_COMPLETE)){
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.NotStabilized)
.message("Command failed to execute. Please check CloudWatch Logs and the events in the CommandRunner Stack " + stacks.get(0).getStackName())
.build();
}
} else {
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.resourceModel(model)
.status(OperationStatus.IN_PROGRESS)
.callbackContext(CallbackContext.builder().stackName(stackName).stackId(callbackContext.getStackId()).build())
.callbackDelaySeconds(30)
.build();
}
}
//It should never reach this code, if it does something went wrong, so it returns internal failure.
return ProgressEvent.<ResourceModel, CallbackContext>builder()
.status(OperationStatus.FAILED)
.errorCode(HandlerErrorCode.InternalFailure)
.message("Internal Failure")
.build();
}