Skip to content

Commit

Permalink
Updated using default framework
Browse files Browse the repository at this point in the history
  • Loading branch information
xiwhuang committed Apr 23, 2020
1 parent 574d4ee commit e242715
Show file tree
Hide file tree
Showing 32 changed files with 1,183 additions and 965 deletions.
97 changes: 62 additions & 35 deletions aws-cloudformation-stackset/aws-cloudformation-stackset.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,58 @@
}
},
"additionalProperties": false
},
"StackInstances": {
"description": "Stack instances in some specific accounts and Regions.",
"type": "object",
"properties": {
"DeploymentTargets": {
"description": " The AWS OrganizationalUnitIds or Accounts for which to create stack instances in the specified Regions.",
"type": "object",
"properties": {
"Accounts": {
"description": "AWS accounts that you want to create stack instances in the specified Region(s) for.",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/Account"
}
},
"OrganizationalUnitIds": {
"description": "The organization root ID or organizational unit (OU) IDs to which StackSets deploys.",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/OrganizationalUnitId"
}
}
}
},
"Regions": {
"description": "The names of one or more Regions where you want to create stack instances using the specified AWS account(s).",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/Region"
}
},
"ParameterOverrides": {
"description": "A list of stack set parameters whose values you want to override in the selected stack instances.",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/Parameter"
}
}
},
"required": [
"DeploymentTargets",
"Regions"
]
}
},
"properties": {
Expand All @@ -100,30 +152,6 @@
"$ref": "#/definitions/Capability"
}
},
"DeploymentTargets": {
"description": "",
"type": "object",
"properties": {
"Accounts" : {
"description": "AWS accounts that you want to create stack instances in the specified Region(s) for.",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/Account"
}
},
"OrganizationalUnitIds": {
"description": "The organization root ID or organizational unit (OU) IDs to which StackSets deploys.",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/OrganizationalUnitId"
}
}
}
},
"Description": {
"description": "A description of the stack set. You can use the description to identify the stack set's purpose or other important information.",
"type": "string",
Expand Down Expand Up @@ -166,6 +194,15 @@
}
}
},
"StackInstancesGroup": {
"description": "",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/StackInstances"
}
},
"Parameters": {
"description": "The input parameters for the stack set template.",
"type": "array",
Expand All @@ -183,15 +220,6 @@
"SELF_MANAGED"
]
},
"Regions": {
"description": "The names of one or more Regions where you want to create stack instances using the specified AWS account(s).",
"type": "array",
"uniqueItems": true,
"insertionOrder": false,
"items": {
"$ref": "#/definitions/Region"
}
},
"Tags": {
"description": "The key-value pairs to associate with this stack set and the stacks created from it. AWS CloudFormation also propagates these tags to supported resources that are created in the stacks. A maximum number of 50 tags can be specified.",
"type": "array",
Expand All @@ -216,8 +244,7 @@
}
},
"required": [
"PermissionModel",
"Regions"
"PermissionModel"
],
"additionalProperties": false,
"createOnlyProperties": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Data;
import software.amazon.cloudformation.stackset.util.EnumUtils.UpdateOperations;
import software.amazon.cloudformation.stackset.util.EnumUtils.Operations;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.stream.Collectors;

@Data
Expand All @@ -19,34 +21,42 @@ public class CallbackContext {
// Operation Id to verify stabilization for StackSet operation.
private String operationId;

// Elapsed counts of retries on specific exceptions.
private int retries;
// Indicates initiation of analyzing template.
private boolean templateAnalyzed;

// Indicates initiation of resource stabilization.
private boolean stabilizationStarted;
private boolean stackSetCreated;

// Indicates initiation of stack instances creation.
private boolean addStacksByRegionsStarted;

// Indicates initiation of stack instances creation.
private boolean addStacksByTargetsStarted;

// Indicates initiation of stack instances delete.
private boolean deleteStacksByRegionsStarted;
private boolean addStacksStarted;

// Indicates initiation of stack instances delete.
private boolean deleteStacksByTargetsStarted;
private boolean deleteStacksStarted;

// Indicates initiation of stack set update.
private boolean updateStackSetStarted;

// Indicates initiation of stack instances update.
private boolean updateStackInstancesStarted;
private boolean updateStacksStarted;

// Total running time
@Builder.Default
private int elapsedTime = 0;

private StackInstances stackInstancesInOperation;

// List to keep track on the complete status for creating
@Builder.Default
private Queue<StackInstances> createStacksQueue = new LinkedList<>();

// List to keep track on stack instances for deleting
@Builder.Default
private Queue<StackInstances> deleteStacksQueue = new LinkedList<>();

// List to keep track on stack instances for update
@Builder.Default
private Queue<StackInstances> updateStacksQueue = new LinkedList<>();

/**
* Default as 0, will be {@link software.amazon.cloudformation.stackset.util.Stabilizer#BASE_CALLBACK_DELAY_SECONDS}
* When it enters the first IN_PROGRESS callback
Expand All @@ -55,14 +65,9 @@ public class CallbackContext {

// Map to keep track on the complete status for operations in Update
@Builder.Default
private Map<UpdateOperations, Boolean> operationsStabilizationMap = Arrays.stream(UpdateOperations.values())
private Map<Operations, Boolean> operationsStabilizationMap = Arrays.stream(Operations.values())
.collect(Collectors.toMap(e -> e, e -> false));

@JsonIgnore
public void incrementRetryCounter() {
retries++;
}

/**
* Increments {@link CallbackContext#elapsedTime} and returns the total elapsed time
* @return {@link CallbackContext#getElapsedTime()} after incrementing
Expand All @@ -72,8 +77,4 @@ public int incrementElapsedTime() {
elapsedTime = elapsedTime + currentDelaySeconds;
return elapsedTime;
}

@JsonPOJOBuilder(withPrefix = "")
public static class CallbackContextBuilder {
}
}
Original file line number Diff line number Diff line change
@@ -1,69 +1,62 @@
package software.amazon.cloudformation.stackset;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import software.amazon.awssdk.services.cloudformation.CloudFormationClient;
import software.amazon.awssdk.services.cloudformation.model.AlreadyExistsException;
import software.amazon.awssdk.services.cloudformation.model.CreateStackInstancesResponse;
import software.amazon.awssdk.services.cloudformation.model.CreateStackSetResponse;
import software.amazon.awssdk.services.cloudformation.model.InsufficientCapabilitiesException;
import software.amazon.awssdk.services.cloudformation.model.LimitExceededException;
import software.amazon.awssdk.services.cloudformation.model.OperationInProgressException;
import software.amazon.awssdk.services.cloudformation.model.StackSetNotFoundException;
import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.exceptions.CfnNotFoundException;
import software.amazon.cloudformation.exceptions.CfnServiceLimitExceededException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
import software.amazon.cloudformation.stackset.util.ClientBuilder;
import software.amazon.cloudformation.stackset.util.InstancesAnalyzer;
import software.amazon.cloudformation.stackset.util.OperationOperator;
import software.amazon.cloudformation.stackset.util.PhysicalIdGenerator;
import software.amazon.cloudformation.stackset.util.Stabilizer;
import software.amazon.cloudformation.stackset.util.Validator;

import static software.amazon.cloudformation.stackset.translator.RequestTranslator.createStackInstancesRequest;
import static software.amazon.cloudformation.stackset.translator.RequestTranslator.createStackSetRequest;
import static software.amazon.cloudformation.stackset.util.Comparator.isAddingStackInstances;
import static software.amazon.cloudformation.stackset.util.EnumUtils.Operations.ADD_INSTANCES;
import static software.amazon.cloudformation.stackset.util.Stabilizer.getDelaySeconds;

@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateHandler extends BaseHandler<CallbackContext> {

private AmazonWebServicesClientProxy proxy;
private ResourceModel model;
private CloudFormationClient client;
private CallbackContext context;
private Logger logger;
private Stabilizer stabilizer;

@Builder.Default
private Validator validator = new Validator();

@Override
public ProgressEvent<ResourceModel, CallbackContext> handleRequest(
final AmazonWebServicesClientProxy proxy,
final ResourceHandlerRequest<ResourceModel> request,
final CallbackContext callbackContext,
final Logger logger) {

this.context = callbackContext == null ? CallbackContext.builder().build() : callbackContext;
this.model = request.getDesiredResourceState();
this.logger = logger;
this.proxy = proxy;
this.client = ClientBuilder.getClient();
this.stabilizer = Stabilizer.builder().proxy(proxy).client(client).logger(logger).build();
final CallbackContext context = callbackContext == null ? CallbackContext.builder().build() : callbackContext;
final ResourceModel model = request.getDesiredResourceState();
final CloudFormationClient client = ClientBuilder.getClient();
final Stabilizer stabilizer = Stabilizer.builder().proxy(proxy).client(client).logger(logger).build();
final OperationOperator operator = OperationOperator.builder()
.client(client).desiredModel(model)
.logger(logger).proxy(proxy).context(context)
.build();
InstancesAnalyzer.builder().desiredModel(model).build().analyzeForCreate(context);

// Create a resource when a creation has not initialed
if (!context.isStabilizationStarted()) {
validator.validateTemplate(proxy, model.getTemplateBody(), model.getTemplateURL(), logger);
if (!context.isStackSetCreated()) {
new Validator().validateTemplate(proxy, model.getTemplateBody(), model.getTemplateURL(), logger);
final String stackSetName = PhysicalIdGenerator.generatePhysicalId(request);
createStackSet(stackSetName, request.getClientRequestToken());
createStackSet(proxy, model, logger, client, context, stackSetName, request.getClientRequestToken());
}

if (stabilizer.isPerformingOperation(isAddingStackInstances(context), context.isAddStacksStarted(), null,
ADD_INSTANCES, context.getCreateStacksQueue(), model, context)) {

} else if (stabilizer.isStabilized(model, context)) {
operator.updateStackSet(ADD_INSTANCES);
}

if (context.getOperationsStabilizationMap().get(ADD_INSTANCES)) {
return ProgressEvent.defaultSuccessHandler(model);
}

Expand All @@ -73,15 +66,22 @@ public ProgressEvent<ResourceModel, CallbackContext> handleRequest(
model);
}

private void createStackSet(final String stackSetName, final String requestToken) {
private void createStackSet(
final AmazonWebServicesClientProxy proxy,
final ResourceModel model,
final Logger logger,
final CloudFormationClient client,
final CallbackContext context,
final String stackSetName,
final String requestToken) {

try {
final CreateStackSetResponse response = proxy.injectCredentialsAndInvokeV2(
createStackSetRequest(model, stackSetName, requestToken), client::createStackSet);
model.setStackSetId(response.stackSetId());

logger.log(String.format("%s [%s] StackSet creation succeeded", ResourceModel.TYPE_NAME, stackSetName));

createStackInstances(stackSetName);
context.setStackSetCreated(true);

} catch (final AlreadyExistsException e) {
throw new CfnAlreadyExistsException(e);
Expand All @@ -93,25 +93,4 @@ private void createStackSet(final String stackSetName, final String requestToken
throw new CfnInvalidRequestException(e);
}
}

private void createStackInstances(final String stackSetName) {
try {
final CreateStackInstancesResponse response = proxy.injectCredentialsAndInvokeV2(
createStackInstancesRequest(stackSetName, model.getOperationPreferences(),
model.getDeploymentTargets(), model.getRegions()),
client::createStackInstances);

logger.log(String.format("%s [%s] stack instances creation initiated",
ResourceModel.TYPE_NAME, stackSetName));

context.setStabilizationStarted(true);
context.setOperationId(response.operationId());

} catch (final StackSetNotFoundException e) {
throw new CfnNotFoundException(e);

} catch (final OperationInProgressException e) {
context.incrementRetryCounter();
}
}
}
Loading

0 comments on commit e242715

Please sign in to comment.