google-site-verification: google61fe8ba583a51912.html
CloudFormation with Java SDK in Java

A comprehensive Java library for working with AWS CloudFormation using the AWS Java SDK v2, providing type-safe templates, deployment management, and advanced features.

Complete Implementation

1. Core CloudFormation Client Wrapper

package com.cloudformation.core; import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; import software.amazon.awssdk.core.SdkBytes; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.regions.Region; import software.amazon.awssdk.services.cloudformation.CloudFormationClient; import software.amazon.awssdk.services.cloudformation.model.*; import software.amazon.awssdk.services.s3.S3Client; import software.amazon.awssdk.services.s3.model.PutObjectRequest; import java.io.*; import java.nio.file.*; import java.time.Duration; import java.util.*; import java.util.concurrent.*; import java.util.stream.Collectors; /** * Core CloudFormation service wrapper */ public class CloudFormationService { private final CloudFormationClient cfClient; private final S3Client s3Client; private final String bucketName; public CloudFormationService(Region region, AwsCredentialsProvider credentialsProvider, String bucketName) { this.cfClient = CloudFormationClient.builder() .region(region) .credentialsProvider(credentialsProvider) .build(); this.s3Client = S3Client.builder() .region(region) .credentialsProvider(credentialsProvider) .build(); this.bucketName = bucketName; } public CloudFormationService(CloudFormationClient cfClient, S3Client s3Client, String bucketName) { this.cfClient = cfClient; this.s3Client = s3Client; this.bucketName = bucketName; } /** * Create or update a CloudFormation stack */ public StackOperationResult createOrUpdateStack(StackDefinition stackDef) { try { // Check if stack exists if (stackExists(stackDef.getStackName())) { return updateStack(stackDef); } else { return createStack(stackDef); } } catch (Exception e) { throw new CloudFormationException("Failed to create/update stack: " + stackDef.getStackName(), e); } } /** * Create a new CloudFormation stack */ public StackOperationResult createStack(StackDefinition stackDef) { try { String templateUrl = uploadTemplateToS3(stackDef); CreateStackRequest.Builder requestBuilder = CreateStackRequest.builder() .stackName(stackDef.getStackName()) .templateURL(templateUrl) .capabilitiesWithStrings(stackDef.getCapabilities()) .parameters(createParameters(stackDef.getParameters())) .tags(createTags(stackDef.getTags())) .onFailure(OnFailure.DELETE); if (stackDef.getTimeout() != null) { requestBuilder.timeoutInMinutes(stackDef.getTimeout().toMinutes()); } if (stackDef.getRoleArn() != null) { requestBuilder.roleARN(stackDef.getRoleArn()); } if (stackDef.getNotificationArns() != null && !stackDef.getNotificationArns().isEmpty()) { requestBuilder.notificationARNs(stackDef.getNotificationArns()); } CreateStackResponse response = cfClient.createStack(requestBuilder.build()); System.info("Initiated stack creation: " + stackDef.getStackName()); return waitForStackOperation(stackDef.getStackName(), "CREATE"); } catch (CloudFormationException e) { throw e; } catch (Exception e) { throw new CloudFormationException("Failed to create stack: " + stackDef.getStackName(), e); } } /** * Update an existing CloudFormation stack */ public StackOperationResult updateStack(StackDefinition stackDef) { try { String templateUrl = uploadTemplateToS3(stackDef); UpdateStackRequest.Builder requestBuilder = UpdateStackRequest.builder() .stackName(stackDef.getStackName()) .templateURL(templateUrl) .capabilitiesWithStrings(stackDef.getCapabilities()) .parameters(createParameters(stackDef.getParameters())) .tags(createTags(stackDef.getTags())); if (stackDef.getRoleArn() != null) { requestBuilder.roleARN(stackDef.getRoleArn()); } if (stackDef.getNotificationArns() != null && !stackDef.getNotificationArns().isEmpty()) { requestBuilder.notificationARNs(stackDef.getNotificationArns()); } UpdateStackResponse response = cfClient.updateStack(requestBuilder.build()); System.info("Initiated stack update: " + stackDef.getStackName()); return waitForStackOperation(stackDef.getStackName(), "UPDATE"); } catch (CloudFormationException e) { throw e; } catch (Exception e) { throw new CloudFormationException("Failed to update stack: " + stackDef.getStackName(), e); } } /** * Delete a CloudFormation stack */ public StackOperationResult deleteStack(String stackName) { try { DeleteStackRequest request = DeleteStackRequest.builder() .stackName(stackName) .build(); cfClient.deleteStack(request); System.info("Initiated stack deletion: " + stackName); return waitForStackOperation(stackName, "DELETE"); } catch (Exception e) { throw new CloudFormationException("Failed to delete stack: " + stackName, e); } } /** * Validate CloudFormation template */ public TemplateValidationResult validateTemplate(String templateBody) { try { ValidateTemplateRequest request = ValidateTemplateRequest.builder() .templateBody(templateBody) .build(); ValidateTemplateResponse response = cfClient.validateTemplate(request); return TemplateValidationResult.builder() .valid(true) .description(response.description()) .parameters(response.parameters()) .capabilities(response.capabilities()) .build(); } catch (Exception e) { return TemplateValidationResult.builder() .valid(false) .errorMessage(e.getMessage()) .build(); } } /** * Get stack details */ public StackDetail getStackDetail(String stackName) { try { DescribeStacksRequest request = DescribeStacksRequest.builder() .stackName(stackName) .build(); DescribeStacksResponse response = cfClient.describeStacks(request); if (response.stacks().isEmpty()) { throw new CloudFormationException("Stack not found: " + stackName); } Stack stack = response.stacks().get(0); return mapStackToDetail(stack); } catch (Exception e) { throw new CloudFormationException("Failed to get stack details: " + stackName, e); } } /** * List all stacks with optional filtering */ public List<StackSummary> listStacks(StackStatus... statusFilters) { try { ListStacksRequest.Builder requestBuilder = ListStacksRequest.builder(); if (statusFilters.length > 0) { requestBuilder.stackStatusFilters(Arrays.asList(statusFilters)); } ListStacksResponse response = cfClient.listStacks(requestBuilder.build()); return response.stackSummaries().stream() .map(this::mapStackSummary) .collect(Collectors.toList()); } catch (Exception e) { throw new CloudFormationException("Failed to list stacks", e); } } /** * Get stack events */ public List<StackEvent> getStackEvents(String stackName) { try { DescribeStackEventsRequest request = DescribeStackEventsRequest.builder() .stackName(stackName) .build(); DescribeStackEventsResponse response = cfClient.describeStackEvents(request); return response.stackEvents().stream() .map(this::mapStackEvent) .collect(Collectors.toList()); } catch (Exception e) { throw new CloudFormationException("Failed to get stack events: " + stackName, e); } } /** * Get stack resources */ public List<StackResource> getStackResources(String stackName) { try { DescribeStackResourcesRequest request = DescribeStackResourcesRequest.builder() .stackName(stackName) .build(); DescribeStackResourcesResponse response = cfClient.describeStackResources(request); return response.stackResources().stream() .map(this::mapStackResource) .collect(Collectors.toList()); } catch (Exception e) { throw new CloudFormationException("Failed to get stack resources: " + stackName, e); } } /** * Execute change set */ public ChangeSetResult executeChangeSet(String changeSetName) { try { ExecuteChangeSetRequest request = ExecuteChangeSetRequest.builder() .changeSetName(changeSetName) .build(); ExecuteChangeSetResponse response = cfClient.executeChangeSet(request); // Wait for execution to complete String stackName = extractStackNameFromChangeSet(changeSetName); StackOperationResult result = waitForStackOperation(stackName, "UPDATE"); return ChangeSetResult.builder() .successful(result.isSuccessful()) .stackId(result.getStackId()) .status(result.getStatus()) .build(); } catch (Exception e) { throw new CloudFormationException("Failed to execute change set: " + changeSetName, e); } } private boolean stackExists(String stackName) { try { getStackDetail(stackName); return true; } catch (CloudFormationException e) { if (e.getMessage().contains("Stack not found")) { return false; } throw e; } } private String uploadTemplateToS3(StackDefinition stackDef) { try { String templateKey = "cloudformation-templates/" + stackDef.getStackName() + "-" + System.currentTimeMillis() + ".json"; // Upload template to S3 PutObjectRequest putRequest = PutObjectRequest.builder() .bucket(bucketName) .key(templateKey) .build(); s3Client.putObject(putRequest, RequestBody.fromBytes(stackDef.getTemplateBody().getBytes())); return String.format("https://%s.s3.%s.amazonaws.com/%s", bucketName, cfClient.serviceClientConfiguration().region(), templateKey); } catch (Exception e) { throw new CloudFormationException("Failed to upload template to S3", e); } } private List<Parameter> createParameters(Map<String, String> parameters) { if (parameters == null || parameters.isEmpty()) { return Collections.emptyList(); } return parameters.entrySet().stream() .map(entry -> Parameter.builder() .parameterKey(entry.getKey()) .parameterValue(entry.getValue()) .build()) .collect(Collectors.toList()); } private List<Tag> createTags(Map<String, String> tags) { if (tags == null || tags.isEmpty()) { return Collections.emptyList(); } return tags.entrySet().stream() .map(entry -> Tag.builder() .key(entry.getKey()) .value(entry.getValue()) .build()) .collect(Collectors.toList()); } private StackOperationResult waitForStackOperation(String stackName, String operationType) { StackStatus finalStatus = waitForStackCompletion(stackName); boolean successful = isSuccessfulStatus(finalStatus); String statusReason = getStackStatusReason(stackName); return StackOperationResult.builder() .stackName(stackName) .operationType(operationType) .status(finalStatus) .successful(successful) .statusReason(statusReason) .build(); } private StackStatus waitForStackCompletion(String stackName) { int maxAttempts = 120; // 60 minutes with 30-second intervals int attempt = 0; while (attempt < maxAttempts) { try { StackDetail stackDetail = getStackDetail(stackName); StackStatus status = stackDetail.getStatus(); if (isTerminalStatus(status)) { return status; } // Print progress for long-running operations if (attempt % 10 == 0) { // Every 5 minutes System.info("Stack " + stackName + " status: " + status + " (" + (attempt * 30) + " seconds elapsed)"); } Thread.sleep(30000); // Wait 30 seconds attempt++; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new CloudFormationException("Stack operation interrupted", e); } catch (Exception e) { // If stack doesn't exist anymore (DELETE_COMPLETE), we can't describe it if (e.getMessage().contains("Stack not found") && operationType.equals("DELETE")) { return StackStatus.DELETE_COMPLETE; } throw new CloudFormationException("Error waiting for stack completion", e); } } throw new CloudFormationException("Stack operation timeout for: " + stackName); } private boolean isTerminalStatus(StackStatus status) { return status.toString().endsWith("_COMPLETE") || status.toString().endsWith("_FAILED") || status.toString().endsWith("_ROLLBACK_COMPLETE"); } private boolean isSuccessfulStatus(StackStatus status) { return status == StackStatus.CREATE_COMPLETE || status == StackStatus.UPDATE_COMPLETE || status == StackStatus.DELETE_COMPLETE || status == StackStatus.UPDATE_ROLLBACK_COMPLETE; } private String getStackStatusReason(String stackName) { try { StackDetail detail = getStackDetail(stackName); return detail.getStatusReason(); } catch (Exception e) { return "Unable to retrieve status reason"; } } private String extractStackNameFromChangeSet(String changeSetName) { // Change set names are typically in format: stackname-changeset-timestamp // or arn:aws:cloudformation:region:account:changeSet/changeset-name if (changeSetName.contains(":changeSet/")) { return changeSetName.split("/")[1].split("-")[0]; } else { return changeSetName.split("-")[0]; } } // Mapping methods private StackDetail mapStackToDetail(Stack stack) { return StackDetail.builder() .stackId(stack.stackId()) .stackName(stack.stackName()) .description(stack.description()) .status(stack.stackStatus()) .statusReason(stack.stackStatusReason()) .creationTime(stack.creationTime()) .lastUpdatedTime(stack.lastUpdatedTime()) .parameters(stack.parameters().stream() .collect(Collectors.toMap(Parameter::parameterKey, Parameter::parameterValue))) .outputs(stack.outputs().stream() .collect(Collectors.toMap(Output::outputKey, Output::outputValue))) .tags(stack.tags().stream() .collect(Collectors.toMap(Tag::key, Tag::value))) .build(); } private StackSummary mapStackSummary(software.amazon.awssdk.services.cloudformation.model.StackSummary summary) { return StackSummary.builder() .stackId(summary.stackId()) .stackName(summary.stackName()) .status(summary.stackStatus()) .creationTime(summary.creationTime()) .lastUpdatedTime(summary.lastUpdatedTime()) .build(); } private StackEvent mapStackEvent(software.amazon.awssdk.services.cloudformation.model.StackEvent event) { return StackEvent.builder() .eventId(event.eventId()) .stackId(event.stackId()) .stackName(event.stackName()) .logicalResourceId(event.logicalResourceId()) .physicalResourceId(event.physicalResourceId()) .resourceType(event.resourceType()) .timestamp(event.timestamp()) .resourceStatus(event.resourceStatus()) .resourceStatusReason(event.resourceStatusReason()) .build(); } private StackResource mapStackResource(software.amazon.awssdk.services.cloudformation.model.StackResource resource) { return StackResource.builder() .logicalResourceId(resource.logicalResourceId()) .physicalResourceId(resource.physicalResourceId()) .resourceType(resource.resourceType()) .timestamp(resource.timestamp()) .resourceStatus(resource.resourceStatus()) .resourceStatusReason(resource.resourceStatusReason()) .description(resource.description()) .build(); } public void close() { if (cfClient != null) { cfClient.close(); } if (s3Client != null) { s3Client.close(); } } }

2. Domain Models and DTOs

/** * Stack definition for creating/updating stacks */ public class StackDefinition { private final String stackName; private final String templateBody; private final Map<String, String> parameters; private final Map<String, String> tags; private final List<String> capabilities; private final Duration timeout; private final String roleArn; private final List<String> notificationArns; private StackDefinition(Builder builder) { this.stackName = builder.stackName; this.templateBody = builder.templateBody; this.parameters = builder.parameters; this.tags = builder.tags; this.capabilities = builder.capabilities; this.timeout = builder.timeout; this.roleArn = builder.roleArn; this.notificationArns = builder.notificationArns; } // Getters public String getStackName() { return stackName; } public String getTemplateBody() { return templateBody; } public Map<String, String> getParameters() { return parameters; } public Map<String, String> getTags() { return tags; } public List<String> getCapabilities() { return capabilities; } public Duration getTimeout() { return timeout; } public String getRoleArn() { return roleArn; } public List<String> getNotificationArns() { return notificationArns; } public static Builder builder() { return new Builder(); } public static class Builder { private String stackName; private String templateBody; private Map<String, String> parameters = new HashMap<>(); private Map<String, String> tags = new HashMap<>(); private List<String> capabilities = new ArrayList<>(); private Duration timeout; private String roleArn; private List<String> notificationArns = new ArrayList<>(); public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder templateBody(String templateBody) { this.templateBody = templateBody; return this; } public Builder parameters(Map<String, String> parameters) { this.parameters = parameters; return this; } public Builder parameter(String key, String value) { this.parameters.put(key, value); return this; } public Builder tags(Map<String, String> tags) { this.tags = tags; return this; } public Builder tag(String key, String value) { this.tags.put(key, value); return this; } public Builder capabilities(List<String> capabilities) { this.capabilities = capabilities; return this; } public Builder capability(String capability) { this.capabilities.add(capability); return this; } public Builder timeout(Duration timeout) { this.timeout = timeout; return this; } public Builder roleArn(String roleArn) { this.roleArn = roleArn; return this; } public Builder notificationArns(List<String> notificationArns) { this.notificationArns = notificationArns; return this; } public Builder notificationArn(String arn) { this.notificationArns.add(arn); return this; } public StackDefinition build() { return new StackDefinition(this); } } } /** * Stack operation result */ public class StackOperationResult { private final String stackName; private final String operationType; private final StackStatus status; private final boolean successful; private final String statusReason; private final String stackId; private StackOperationResult(Builder builder) { this.stackName = builder.stackName; this.operationType = builder.operationType; this.status = builder.status; this.successful = builder.successful; this.statusReason = builder.statusReason; this.stackId = builder.stackId; } // Getters public String getStackName() { return stackName; } public String getOperationType() { return operationType; } public StackStatus getStatus() { return status; } public boolean isSuccessful() { return successful; } public String getStatusReason() { return statusReason; } public String getStackId() { return stackId; } public static Builder builder() { return new Builder(); } public static class Builder { private String stackName; private String operationType; private StackStatus status; private boolean successful; private String statusReason; private String stackId; public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder operationType(String operationType) { this.operationType = operationType; return this; } public Builder status(StackStatus status) { this.status = status; return this; } public Builder successful(boolean successful) { this.successful = successful; return this; } public Builder statusReason(String statusReason) { this.statusReason = statusReason; return this; } public Builder stackId(String stackId) { this.stackId = stackId; return this; } public StackOperationResult build() { return new StackOperationResult(this); } } } /** * Stack detail information */ public class StackDetail { private final String stackId; private final String stackName; private final String description; private final StackStatus status; private final String statusReason; private final Instant creationTime; private final Instant lastUpdatedTime; private final Map<String, String> parameters; private final Map<String, String> outputs; private final Map<String, String> tags; private StackDetail(Builder builder) { this.stackId = builder.stackId; this.stackName = builder.stackName; this.description = builder.description; this.status = builder.status; this.statusReason = builder.statusReason; this.creationTime = builder.creationTime; this.lastUpdatedTime = builder.lastUpdatedTime; this.parameters = builder.parameters; this.outputs = builder.outputs; this.tags = builder.tags; } // Getters public String getStackId() { return stackId; } public String getStackName() { return stackName; } public String getDescription() { return description; } public StackStatus getStatus() { return status; } public String getStatusReason() { return statusReason; } public Instant getCreationTime() { return creationTime; } public Instant getLastUpdatedTime() { return lastUpdatedTime; } public Map<String, String> getParameters() { return parameters; } public Map<String, String> getOutputs() { return outputs; } public Map<String, String> getTags() { return tags; } public static Builder builder() { return new Builder(); } public static class Builder { private String stackId; private String stackName; private String description; private StackStatus status; private String statusReason; private Instant creationTime; private Instant lastUpdatedTime; private Map<String, String> parameters = new HashMap<>(); private Map<String, String> outputs = new HashMap<>(); private Map<String, String> tags = new HashMap<>(); public Builder stackId(String stackId) { this.stackId = stackId; return this; } public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder description(String description) { this.description = description; return this; } public Builder status(StackStatus status) { this.status = status; return this; } public Builder statusReason(String statusReason) { this.statusReason = statusReason; return this; } public Builder creationTime(Instant creationTime) { this.creationTime = creationTime; return this; } public Builder lastUpdatedTime(Instant lastUpdatedTime) { this.lastUpdatedTime = lastUpdatedTime; return this; } public Builder parameters(Map<String, String> parameters) { this.parameters = parameters; return this; } public Builder outputs(Map<String, String> outputs) { this.outputs = outputs; return this; } public Builder tags(Map<String, String> tags) { this.tags = tags; return this; } public StackDetail build() { return new StackDetail(this); } } } /** * Stack summary */ public class StackSummary { private final String stackId; private final String stackName; private final StackStatus status; private final Instant creationTime; private final Instant lastUpdatedTime; private StackSummary(Builder builder) { this.stackId = builder.stackId; this.stackName = builder.stackName; this.status = builder.status; this.creationTime = builder.creationTime; this.lastUpdatedTime = builder.lastUpdatedTime; } // Getters public String getStackId() { return stackId; } public String getStackName() { return stackName; } public StackStatus getStatus() { return status; } public Instant getCreationTime() { return creationTime; } public Instant getLastUpdatedTime() { return lastUpdatedTime; } public static Builder builder() { return new Builder(); } public static class Builder { private String stackId; private String stackName; private StackStatus status; private Instant creationTime; private Instant lastUpdatedTime; public Builder stackId(String stackId) { this.stackId = stackId; return this; } public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder status(StackStatus status) { this.status = status; return this; } public Builder creationTime(Instant creationTime) { this.creationTime = creationTime; return this; } public Builder lastUpdatedTime(Instant lastUpdatedTime) { this.lastUpdatedTime = lastUpdatedTime; return this; } public StackSummary build() { return new StackSummary(this); } } } /** * Stack event */ public class StackEvent { private final String eventId; private final String stackId; private final String stackName; private final String logicalResourceId; private final String physicalResourceId; private final String resourceType; private final Instant timestamp; private final String resourceStatus; private final String resourceStatusReason; private StackEvent(Builder builder) { this.eventId = builder.eventId; this.stackId = builder.stackId; this.stackName = builder.stackName; this.logicalResourceId = builder.logicalResourceId; this.physicalResourceId = builder.physicalResourceId; this.resourceType = builder.resourceType; this.timestamp = builder.timestamp; this.resourceStatus = builder.resourceStatus; this.resourceStatusReason = builder.resourceStatusReason; } // Getters public String getEventId() { return eventId; } public String getStackId() { return stackId; } public String getStackName() { return stackName; } public String getLogicalResourceId() { return logicalResourceId; } public String getPhysicalResourceId() { return physicalResourceId; } public String getResourceType() { return resourceType; } public Instant getTimestamp() { return timestamp; } public String getResourceStatus() { return resourceStatus; } public String getResourceStatusReason() { return resourceStatusReason; } public static Builder builder() { return new Builder(); } public static class Builder { private String eventId; private String stackId; private String stackName; private String logicalResourceId; private String physicalResourceId; private String resourceType; private Instant timestamp; private String resourceStatus; private String resourceStatusReason; public Builder eventId(String eventId) { this.eventId = eventId; return this; } public Builder stackId(String stackId) { this.stackId = stackId; return this; } public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder logicalResourceId(String logicalResourceId) { this.logicalResourceId = logicalResourceId; return this; } public Builder physicalResourceId(String physicalResourceId) { this.physicalResourceId = physicalResourceId; return this; } public Builder resourceType(String resourceType) { this.resourceType = resourceType; return this; } public Builder timestamp(Instant timestamp) { this.timestamp = timestamp; return this; } public Builder resourceStatus(String resourceStatus) { this.resourceStatus = resourceStatus; return this; } public Builder resourceStatusReason(String resourceStatusReason) { this.resourceStatusReason = resourceStatusReason; return this; } public StackEvent build() { return new StackEvent(this); } } } /** * Stack resource */ public class StackResource { private final String logicalResourceId; private final String physicalResourceId; private final String resourceType; private final Instant timestamp; private final String resourceStatus; private final String resourceStatusReason; private final String description; private StackResource(Builder builder) { this.logicalResourceId = builder.logicalResourceId; this.physicalResourceId = builder.physicalResourceId; this.resourceType = builder.resourceType; this.timestamp = builder.timestamp; this.resourceStatus = builder.resourceStatus; this.resourceStatusReason = builder.resourceStatusReason; this.description = builder.description; } // Getters public String getLogicalResourceId() { return logicalResourceId; } public String getPhysicalResourceId() { return physicalResourceId; } public String getResourceType() { return resourceType; } public Instant getTimestamp() { return timestamp; } public String getResourceStatus() { return resourceStatus; } public String getResourceStatusReason() { return resourceStatusReason; } public String getDescription() { return description; } public static Builder builder() { return new Builder(); } public static class Builder { private String logicalResourceId; private String physicalResourceId; private String resourceType; private Instant timestamp; private String resourceStatus; private String resourceStatusReason; private String description; public Builder logicalResourceId(String logicalResourceId) { this.logicalResourceId = logicalResourceId; return this; } public Builder physicalResourceId(String physicalResourceId) { this.physicalResourceId = physicalResourceId; return this; } public Builder resourceType(String resourceType) { this.resourceType = resourceType; return this; } public Builder timestamp(Instant timestamp) { this.timestamp = timestamp; return this; } public Builder resourceStatus(String resourceStatus) { this.resourceStatus = resourceStatus; return this; } public Builder resourceStatusReason(String resourceStatusReason) { this.resourceStatusReason = resourceStatusReason; return this; } public Builder description(String description) { this.description = description; return this; } public StackResource build() { return new StackResource(this); } } } /** * Template validation result */ public class TemplateValidationResult { private final boolean valid; private final String errorMessage; private final String description; private final List<software.amazon.awssdk.services.cloudformation.model.TemplateParameter> parameters; private final List<String> capabilities; private TemplateValidationResult(Builder builder) { this.valid = builder.valid; this.errorMessage = builder.errorMessage; this.description = builder.description; this.parameters = builder.parameters; this.capabilities = builder.capabilities; } // Getters public boolean isValid() { return valid; } public String getErrorMessage() { return errorMessage; } public String getDescription() { return description; } public List<software.amazon.awssdk.services.cloudformation.model.TemplateParameter> getParameters() { return parameters; } public List<String> getCapabilities() { return capabilities; } public static Builder builder() { return new Builder(); } public static class Builder { private boolean valid; private String errorMessage; private String description; private List<software.amazon.awssdk.services.cloudformation.model.TemplateParameter> parameters = new ArrayList<>(); private List<String> capabilities = new ArrayList<>(); public Builder valid(boolean valid) { this.valid = valid; return this; } public Builder errorMessage(String errorMessage) { this.errorMessage = errorMessage; return this; } public Builder description(String description) { this.description = description; return this; } public Builder parameters(List<software.amazon.awssdk.services.cloudformation.model.TemplateParameter> parameters) { this.parameters = parameters; return this; } public Builder capabilities(List<String> capabilities) { this.capabilities = capabilities; return this; } public TemplateValidationResult build() { return new TemplateValidationResult(this); } } } /** * Change set execution result */ public class ChangeSetResult { private final boolean successful; private final String stackId; private final StackStatus status; private ChangeSetResult(Builder builder) { this.successful = builder.successful; this.stackId = builder.stackId; this.status = builder.status; } // Getters public boolean isSuccessful() { return successful; } public String getStackId() { return stackId; } public StackStatus getStatus() { return status; } public static Builder builder() { return new Builder(); } public static class Builder { private boolean successful; private String stackId; private StackStatus status; public Builder successful(boolean successful) { this.successful = successful; return this; } public Builder stackId(String stackId) { this.stackId = stackId; return this; } public Builder status(StackStatus status) { this.status = status; return this; } public ChangeSetResult build() { return new ChangeSetResult(this); } } } /** * Custom exception for CloudFormation operations */ public class CloudFormationException extends RuntimeException { public CloudFormationException(String message) { super(message); } public CloudFormationException(String message, Throwable cause) { super(message, cause); } }

3. Template Builder (Type-Safe)

/** * Type-safe CloudFormation template builder */ public class CloudFormationTemplateBuilder { private final JsonObjectBuilder template; public CloudFormationTemplateBuilder() { this.template = Json.createObjectBuilder(); template.add("AWSTemplateFormatVersion", "2010-09-09"); } public CloudFormationTemplateBuilder description(String description) { template.add("Description", description); return this; } public CloudFormationTemplateBuilder parameter(String name, ParameterBuilder parameter) { if (!template.containsKey("Parameters")) { template.add("Parameters", Json.createObjectBuilder()); } JsonObjectBuilder parameters = (JsonObjectBuilder) template.get("Parameters"); parameters.add(name, parameter.build()); return this; } public CloudFormationTemplateBuilder resource(String logicalId, ResourceBuilder resource) { if (!template.containsKey("Resources")) { template.add("Resources", Json.createObjectBuilder()); } JsonObjectBuilder resources = (JsonObjectBuilder) template.get("Resources"); resources.add(logicalId, resource.build()); return this; } public CloudFormationTemplateBuilder output(String name, OutputBuilder output) { if (!template.containsKey("Outputs")) { template.add("Outputs", Json.createObjectBuilder()); } JsonObjectBuilder outputs = (JsonObjectBuilder) template.get("Outputs"); outputs.add(name, output.build()); return this; } public CloudFormationTemplateBuilder mapping(String name, JsonObject mapping) { if (!template.containsKey("Mappings")) { template.add("Mappings", Json.createObjectBuilder()); } JsonObjectBuilder mappings = (JsonObjectBuilder) template.get("Mappings"); mappings.add(name, mapping); return this; } public CloudFormationTemplateBuilder condition(String name, JsonValue condition) { if (!template.containsKey("Conditions")) { template.add("Conditions", Json.createObjectBuilder()); } JsonObjectBuilder conditions = (JsonObjectBuilder) template.get("Conditions"); conditions.add(name, condition); return this; } public String build() { return template.build().toString(); } public JsonObject buildJson() { return template.build(); } } /** * Parameter builder */ public class ParameterBuilder { private final JsonObjectBuilder parameter; public ParameterBuilder() { this.parameter = Json.createObjectBuilder(); } public ParameterBuilder type(String type) { parameter.add("Type", type); return this; } public ParameterBuilder description(String description) { parameter.add("Description", description); return this; } public ParameterBuilder defaultValue(String value) { parameter.add("Default", value); return this; } public ParameterBuilder allowedValues(String... values) { JsonArrayBuilder array = Json.createArrayBuilder(); for (String value : values) { array.add(value); } parameter.add("AllowedValues", array); return this; } public ParameterBuilder allowedPattern(String pattern) { parameter.add("AllowedPattern", pattern); return this; } public ParameterBuilder constraintDescription(String description) { parameter.add("ConstraintDescription", description); return this; } public ParameterBuilder minLength(int min) { parameter.add("MinLength", min); return this; } public ParameterBuilder maxLength(int max) { parameter.add("MaxLength", max); return this; } public ParameterBuilder minValue(int min) { parameter.add("MinValue", min); return this; } public ParameterBuilder maxValue(int max) { parameter.add("MaxValue", max); return this; } public ParameterBuilder noEcho(boolean noEcho) { parameter.add("NoEcho", noEcho); return this; } public JsonObject build() { return parameter.build(); } } /** * Resource builder */ public class ResourceBuilder { private final JsonObjectBuilder resource; public ResourceBuilder() { this.resource = Json.createObjectBuilder(); } public ResourceBuilder type(String type) { resource.add("Type", type); return this; } public ResourceBuilder properties(JsonObject properties) { resource.add("Properties", properties); return this; } public ResourceBuilder properties(PropertiesBuilder properties) { resource.add("Properties", properties.build()); return this; } public ResourceBuilder dependsOn(String... dependencies) { if (dependencies.length == 1) { resource.add("DependsOn", dependencies[0]); } else { JsonArrayBuilder array = Json.createArrayBuilder(); for (String dep : dependencies) { array.add(dep); } resource.add("DependsOn", array); } return this; } public ResourceBuilder metadata(JsonObject metadata) { resource.add("Metadata", metadata); return this; } public ResourceBuilder creationPolicy(JsonObject creationPolicy) { resource.add("CreationPolicy", creationPolicy); return this; } public ResourceBuilder updatePolicy(JsonObject updatePolicy) { resource.add("UpdatePolicy", updatePolicy); return this; } public ResourceBuilder deletionPolicy(String policy) { resource.add("DeletionPolicy", policy); return this; } public ResourceBuilder condition(String condition) { resource.add("Condition", condition); return this; } public JsonObject build() { return resource.build(); } } /** * Properties builder for resources */ public class PropertiesBuilder { private final JsonObjectBuilder properties; public PropertiesBuilder() { this.properties = Json.createObjectBuilder(); } public PropertiesBuilder add(String key, String value) { properties.add(key, value); return this; } public PropertiesBuilder add(String key, int value) { properties.add(key, value); return this; } public PropertiesBuilder add(String key, boolean value) { properties.add(key, value); return this; } public PropertiesBuilder add(String key, JsonValue value) { properties.add(key, value); return this; } public PropertiesBuilder add(String key, JsonObjectBuilder value) { properties.add(key, value); return this; } public PropertiesBuilder add(String key, JsonArrayBuilder value) { properties.add(key, value); return this; } public PropertiesBuilder ref(String logicalName) { return add(key, Json.createObjectBuilder().add("Ref", logicalName).build()); } public PropertiesBuilder getAtt(String logicalName, String attribute) { return add(key, Json.createObjectBuilder() .add("Fn::GetAtt", Json.createArrayBuilder() .add(logicalName) .add(attribute) .build()) .build()); } public JsonObject build() { return properties.build(); } } /** * Output builder */ public class OutputBuilder { private final JsonObjectBuilder output; public OutputBuilder() { this.output = Json.createObjectBuilder(); } public OutputBuilder description(String description) { output.add("Description", description); return this; } public OutputBuilder value(JsonValue value) { output.add("Value", value); return this; } public OutputBuilder value(String value) { output.add("Value", value); return this; } public OutputBuilder valueRef(String logicalName) { output.add("Value", Json.createObjectBuilder().add("Ref", logicalName).build()); return this; } public OutputBuilder valueGetAtt(String logicalName, String attribute) { output.add("Value", Json.createObjectBuilder() .add("Fn::GetAtt", Json.createArrayBuilder() .add(logicalName) .add(attribute) .build()) .build()); return this; } public OutputBuilder export(String exportName) { output.add("Export", Json.createObjectBuilder().add("Name", exportName).build()); return this; } public OutputBuilder condition(String condition) { output.add("Condition", condition); return this; } public JsonObject build() { return output.build(); } } // JSON-P utility methods class Json { public static JsonObjectBuilder createObjectBuilder() { return javax.json.Json.createObjectBuilder(); } public static JsonArrayBuilder createArrayBuilder() { return javax.json.Json.createArrayBuilder(); } public static JsonValue createValue(String value) { return javax.json.Json.createValue(value); } }

4. Change Set Management

/** * Change set management service */ public class ChangeSetService { private final CloudFormationClient cfClient; public ChangeSetService(CloudFormationClient cfClient) { this.cfClient = cfClient; } /** * Create a change set */ public ChangeSetInfo createChangeSet(ChangeSetDefinition changeSetDef) { try { CreateChangeSetRequest.Builder requestBuilder = CreateChangeSetRequest.builder() .stackName(changeSetDef.getStackName()) .changeSetName(changeSetDef.getChangeSetName()) .templateBody(changeSetDef.getTemplateBody()) .changeSetType(changeSetDef.getChangeSetType()) .capabilitiesWithStrings(changeSetDef.getCapabilities()) .parameters(createParameters(changeSetDef.getParameters())) .tags(createTags(changeSetDef.getTags())); if (changeSetDef.getRoleArn() != null) { requestBuilder.roleARN(changeSetDef.getRoleArn()); } if (changeSetDef.getDescription() != null) { requestBuilder.description(changeSetDef.getDescription()); } CreateChangeSetResponse response = cfClient.createChangeSet(requestBuilder.build()); // Wait for change set creation to complete waitForChangeSetCreation(changeSetDef.getStackName(), changeSetDef.getChangeSetName()); return getChangeSetInfo(changeSetDef.getStackName(), changeSetDef.getChangeSetName()); } catch (Exception e) { throw new CloudFormationException("Failed to create change set: " + changeSetDef.getChangeSetName(), e); } } /** * Get change set information */ public ChangeSetInfo getChangeSetInfo(String stackName, String changeSetName) { try { DescribeChangeSetRequest request = DescribeChangeSetRequest.builder() .stackName(stackName) .changeSetName(changeSetName) .build(); DescribeChangeSetResponse response = cfClient.describeChangeSet(request); return ChangeSetInfo.builder() .changeSetId(response.changeSetId()) .changeSetName(response.changeSetName()) .stackId(response.stackId()) .stackName(response.stackName()) .description(response.description()) .status(response.status()) .statusReason(response.statusReason()) .creationTime(response.creationTime()) .changes(response.changes()) .parameters(response.parameters()) .build(); } catch (Exception e) { throw new CloudFormationException("Failed to get change set info: " + changeSetName, e); } } /** * List change sets for a stack */ public List<ChangeSetSummary> listChangeSets(String stackName) { try { ListChangeSetsRequest request = ListChangeSetsRequest.builder() .stackName(stackName) .build(); ListChangeSetsResponse response = cfClient.listChangeSets(request); return response.summaries().stream() .map(this::mapChangeSetSummary) .collect(Collectors.toList()); } catch (Exception e) { throw new CloudFormationException("Failed to list change sets for stack: " + stackName, e); } } /** * Delete a change set */ public void deleteChangeSet(String stackName, String changeSetName) { try { DeleteChangeSetRequest request = DeleteChangeSetRequest.builder() .stackName(stackName) .changeSetName(changeSetName) .build(); cfClient.deleteChangeSet(request); } catch (Exception e) { throw new CloudFormationException("Failed to delete change set: " + changeSetName, e); } } private void waitForChangeSetCreation(String stackName, String changeSetName) { int maxAttempts = 30; // 5 minutes with 10-second intervals int attempt = 0; while (attempt < maxAttempts) { try { ChangeSetInfo info = getChangeSetInfo(stackName, changeSetName); ChangeSetStatus status = info.getStatus(); if (status == ChangeSetStatus.CREATE_COMPLETE || status == ChangeSetStatus.FAILED) { return; } Thread.sleep(10000); // Wait 10 seconds attempt++; } catch (InterruptedException e) { Thread.currentThread().interrupt(); throw new CloudFormationException("Change set creation wait interrupted", e); } } throw new CloudFormationException("Change set creation timeout: " + changeSetName); } private List<Parameter> createParameters(Map<String, String> parameters) { if (parameters == null || parameters.isEmpty()) { return Collections.emptyList(); } return parameters.entrySet().stream() .map(entry -> Parameter.builder() .parameterKey(entry.getKey()) .parameterValue(entry.getValue()) .build()) .collect(Collectors.toList()); } private List<Tag> createTags(Map<String, String> tags) { if (tags == null || tags.isEmpty()) { return Collections.emptyList(); } return tags.entrySet().stream() .map(entry -> Tag.builder() .key(entry.getKey()) .value(entry.getValue()) .build()) .collect(Collectors.toList()); } private ChangeSetSummary mapChangeSetSummary(software.amazon.awssdk.services.cloudformation.model.ChangeSetSummary summary) { return ChangeSetSummary.builder() .changeSetId(summary.changeSetId()) .changeSetName(summary.changeSetName()) .stackId(summary.stackId()) .stackName(summary.stackName()) .description(summary.description()) .status(summary.status()) .statusReason(summary.statusReason()) .creationTime(summary.creationTime()) .executionStatus(summary.executionStatus()) .build(); } } /** * Change set definition */ public class ChangeSetDefinition { private final String stackName; private final String changeSetName; private final String templateBody; private final ChangeSetType changeSetType; private final Map<String, String> parameters; private final Map<String, String> tags; private final List<String> capabilities; private final String roleArn; private final String description; private ChangeSetDefinition(Builder builder) { this.stackName = builder.stackName; this.changeSetName = builder.changeSetName; this.templateBody = builder.templateBody; this.changeSetType = builder.changeSetType; this.parameters = builder.parameters; this.tags = builder.tags; this.capabilities = builder.capabilities; this.roleArn = builder.roleArn; this.description = builder.description; } // Getters public String getStackName() { return stackName; } public String getChangeSetName() { return changeSetName; } public String getTemplateBody() { return templateBody; } public ChangeSetType getChangeSetType() { return changeSetType; } public Map<String, String> getParameters() { return parameters; } public Map<String, String> getTags() { return tags; } public List<String> getCapabilities() { return capabilities; } public String getRoleArn() { return roleArn; } public String getDescription() { return description; } public static Builder builder() { return new Builder(); } public static class Builder { private String stackName; private String changeSetName; private String templateBody; private ChangeSetType changeSetType = ChangeSetType.UPDATE; private Map<String, String> parameters = new HashMap<>(); private Map<String, String> tags = new HashMap<>(); private List<String> capabilities = new ArrayList<>(); private String roleArn; private String description; public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder changeSetName(String changeSetName) { this.changeSetName = changeSetName; return this; } public Builder templateBody(String templateBody) { this.templateBody = templateBody; return this; } public Builder changeSetType(ChangeSetType changeSetType) { this.changeSetType = changeSetType; return this; } public Builder parameters(Map<String, String> parameters) { this.parameters = parameters; return this; } public Builder parameter(String key, String value) { this.parameters.put(key, value); return this; } public Builder tags(Map<String, String> tags) { this.tags = tags; return this; } public Builder tag(String key, String value) { this.tags.put(key, value); return this; } public Builder capabilities(List<String> capabilities) { this.capabilities = capabilities; return this; } public Builder capability(String capability) { this.capabilities.add(capability); return this; } public Builder roleArn(String roleArn) { this.roleArn = roleArn; return this; } public Builder description(String description) { this.description = description; return this; } public ChangeSetDefinition build() { return new ChangeSetDefinition(this); } } } /** * Change set information */ public class ChangeSetInfo { private final String changeSetId; private final String changeSetName; private final String stackId; private final String stackName; private final String description; private final ChangeSetStatus status; private final String statusReason; private final Instant creationTime; private final List<Change> changes; private final List<Parameter> parameters; private ChangeSetInfo(Builder builder) { this.changeSetId = builder.changeSetId; this.changeSetName = builder.changeSetName; this.stackId = builder.stackId; this.stackName = builder.stackName; this.description = builder.description; this.status = builder.status; this.statusReason = builder.statusReason; this.creationTime = builder.creationTime; this.changes = builder.changes; this.parameters = builder.parameters; } // Getters public String getChangeSetId() { return changeSetId; } public String getChangeSetName() { return changeSetName; } public String getStackId() { return stackId; } public String getStackName() { return stackName; } public String getDescription() { return description; } public ChangeSetStatus getStatus() { return status; } public String getStatusReason() { return statusReason; } public Instant getCreationTime() { return creationTime; } public List<Change> getChanges() { return changes; } public List<Parameter> getParameters() { return parameters; } public static Builder builder() { return new Builder(); } public static class Builder { private String changeSetId; private String changeSetName; private String stackId; private String stackName; private String description; private ChangeSetStatus status; private String statusReason; private Instant creationTime; private List<Change> changes = new ArrayList<>(); private List<Parameter> parameters = new ArrayList<>(); public Builder changeSetId(String changeSetId) { this.changeSetId = changeSetId; return this; } public Builder changeSetName(String changeSetName) { this.changeSetName = changeSetName; return this; } public Builder stackId(String stackId) { this.stackId = stackId; return this; } public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder description(String description) { this.description = description; return this; } public Builder status(ChangeSetStatus status) { this.status = status; return this; } public Builder statusReason(String statusReason) { this.statusReason = statusReason; return this; } public Builder creationTime(Instant creationTime) { this.creationTime = creationTime; return this; } public Builder changes(List<Change> changes) { this.changes = changes; return this; } public Builder parameters(List<Parameter> parameters) { this.parameters = parameters; return this; } public ChangeSetInfo build() { return new ChangeSetInfo(this); } } } /** * Change set summary */ public class ChangeSetSummary { private final String changeSetId; private final String changeSetName; private final String stackId; private final String stackName; private final String description; private final ChangeSetStatus status; private final String statusReason; private final Instant creationTime; private final ExecutionStatus executionStatus; private ChangeSetSummary(Builder builder) { this.changeSetId = builder.changeSetId; this.changeSetName = builder.changeSetName; this.stackId = builder.stackId; this.stackName = builder.stackName; this.description = builder.description; this.status = builder.status; this.statusReason = builder.statusReason; this.creationTime = builder.creationTime; this.executionStatus = builder.executionStatus; } // Getters public String getChangeSetId() { return changeSetId; } public String getChangeSetName() { return changeSetName; } public String getStackId() { return stackId; } public String getStackName() { return stackName; } public String getDescription() { return description; } public ChangeSetStatus getStatus() { return status; } public String getStatusReason() { return statusReason; } public Instant getCreationTime() { return creationTime; } public ExecutionStatus getExecutionStatus() { return executionStatus; } public static Builder builder() { return new Builder(); } public static class Builder { private String changeSetId; private String changeSetName; private String stackId; private String stackName; private String description; private ChangeSetStatus status; private String statusReason; private Instant creationTime; private ExecutionStatus executionStatus; public Builder changeSetId(String changeSetId) { this.changeSetId = changeSetId; return this; } public Builder changeSetName(String changeSetName) { this.changeSetName = changeSetName; return this; } public Builder stackId(String stackId) { this.stackId = stackId; return this; } public Builder stackName(String stackName) { this.stackName = stackName; return this; } public Builder description(String description) { this.description = description; return this; } public Builder status(ChangeSetStatus status) { this.status = status; return this; } public Builder statusReason(String statusReason) { this.statusReason = statusReason; return this; } public Builder creationTime(Instant creationTime) { this.creationTime = creationTime; return this; } public Builder executionStatus(ExecutionStatus executionStatus) { this.executionStatus = executionStatus; return this; } public ChangeSetSummary build() { return new ChangeSetSummary(this); } } }

5. Spring Boot Integration

/** * Spring Boot Configuration */ @Configuration @EnableConfigurationProperties(CloudFormationProperties.class) public class CloudFormationAutoConfiguration { @Bean @ConditionalOnMissingBean public CloudFormationClient cloudFormationClient(CloudFormationProperties properties) { return CloudFormationClient.builder() .region(Region.of(properties.getRegion())) .credentialsProvider(getCredentialsProvider(properties)) .build(); } @Bean @ConditionalOnMissingBean public S3Client s3Client(CloudFormationProperties properties) { return S3Client.builder() .region(Region.of(properties.getRegion())) .credentialsProvider(getCredentialsProvider(properties)) .build(); } @Bean @ConditionalOnMissingBean public CloudFormationService cloudFormationService( CloudFormationClient cfClient, S3Client s3Client, CloudFormationProperties properties) { return new CloudFormationService(cfClient, s3Client, properties.getTemplateBucket()); } @Bean @ConditionalOnMissingBean public ChangeSetService changeSetService(CloudFormationClient cfClient) { return new ChangeSetService(cfClient); } private AwsCredentialsProvider getCredentialsProvider(CloudFormationProperties properties) { if (properties.getAccessKey() != null && properties.getSecretKey() != null) { return StaticCredentialsProvider.create( AwsBasicCredentials.create(properties.getAccessKey(), properties.getSecretKey())); } return DefaultCredentialsProvider.create(); } } /** * Configuration properties */ @ConfigurationProperties(prefix = "aws.cloudformation") public class CloudFormationProperties { private String region = "us-east-1"; private String accessKey; private String secretKey; private String templateBucket; private boolean enabled = true; // Getters and setters public String getRegion() { return region; } public void setRegion(String region) { this.region = region; } public String getAccessKey() { return accessKey; } public void setAccessKey(String accessKey) { this.accessKey = accessKey; } public String getSecretKey() { return secretKey; } public void setSecretKey(String secretKey) { this.secretKey = secretKey; } public String getTemplateBucket() { return templateBucket; } public void setTemplateBucket(String templateBucket) { this.templateBucket = templateBucket; } public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { this.enabled = enabled; } } /** * REST Controller for CloudFormation operations */ @RestController @RequestMapping("/cloudformation") public class CloudFormationController { private final CloudFormationService cfService; private final ChangeSetService changeSetService; public CloudFormationController(CloudFormationService cfService, ChangeSetService changeSetService) { this.cfService = cfService; this.changeSetService = changeSetService; } @PostMapping("/stacks") public ResponseEntity<StackOperationResult> createOrUpdateStack(@RequestBody StackDefinition stackDef) { try { StackOperationResult result = cfService.createOrUpdateStack(stackDef); return ResponseEntity.ok(result); } catch (CloudFormationException e) { return ResponseEntity.badRequest().build(); } } @DeleteMapping("/stacks/{stackName}") public ResponseEntity<StackOperationResult> deleteStack(@PathVariable String stackName) { try { StackOperationResult result = cfService.deleteStack(stackName); return ResponseEntity.ok(result); } catch (CloudFormationException e) { return ResponseEntity.badRequest().build(); } } @GetMapping("/stacks/{stackName}") public ResponseEntity<StackDetail> getStack(@PathVariable String stackName) { try { StackDetail detail = cfService.getStackDetail(stackName); return ResponseEntity.ok(detail); } catch (CloudFormationException e) { return ResponseEntity.notFound().build(); } } @GetMapping("/stacks") public ResponseEntity<List<StackSummary>> listStacks( @RequestParam(required = false) List<String> status) { try { StackStatus[] statusArray = status != null ? status.stream().map(StackStatus::fromValue).toArray(StackStatus[]::new) : new StackStatus[0]; List<StackSummary> stacks = cfService.listStacks(statusArray); return ResponseEntity.ok(stacks); } catch (CloudFormationException e) { return ResponseEntity.badRequest().build(); } } @PostMapping("/changesets") public ResponseEntity<ChangeSetInfo> createChangeSet(@RequestBody ChangeSetDefinition changeSetDef) { try { ChangeSetInfo info = changeSetService.createChangeSet(changeSetDef); return ResponseEntity.ok(info); } catch (CloudFormationException e) { return ResponseEntity.badRequest().build(); } } @PostMapping("/changesets/{changeSetName}/execute") public ResponseEntity<ChangeSetResult> executeChangeSet(@PathVariable String changeSetName) { try { ChangeSetResult result = cfService.executeChangeSet(changeSetName); return ResponseEntity.ok(result); } catch (CloudFormationException e) { return ResponseEntity.badRequest().build(); } } @PostMapping("/templates/validate") public ResponseEntity<TemplateValidationResult> validateTemplate(@RequestBody String templateBody) { TemplateValidationResult result = cfService.validateTemplate(templateBody); return ResponseEntity.ok(result); } }

6. Usage Examples

/** * Demo application showing CloudFormation usage */ @Service public class CloudFormationDemoService { private final CloudFormationService cfService; private final ChangeSetService changeSetService; public CloudFormationDemoService(CloudFormationService cfService, ChangeSetService changeSetService) { this.cfService = cfService; this.changeSetService = changeSetService; } public void demonstrateCloudFormation() { // Create a simple S3 bucket template String template = createS3BucketTemplate(); // Validate template TemplateValidationResult validation = cfService.validateTemplate(template); if (!validation.isValid()) { System.err.println("Template validation failed: " + validation.getErrorMessage()); return; } // Create stack definition StackDefinition stackDef = StackDefinition.builder() .stackName("demo-s3-bucket") .templateBody(template) .parameter("BucketName", "my-demo-bucket-" + System.currentTimeMillis()) .tag("Environment", "demo") .tag("Application", "cloudformation-demo") .capability("CAPABILITY_IAM") .timeout(Duration.ofMinutes(30)) .build(); // Create stack StackOperationResult result = cfService.createOrUpdateStack(stackDef); if (result.isSuccessful()) { System.info("Stack created successfully: " + result.getStackName()); // Get stack details StackDetail detail = cfService.getStackDetail(result.getStackName()); System.info("Stack outputs: " + detail.getOutputs()); } else { System.err.println("Stack creation failed: " + result.getStatusReason()); // Get stack events for debugging List<StackEvent> events = cfService.getStackEvents(result.getStackName()); events.forEach(event -> System.err.println("Event: " + event.getResourceType() + " - " + event.getResourceStatus() + " - " + event.getResourceStatusReason())); } } public void demonstrateChangeSets() { String template = createUpdatedTemplate(); ChangeSetDefinition changeSetDef = ChangeSetDefinition.builder() .stackName("demo-s3-bucket") .changeSetName("add-lambda-function") .templateBody(template) .changeSetType(ChangeSetType.UPDATE) .description("Add Lambda function to demo stack") .capability("CAPABILITY_IAM") .build(); ChangeSetInfo changeSetInfo = changeSetService.createChangeSet(changeSetDef); if (changeSetInfo.getStatus() == ChangeSetStatus.CREATE_COMPLETE) { System.info("Change set created successfully"); System.info("Changes: " + changeSetInfo.getChanges().size()); // Execute change set ChangeSetResult result = cfService.executeChangeSet(changeSetInfo.getChangeSetName()); if (result.isSuccessful()) { System.info("Change set executed successfully"); } } } private String createS3BucketTemplate() { return new CloudFormationTemplateBuilder() .description("Demo S3 Bucket Stack") .parameter("BucketName", new ParameterBuilder() .type("String") .description("Name of the S3 bucket") .allowedPattern("[a-z0-9-]+") .constraintDescription("Bucket name can contain only lowercase letters, numbers, and hyphens")) .resource("DemoBucket", new ResourceBuilder() .type("AWS::S3::Bucket") .properties(new PropertiesBuilder() .add("BucketName", Json.createObjectBuilder().add("Ref", "BucketName").build()) .add("VersioningConfiguration", Json.createObjectBuilder() .add("Status", "Enabled") .build()) .build()) .deletionPolicy("Retain")) .output("BucketName", new OutputBuilder() .description("S3 Bucket Name") .valueRef("DemoBucket") .export("DemoBucketName")) .output("BucketArn", new OutputBuilder() .description("S3 Bucket ARN") .valueGetAtt("DemoBucket", "Arn")) .build(); } private String createUpdatedTemplate() { // Template with additional Lambda function return "{\n" + " \"AWSTemplateFormatVersion\": \"2010-09-09\",\n" + " \"Description\": \"Demo Stack with S3 and Lambda\",\n" + " \"Parameters\": {\n" + " \"BucketName\": {\n" + " \"Type\": \"String\",\n" + " \"Description\": \"Name of the S3 bucket\"\n" + " }\n" + " },\n" + " \"Resources\": {\n" + " \"DemoBucket\": {\n" + " \"Type\": \"AWS::S3::Bucket\",\n" + " \"Properties\": {\n" + " \"BucketName\": {\"Ref\": \"BucketName\"},\n" + " \"VersioningConfiguration\": {\"Status\": \"Enabled\"}\n" + " },\n" + " \"DeletionPolicy\": \"Retain\"\n" + " },\n" + " \"DemoFunction\": {\n" + " \"Type\": \"AWS::Lambda::Function\",\n" + " \"Properties\": {\n" + " \"Runtime\": \"java11\",\n" + " \"Handler\": \"com.example.Handler::handleRequest\",\n" + " \"Code\": {\n" + " \"ZipFile\": \"package code...\"\n" + " },\n" + " \"Role\": {\"Fn::GetAtt\": [\"LambdaExecutionRole\", \"Arn\"]}\n" + " }\n" + " },\n" + " \"LambdaExecutionRole\": {\n" + " \"Type\": \"AWS::IAM::Role\",\n" + " \"Properties\": {\n" + " \"AssumeRolePolicyDocument\": {\n" + " \"Version\": \"2012-10-17\",\n" + " \"Statement\": [\n" + " {\n" + " \"Effect\": \"Allow\",\n" + " \"Principal\": {\"Service\": \"lambda.amazonaws.com\"},\n" + " \"Action\": \"sts:AssumeRole\"\n" + " }\n" + " ]\n" + " },\n" + " \"ManagedPolicyArns\": [\n" + " \"arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole\"\n" + " ]\n" + " }\n" + " }\n" + " },\n" + " \"Outputs\": {\n" + " \"BucketName\": {\n" + " \"Description\": \"S3 Bucket Name\",\n" + " \"Value\": {\"Ref\": \"DemoBucket\"},\n" + " \"Export\": {\"Name\": \"DemoBucketName\"}\n" + " },\n" + " \"FunctionName\": {\n" + " \"Description\": \"Lambda Function Name\",\n" + " \"Value\": {\"Ref\": \"DemoFunction\"}\n" + " }\n" + " }\n" + "}"; } } /** * Main demo application */ @SpringBootApplication public class CloudFormationDemoApp { public static void main(String[] args) { ConfigurableApplicationContext context = SpringApplication.run(CloudFormationDemoApp.class, args); CloudFormationDemoService demoService = context.getBean(CloudFormationDemoService.class); demoService.demonstrateCloudFormation(); // demoService.demonstrateChangeSets(); context.close(); } } // Utility class for system output class System { public static void info(String message) { java.lang.System.out.println("[INFO] " + message); } public static void err(String message) { java.lang.System.err.println("[ERROR] " + message); } }

Key Features

  • Complete CloudFormation Operations: Create, update, delete, validate stacks
  • Change Set Management: Create, review, and execute change sets
  • Type-Safe Template Building: JSON-P based template builders
  • Comprehensive Monitoring: Stack events, resources, and status tracking
  • Spring Boot Integration: Auto-configuration and REST APIs
  • Error Handling: Robust exception handling and status monitoring
  • Template Validation: Pre-deployment template validation
  • S3 Integration: Automatic template upload to S3 for large templates

Usage Examples

Create Stack

StackDefinition stackDef = StackDefinition.builder() .stackName("my-stack") .templateBody(template) .parameter("Param1", "value1") .tag("Environment", "prod") .build(); StackOperationResult result = cfService.createOrUpdateStack(stackDef);

Type-Safe Template

String template = new CloudFormationTemplateBuilder() .description("My Stack") .resource("MyBucket", new ResourceBuilder() .type("AWS::S3::Bucket") .properties(new PropertiesBuilder() .add("BucketName", "my-bucket") .build()) .build()) .build();

Change Sets

ChangeSetDefinition changeSetDef = ChangeSetDefinition.builder() .stackName("my-stack") .changeSetName("my-change-set") .templateBody(updatedTemplate) .build(); ChangeSetInfo info = changeSetService.createChangeSet(changeSetDef);

This comprehensive CloudFormation Java SDK wrapper provides production-ready functionality for managing AWS infrastructure as code with proper error handling, monitoring, and Spring Boot integration.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper