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