Skip to content

Commit 36e4e41

Browse files
authored
Add support for stack and change set level hooks
* Add StackHookTargetModel for support of stack-level hooks Add new type that represents the payload sent to hook handlers that will contain information related to stack hooks. * Add support to download hook target data for stack-level hooks Stack level hooks will not be provided with invocation payload information, unlike resource level hooks. Instead, Hooks Service will pass in an S3 presigned URL that points to a file that contains the stack-level invocation payload. The base hook handler (before it reaches the customer's handler code), will use that URL to download the data and set it on the target model that is passed to the customer's handler. * Add support to download hook target data for stack-level hooks Stack level hooks will not be provided with invocation payload information, unlike resource level hooks. Instead, Hooks Service will pass in an S3 presigned URL that points to a file that contains the stack-level invocation payload. The base hook handler (before it reaches the customer's handler code), will use that URL to download the data and set it on the target model that is passed to the customer's handler. * Add support to download hook target data for stack-level hooks Stack level hooks will not be provided with invocation payload information, unlike resource level hooks. Instead, Hooks Service will pass in an S3 presigned URL that points to a file that contains the stack-level invocation payload. The base hook handler (before it reaches the customer's handler code), will use that URL to download the data and set it on the target model that is passed to the customer's handler. * Skip stack level hook for stack if prior stack level change set hook succeeded For stack level hooks, customers are able to return a new status that allow stack level hooks that execute against a stack to skip with a successful status. The idea is that if a stack hook invoked against a change set succeeds, there is no need to invoke against the stack once the change set is processed. * Skip stack level hook for stack if prior stack level change set hook succeeded For stack level hooks, customers are able to return a new status that allow stack level hooks that execute against a stack to skip with a successful status. The idea is that if a stack hook invoked against a change set succeeds, there is no need to invoke against the stack once the change set is processed. * fix method * Fix resource targetting for a stack level hook * Fix resource targetting for a stack level hook * bump version
1 parent 3b91e11 commit 36e4e41

File tree

13 files changed

+441
-14
lines changed

13 files changed

+441
-14
lines changed

pom.xml

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>software.amazon.cloudformation</groupId>
55
<artifactId>aws-cloudformation-rpdk-java-plugin</artifactId>
6-
<version>2.1.1</version>
6+
<version>2.2.1</version>
77
<name>AWS CloudFormation RPDK Java Plugin</name>
88
<description>The CloudFormation Resource Provider Development Kit (RPDK) allows you to author your own resource providers that can be used by CloudFormation. This plugin library helps to provide runtime bindings for the execution of your providers by CloudFormation.
99
</description>
@@ -491,7 +491,7 @@
491491
<plugin>
492492
<groupId>org.sonatype.plugins</groupId>
493493
<artifactId>nexus-staging-maven-plugin</artifactId>
494-
<version>1.6.13</version>
494+
<version>1.6.8</version>
495495
<extensions>true</extensions>
496496
<configuration>
497497
<serverId>sonatype-nexus-staging</serverId>

python/rpdk/java/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
22

3-
__version__ = "2.1.1"
3+
__version__ = "2.2.1"
44

55
logging.getLogger(__name__).addHandler(logging.NullHandler())

src/main/java/software/amazon/cloudformation/HookAbstractWrapper.java

+66-5
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@
1717
import com.amazonaws.AmazonServiceException;
1818
import com.amazonaws.retry.RetryUtils;
1919
import com.fasterxml.jackson.core.type.TypeReference;
20+
import com.google.common.annotations.VisibleForTesting;
21+
import java.io.ByteArrayOutputStream;
2022
import java.io.IOException;
2123
import java.io.InputStream;
2224
import java.io.OutputStream;
25+
import java.net.URISyntaxException;
26+
import java.net.URL;
2327
import java.nio.charset.StandardCharsets;
2428
import java.time.Instant;
29+
import java.util.Collections;
2530
import java.util.Date;
31+
import java.util.Map;
2632
import org.apache.commons.io.FileUtils;
2733
import org.apache.commons.io.IOUtils;
2834
import org.apache.commons.lang3.exception.ExceptionUtils;
@@ -31,10 +37,15 @@
3137
import org.slf4j.Logger;
3238
import org.slf4j.LoggerFactory;
3339
import software.amazon.awssdk.awscore.exception.AwsServiceException;
40+
import software.amazon.awssdk.http.HttpExecuteRequest;
41+
import software.amazon.awssdk.http.HttpExecuteResponse;
3442
import software.amazon.awssdk.http.HttpStatusCode;
3543
import software.amazon.awssdk.http.HttpStatusFamily;
3644
import software.amazon.awssdk.http.SdkHttpClient;
45+
import software.amazon.awssdk.http.SdkHttpMethod;
46+
import software.amazon.awssdk.http.SdkHttpRequest;
3747
import software.amazon.awssdk.http.apache.ApacheHttpClient;
48+
import software.amazon.awssdk.utils.IoUtils;
3849
import software.amazon.cloudformation.encryption.Cipher;
3950
import software.amazon.cloudformation.encryption.KMSCipher;
4051
import software.amazon.cloudformation.exceptions.BaseHandlerException;
@@ -63,6 +74,7 @@
6374
import software.amazon.cloudformation.proxy.hook.HookInvocationRequest;
6475
import software.amazon.cloudformation.proxy.hook.HookProgressEvent;
6576
import software.amazon.cloudformation.proxy.hook.HookRequestContext;
77+
import software.amazon.cloudformation.proxy.hook.HookRequestData;
6678
import software.amazon.cloudformation.proxy.hook.HookStatus;
6779
import software.amazon.cloudformation.resource.SchemaValidator;
6880
import software.amazon.cloudformation.resource.Serializer;
@@ -89,6 +101,9 @@ public abstract class HookAbstractWrapper<TargetT, CallbackT, ConfigurationT> {
89101
final SchemaValidator validator;
90102
final TypeReference<HookInvocationRequest<ConfigurationT, CallbackT>> typeReference;
91103

104+
final TypeReference<Map<String, Object>> hookStackPayloadS3TypeReference = new TypeReference<>() {
105+
};
106+
92107
private MetricsPublisher providerMetricsPublisher;
93108

94109
private CloudWatchLogHelper cloudWatchLogHelper;
@@ -222,18 +237,20 @@ private ProgressEvent<TargetT, CallbackT> processInvocation(final JSONObject raw
222237

223238
assert request != null : "Invalid request object received. Request object is null";
224239

225-
if (request.getRequestData() == null || request.getRequestData().getTargetModel() == null) {
226-
throw new TerminalException("Invalid request object received. Target Model can not be null.");
227-
}
228-
229-
// TODO: Include hook schema validation here after schema is finalized
240+
boolean isPayloadRemote = isHookInvocationPayloadRemote(request.getRequestData());
230241

231242
try {
232243
// initialise dependencies with platform credentials
233244
initialiseRuntime(request.getHookTypeName(), request.getRequestData().getProviderCredentials(),
234245
request.getRequestData().getProviderLogGroupName(), request.getAwsAccountId(),
235246
request.getRequestData().getHookEncryptionKeyArn(), request.getRequestData().getHookEncryptionKeyRole());
236247

248+
if (isPayloadRemote) {
249+
Map<String, Object> targetModelData = retrieveHookInvocationPayloadFromS3(request.getRequestData().getPayload());
250+
251+
request.getRequestData().setTargetModel(targetModelData);
252+
}
253+
237254
// transform the request object to pass to caller
238255
HookHandlerRequest hookHandlerRequest = transform(request);
239256
ConfigurationT typeConfiguration = request.getHookModel();
@@ -366,6 +383,50 @@ private void writeResponse(final OutputStream outputStream, final HookProgressEv
366383
outputStream.flush();
367384
}
368385

386+
public Map<String, Object> retrieveHookInvocationPayloadFromS3(final String s3PresignedUrl) {
387+
if (s3PresignedUrl != null) {
388+
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
389+
390+
try {
391+
URL presignedUrl = new URL(s3PresignedUrl);
392+
SdkHttpRequest httpRequest = SdkHttpRequest.builder().method(SdkHttpMethod.GET).uri(presignedUrl.toURI()).build();
393+
394+
HttpExecuteRequest executeRequest = HttpExecuteRequest.builder().request(httpRequest).build();
395+
396+
HttpExecuteResponse response = HTTP_CLIENT.prepareRequest(executeRequest).call();
397+
398+
response.responseBody().ifPresentOrElse(abortableInputStream -> {
399+
try {
400+
IoUtils.copy(abortableInputStream, byteArrayOutputStream);
401+
} catch (IOException e) {
402+
throw new RuntimeException(e);
403+
}
404+
}, () -> loggerProxy.log("Hook invocation payload is empty."));
405+
406+
String str = byteArrayOutputStream.toString(StandardCharsets.UTF_8);
407+
408+
return this.serializer.deserialize(str, hookStackPayloadS3TypeReference);
409+
} catch (RuntimeException | IOException | URISyntaxException exp) {
410+
loggerProxy.log("Failed to retrieve hook invocation payload" + exp.toString());
411+
}
412+
}
413+
return Collections.emptyMap();
414+
}
415+
416+
@VisibleForTesting
417+
protected boolean isHookInvocationPayloadRemote(HookRequestData hookRequestData) {
418+
if (hookRequestData == null) {
419+
throw new TerminalException("Invalid request object received. Target Model can not be null.");
420+
}
421+
422+
if ((hookRequestData.getTargetModel() == null || hookRequestData.getTargetModel().isEmpty())
423+
&& hookRequestData.getPayload() == null) {
424+
throw new TerminalException("No payload data set.");
425+
}
426+
427+
return (hookRequestData.getTargetModel() == null || hookRequestData.getTargetModel().isEmpty());
428+
}
429+
369430
/**
370431
* Transforms the incoming request to the subset of typed models which the
371432
* handler implementor needs

src/main/java/software/amazon/cloudformation/proxy/OperationStatus.java

+1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ public enum OperationStatus {
1818
PENDING,
1919
IN_PROGRESS,
2020
SUCCESS,
21+
CHANGE_SET_SUCCESS_SKIP_STACK_HOOK,
2122
FAILED
2223
}

src/main/java/software/amazon/cloudformation/proxy/hook/HookRequestData.java

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ public class HookRequestData {
2929
private String targetType;
3030
private String targetLogicalId;
3131
private Map<String, Object> targetModel;
32+
private String payload;
3233
private String callerCredentials;
3334
private String providerCredentials;
3435
private String providerLogGroupName;

src/main/java/software/amazon/cloudformation/proxy/hook/HookStatus.java

+1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,6 @@ public enum HookStatus {
1818
PENDING,
1919
IN_PROGRESS,
2020
SUCCESS,
21+
CHANGE_SET_SUCCESS_SKIP_STACK_HOOK,
2122
FAILED
2223
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package software.amazon.cloudformation.proxy.hook.targetmodel;
16+
17+
import com.fasterxml.jackson.annotation.JsonProperty;
18+
import lombok.AllArgsConstructor;
19+
import lombok.Builder;
20+
import lombok.Data;
21+
import lombok.NoArgsConstructor;
22+
23+
@Data
24+
@Builder
25+
@AllArgsConstructor
26+
@NoArgsConstructor
27+
public class ChangedResource {
28+
@JsonProperty("LogicalResourceId")
29+
private String logicalResourceId;
30+
31+
@JsonProperty("ResourceType")
32+
private String resourceType;
33+
34+
@JsonProperty("LineNumber")
35+
private Integer lineNumber;
36+
37+
@JsonProperty("Action")
38+
private String action;
39+
40+
@JsonProperty("ResourceProperties")
41+
private String resourceProperties;
42+
43+
@JsonProperty("PreviousResourceProperties")
44+
private String previousResourceProperties;
45+
}

src/main/java/software/amazon/cloudformation/proxy/hook/targetmodel/HookTargetType.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,17 @@ public enum HookTargetType {
2626
* A target model meant to represent a target for a Resource Hook. This model
2727
* type will have properties specific to the resource type.
2828
*/
29-
RESOURCE;
29+
RESOURCE,
30+
31+
/**
32+
* A target model meant to represent a target for a Stack Hook. This model type
33+
* will have properties specific to the stack type.
34+
*/
35+
STACK,
36+
37+
/**
38+
* A target model meant to represent a target for a stack Change Set Hook. This
39+
* model type will have properties specific to the change set type.
40+
*/
41+
CHANGE_SET;
3042
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright 2010-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package software.amazon.cloudformation.proxy.hook.targetmodel;
16+
17+
import com.fasterxml.jackson.annotation.JsonAutoDetect;
18+
import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
19+
import com.fasterxml.jackson.annotation.JsonProperty;
20+
import com.fasterxml.jackson.core.type.TypeReference;
21+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
22+
import java.util.List;
23+
import lombok.EqualsAndHashCode;
24+
import lombok.Getter;
25+
import lombok.NoArgsConstructor;
26+
import lombok.ToString;
27+
28+
@EqualsAndHashCode(callSuper = false)
29+
@Getter
30+
@NoArgsConstructor
31+
@ToString
32+
@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE)
33+
@JsonDeserialize(as = StackHookTargetModel.class)
34+
public class StackHookTargetModel extends HookTargetModel {
35+
private static final TypeReference<StackHookTargetModel> MODEL_REFERENCE = new TypeReference<StackHookTargetModel>() {
36+
};
37+
38+
@JsonProperty("Template")
39+
private Object template;
40+
41+
@JsonProperty("PreviousTemplate")
42+
private Object previousTemplate;
43+
44+
@JsonProperty("ResolvedTemplate")
45+
private Object resolvedTemplate;
46+
47+
@JsonProperty("ChangedResources")
48+
private List<ChangedResource> changedResources;
49+
50+
@Override
51+
public TypeReference<? extends HookTarget> getHookTargetTypeReference() {
52+
return null;
53+
}
54+
55+
@Override
56+
public TypeReference<? extends HookTargetModel> getTargetModelTypeReference() {
57+
return MODEL_REFERENCE;
58+
}
59+
60+
@Override
61+
public final HookTargetType getHookTargetType() {
62+
return HookTargetType.STACK;
63+
}
64+
}

src/test/java/software/amazon/cloudformation/HookLambdaWrapperOverride.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.nio.charset.StandardCharsets;
2020
import java.util.LinkedList;
2121
import java.util.List;
22+
import java.util.Map;
2223
import java.util.Queue;
2324
import lombok.Data;
2425
import lombok.EqualsAndHashCode;
@@ -32,8 +33,10 @@
3233
import software.amazon.cloudformation.metrics.MetricsPublisher;
3334
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
3435
import software.amazon.cloudformation.proxy.ProgressEvent;
36+
import software.amazon.cloudformation.proxy.hook.HookContext;
3537
import software.amazon.cloudformation.proxy.hook.HookHandlerRequest;
3638
import software.amazon.cloudformation.proxy.hook.HookInvocationRequest;
39+
import software.amazon.cloudformation.proxy.hook.targetmodel.HookTargetModel;
3740
import software.amazon.cloudformation.resource.SchemaValidator;
3841
import software.amazon.cloudformation.resource.Serializer;
3942

@@ -44,6 +47,8 @@
4447
@EqualsAndHashCode(callSuper = true)
4548
public class HookLambdaWrapperOverride extends HookLambdaWrapper<TestModel, TestContext, TestConfigurationModel> {
4649

50+
private Map<String, Object> hookInvocationPayloadFromS3;
51+
4752
/**
4853
* This .ctor provided for testing
4954
*/
@@ -112,11 +117,29 @@ public void enqueueResponses(final List<ProgressEvent<TestModel, TestContext>> r
112117

113118
@Override
114119
protected HookHandlerRequest transform(final HookInvocationRequest<TestConfigurationModel, TestContext> request) {
115-
return transformResponse;
120+
this.request = HookHandlerRequest.builder().clientRequestToken(request.getClientRequestToken())
121+
.hookContext(HookContext.builder().awsAccountId(request.getAwsAccountId()).stackId(request.getStackId())
122+
.changeSetId(request.getChangeSetId()).hookTypeName(request.getHookTypeName())
123+
.hookTypeVersion(request.getHookTypeVersion()).invocationPoint(request.getActionInvocationPoint())
124+
.targetName(request.getRequestData().getTargetName()).targetType(request.getRequestData().getTargetType())
125+
.targetLogicalId(request.getRequestData().getTargetLogicalId())
126+
.targetModel(HookTargetModel.of(request.getRequestData().getTargetModel())).build())
127+
.build();
128+
129+
return this.request;
116130
}
117131

118132
public HookHandlerRequest transformResponse;
119133

134+
@Override
135+
public Map<String, Object> retrieveHookInvocationPayloadFromS3(final String s3PresignedUrl) {
136+
return hookInvocationPayloadFromS3;
137+
}
138+
139+
public void setHookInvocationPayloadFromS3(Map<String, Object> input) {
140+
hookInvocationPayloadFromS3 = input;
141+
}
142+
120143
@Override
121144
protected TypeReference<HookInvocationRequest<TestConfigurationModel, TestContext>> getTypeReference() {
122145
return new TypeReference<HookInvocationRequest<TestConfigurationModel, TestContext>>() {

0 commit comments

Comments
 (0)