diff --git a/pom.xml b/pom.xml index 0dbcd13b..c01132fc 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 software.amazon.cloudformation aws-cloudformation-rpdk-java-plugin - 1.0.5 + 2.0.0 AWS CloudFormation RPDK Java Plugin 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. @@ -307,12 +307,12 @@ BRANCH COVEREDRATIO - 0.80 + 0.79 INSTRUCTION COVEREDRATIO - 0.87 + 0.89 diff --git a/python/rpdk/java/__init__.py b/python/rpdk/java/__init__.py index c918e14c..5684f64a 100644 --- a/python/rpdk/java/__init__.py +++ b/python/rpdk/java/__init__.py @@ -1,5 +1,5 @@ import logging -__version__ = "0.1.6" +__version__ = "2.0.0" logging.getLogger(__name__).addHandler(logging.NullHandler()) diff --git a/python/rpdk/java/codegen.py b/python/rpdk/java/codegen.py index 609f7dc3..9d03c919 100644 --- a/python/rpdk/java/codegen.py +++ b/python/rpdk/java/codegen.py @@ -2,7 +2,9 @@ # pylint doesn't recognize abstract methods import logging import shutil +import xml.etree.ElementTree as ET # nosec from collections import namedtuple +from xml.etree.ElementTree import ParseError # nosec from rpdk.core.data_loaders import resource_stream from rpdk.core.exceptions import InternalError, SysExitRecommendedError @@ -37,10 +39,29 @@ def wrapper(*args, **kwargs): return wrapper +DEFAULT_PROTOCOL_VERSION = "2.0.0" +PROTOCOL_VERSION_SETTING = "protocolVersion" +DEFAULT_SETTINGS = {PROTOCOL_VERSION_SETTING: DEFAULT_PROTOCOL_VERSION} + +MINIMUM_JAVA_DEPENDENCY_VERSION = "2.0.0" + + class JavaArchiveNotFoundError(SysExitRecommendedError): pass +class JavaPluginVersionNotSupportedError(SysExitRecommendedError): + pass + + +class JavaPluginNotFoundError(SysExitRecommendedError): + pass + + +class InvalidMavenPOMError(SysExitRecommendedError): + pass + + class JavaLanguagePlugin(LanguagePlugin): MODULE_NAME = __name__ RUNTIME = "java8" @@ -66,7 +87,7 @@ def _namespace_from_project(self, project): fallback = ("com",) + project.type_info namespace = tuple(safe_reserved(s.lower()) for s in fallback) self.namespace = project.settings["namespace"] = namespace - project._write_settings("java") # pylint: disable=protected-access + project.write_settings() self.package_name = ".".join(self.namespace) @@ -283,6 +304,7 @@ def _init_settings(self, project): project.runtime = self.RUNTIME project.entrypoint = self.ENTRY_POINT.format(self.package_name) project.test_entrypoint = self.TEST_ENTRY_POINT.format(self.package_name) + project.settings.update(DEFAULT_SETTINGS) @staticmethod def _get_generated_root(project): @@ -377,6 +399,24 @@ def generate(self, project): ) project.overwrite(path, contents) + # Update settings + java_plugin_dependency_version = self._get_java_plugin_dependency_version( + project + ) + if java_plugin_dependency_version < MINIMUM_JAVA_DEPENDENCY_VERSION: + raise JavaPluginVersionNotSupportedError( + "'aws-cloudformation-rpdk-java-plugin' {} is no longer supported." + "Please update it in pom.xml to version {} or above.".format( + java_plugin_dependency_version, MINIMUM_JAVA_DEPENDENCY_VERSION + ) + ) + protocol_version = project.settings.get(PROTOCOL_VERSION_SETTING) + if protocol_version != DEFAULT_PROTOCOL_VERSION: + project.settings[PROTOCOL_VERSION_SETTING] = DEFAULT_PROTOCOL_VERSION + project.write_settings() + + LOG.debug("Generate complete") + @staticmethod def _find_jar(project): jar_glob = list( @@ -399,6 +439,26 @@ def _find_jar(project): return jar_glob[0] + @staticmethod + def _get_java_plugin_dependency_version(project): + try: + tree = ET.parse(project.root / "pom.xml") + root = tree.getroot() + namespace = {"mvn": "http://maven.apache.org/POM/4.0.0"} + plugin_dependency_version = root.find( + "./mvn:dependencies/mvn:dependency" + "/[mvn:artifactId='aws-cloudformation-rpdk-java-plugin']/mvn:version", + namespace, + ) + if plugin_dependency_version is None: + raise JavaPluginNotFoundError( + "'aws-cloudformation-rpdk-java-plugin' maven dependency " + "not found in pom.xml." + ) + return plugin_dependency_version.text + except ParseError: + raise InvalidMavenPOMError("pom.xml is invalid.") + @logdebug def package(self, project, zip_file): """Packaging Java project""" diff --git a/python/rpdk/java/templates/init/shared/pom.xml b/python/rpdk/java/templates/init/shared/pom.xml index d811a269..b40c08d5 100644 --- a/python/rpdk/java/templates/init/shared/pom.xml +++ b/python/rpdk/java/templates/init/shared/pom.xml @@ -23,7 +23,7 @@ software.amazon.cloudformation aws-cloudformation-rpdk-java-plugin - 1.0.5 + 2.0.0 diff --git a/src/main/java/software/amazon/cloudformation/LambdaWrapper.java b/src/main/java/software/amazon/cloudformation/LambdaWrapper.java index 264164b6..c8bfdcad 100644 --- a/src/main/java/software/amazon/cloudformation/LambdaWrapper.java +++ b/src/main/java/software/amazon/cloudformation/LambdaWrapper.java @@ -14,7 +14,6 @@ */ package software.amazon.cloudformation; -import static com.google.common.util.concurrent.Uninterruptibles.sleepUninterruptibly; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.lambda.runtime.Context; import com.amazonaws.services.lambda.runtime.LambdaLogger; @@ -26,7 +25,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.time.Instant; import java.util.Arrays; @@ -34,7 +32,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; @@ -47,8 +44,6 @@ import software.amazon.cloudformation.exceptions.BaseHandlerException; import software.amazon.cloudformation.exceptions.FileScrubberException; import software.amazon.cloudformation.exceptions.TerminalException; -import software.amazon.cloudformation.injection.CloudFormationProvider; -import software.amazon.cloudformation.injection.CloudWatchEventsProvider; import software.amazon.cloudformation.injection.CloudWatchLogsProvider; import software.amazon.cloudformation.injection.CloudWatchProvider; import software.amazon.cloudformation.injection.CredentialsProvider; @@ -60,8 +55,6 @@ import software.amazon.cloudformation.metrics.MetricsPublisher; import software.amazon.cloudformation.metrics.MetricsPublisherImpl; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; -import software.amazon.cloudformation.proxy.CallbackAdapter; -import software.amazon.cloudformation.proxy.CloudFormationCallbackAdapter; import software.amazon.cloudformation.proxy.Credentials; import software.amazon.cloudformation.proxy.DelayFactory; import software.amazon.cloudformation.proxy.HandlerErrorCode; @@ -70,21 +63,18 @@ import software.amazon.cloudformation.proxy.MetricsPublisherProxy; import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; -import software.amazon.cloudformation.proxy.RequestContext; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.cloudformation.resource.ResourceTypeSchema; import software.amazon.cloudformation.resource.SchemaValidator; import software.amazon.cloudformation.resource.Serializer; import software.amazon.cloudformation.resource.Validator; import software.amazon.cloudformation.resource.exceptions.ValidationException; -import software.amazon.cloudformation.scheduler.CloudWatchScheduler; public abstract class LambdaWrapper implements RequestStreamHandler { public static final SdkHttpClient HTTP_CLIENT = ApacheHttpClient.builder().build(); private static final List MUTATING_ACTIONS = Arrays.asList(Action.CREATE, Action.DELETE, Action.UPDATE); - private static final int INVOCATION_TIMEOUT_MS = 60000; protected final Serializer serializer; protected LoggerProxy loggerProxy; @@ -95,33 +85,22 @@ public abstract class LambdaWrapper implements RequestStre // provider... prefix indicates credential provided by resource owner - final CredentialsProvider platformCredentialsProvider; final CredentialsProvider providerCredentialsProvider; - final CloudFormationProvider cloudFormationProvider; - final CloudWatchProvider platformCloudWatchProvider; final CloudWatchProvider providerCloudWatchProvider; - final CloudWatchEventsProvider platformCloudWatchEventsProvider; final CloudWatchLogsProvider cloudWatchLogsProvider; final SchemaValidator validator; final TypeReference> typeReference; - private CallbackAdapter callbackAdapter; - private MetricsPublisher platformMetricsPublisher; private MetricsPublisher providerMetricsPublisher; - private CloudWatchScheduler scheduler; private LogPublisher platformLambdaLogger; private CloudWatchLogHelper cloudWatchLogHelper; private CloudWatchLogPublisher providerEventsLogger; protected LambdaWrapper() { - this.platformCredentialsProvider = new SessionCredentialsProvider(); this.providerCredentialsProvider = new SessionCredentialsProvider(); - this.cloudFormationProvider = new CloudFormationProvider(this.platformCredentialsProvider, HTTP_CLIENT); - this.platformCloudWatchProvider = new CloudWatchProvider(this.platformCredentialsProvider, HTTP_CLIENT); this.providerCloudWatchProvider = new CloudWatchProvider(this.providerCredentialsProvider, HTTP_CLIENT); - this.platformCloudWatchEventsProvider = new CloudWatchEventsProvider(this.platformCredentialsProvider, HTTP_CLIENT); this.cloudWatchLogsProvider = new CloudWatchLogsProvider(this.providerCredentialsProvider, HTTP_CLIENT); this.serializer = new Serializer(); this.validator = new Validator(); @@ -131,31 +110,20 @@ protected LambdaWrapper() { /* * This .ctor provided for testing */ - public LambdaWrapper(final CallbackAdapter callbackAdapter, - final CredentialsProvider platformCredentialsProvider, - final CredentialsProvider providerCredentialsProvider, - final CloudWatchLogPublisher providerEventsLogger, + public LambdaWrapper(final CredentialsProvider providerCredentialsProvider, final LogPublisher platformEventsLogger, - final MetricsPublisher platformMetricsPublisher, + final CloudWatchLogPublisher providerEventsLogger, final MetricsPublisher providerMetricsPublisher, - final CloudWatchScheduler scheduler, final SchemaValidator validator, final Serializer serializer, final SdkHttpClient httpClient) { - this.callbackAdapter = callbackAdapter; - this.platformCredentialsProvider = platformCredentialsProvider; this.providerCredentialsProvider = providerCredentialsProvider; - this.cloudFormationProvider = new CloudFormationProvider(this.platformCredentialsProvider, httpClient); - this.platformCloudWatchProvider = new CloudWatchProvider(this.platformCredentialsProvider, httpClient); this.providerCloudWatchProvider = new CloudWatchProvider(this.providerCredentialsProvider, httpClient); - this.platformCloudWatchEventsProvider = new CloudWatchEventsProvider(this.platformCredentialsProvider, httpClient); this.cloudWatchLogsProvider = new CloudWatchLogsProvider(this.providerCredentialsProvider, httpClient); this.providerEventsLogger = providerEventsLogger; this.platformLambdaLogger = platformEventsLogger; - this.platformMetricsPublisher = platformMetricsPublisher; this.providerMetricsPublisher = providerMetricsPublisher; - this.scheduler = scheduler; this.serializer = serializer; this.validator = validator; this.typeReference = getTypeReference(); @@ -166,12 +134,10 @@ public LambdaWrapper(final CallbackAdapter callbackAdapter, * passed at function invoke and not available during construction */ private void initialiseRuntime(final String resourceType, - final Credentials platformCredentials, final Credentials providerCredentials, final String providerLogGroupName, final Context context, - final String awsAccountId, - final URI callbackEndpoint) { + final String awsAccountId) { this.loggerProxy = new LoggerProxy(); this.metricsPublisherProxy = new MetricsPublisherProxy(); @@ -179,20 +145,8 @@ private void initialiseRuntime(final String resourceType, this.platformLambdaLogger = new LambdaLogPublisher(context.getLogger()); this.loggerProxy.addLogPublisher(this.platformLambdaLogger); - this.cloudFormationProvider.setCallbackEndpoint(callbackEndpoint); - this.platformCredentialsProvider.setCredentials(platformCredentials); - // Initialisation skipped if dependencies were set during injection (in unit // tests). - // e.g. "if (this.platformMetricsPublisher == null)" - if (this.platformMetricsPublisher == null) { - // platformMetricsPublisher needs aws account id to differentiate metrics - // namespace - this.platformMetricsPublisher = new MetricsPublisherImpl(this.platformCloudWatchProvider, this.loggerProxy, - awsAccountId, resourceType); - } - this.metricsPublisherProxy.addMetricsPublisher(this.platformMetricsPublisher); - this.platformMetricsPublisher.refreshClient(); // NOTE: providerCredentials and providerLogGroupName are null/not null in // sync. @@ -222,18 +176,6 @@ private void initialiseRuntime(final String resourceType, this.loggerProxy.addLogPublisher(this.providerEventsLogger); this.providerEventsLogger.refreshClient(); } - - if (this.callbackAdapter == null) { - this.callbackAdapter = new CloudFormationCallbackAdapter<>(this.cloudFormationProvider, this.loggerProxy, - this.serializer, ResourceTypeSchema - .load(provideResourceSchemaJSONObject())); - } - this.callbackAdapter.refreshClient(); - - if (this.scheduler == null) { - this.scheduler = new CloudWatchScheduler(this.platformCloudWatchEventsProvider, this.loggerProxy, this.serializer); - } - this.scheduler.refreshClient(); } @Override @@ -254,8 +196,6 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp String input = this.serializer.decompress(IOUtils.toString(inputStream, StandardCharsets.UTF_8)); JSONObject rawInput = new JSONObject(new JSONTokener(input)); - bearerToken = rawInput.getString("bearerToken"); - // deserialize incoming payload to modelled request try { request = this.serializer.deserialize(input, typeReference); @@ -292,7 +232,7 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp } finally { // A response will be output on all paths, though CloudFormation will // not block on invoking the handlers, but rather listen for callbacks - writeResponse(outputStream, createProgressResponse(handlerResponse, bearerToken)); + writeResponse(outputStream, handlerResponse); } } @@ -313,42 +253,13 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp } } - if (StringUtils.isEmpty(request.getResponseEndpoint())) { - throw new TerminalException("No callback endpoint received"); - } - - // ensure required execution credentials have been passed and inject them - if (request.getRequestData().getPlatformCredentials() == null) { - throw new TerminalException("Missing required platform credentials"); - } - - // initialise dependencies with platform credentials - initialiseRuntime(request.getResourceType(), request.getRequestData().getPlatformCredentials(), - request.getRequestData().getProviderCredentials(), request.getRequestData().getProviderLogGroupName(), context, - request.getAwsAccountId(), URI.create(request.getResponseEndpoint())); + // initialise dependencies + initialiseRuntime(request.getResourceType(), request.getRequestData().getProviderCredentials(), + request.getRequestData().getProviderLogGroupName(), context, request.getAwsAccountId()); // transform the request object to pass to caller ResourceHandlerRequest resourceHandlerRequest = transform(request); - RequestContext requestContext = request.getRequestContext(); - - if (requestContext == null || requestContext.getInvocation() == 0) { - // Acknowledge the task for first time invocation - this.callbackAdapter.reportProgress(request.getBearerToken(), null, OperationStatus.IN_PROGRESS, - OperationStatus.PENDING, null, null); - } - - if (requestContext != null) { - // If this invocation was triggered by a 're-invoke' CloudWatch Event, clean it - // up - String cloudWatchEventsRuleName = requestContext.getCloudWatchEventsRuleName(); - if (!StringUtils.isBlank(cloudWatchEventsRuleName)) { - this.scheduler.cleanupCloudWatchEvents(cloudWatchEventsRuleName, requestContext.getCloudWatchEventsTargetId()); - log(String.format("Cleaned up previous Request Context of Rule %s and Target %s", - requestContext.getCloudWatchEventsRuleName(), requestContext.getCloudWatchEventsTargetId())); - } - } - this.metricsPublisherProxy.publishInvocationMetric(Instant.now(), request.getAction()); // for CUD actions, validate incoming model - any error is a terminal failure on @@ -382,48 +293,27 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp } } publishExceptionMetric(request.getAction(), e, HandlerErrorCode.InvalidRequest); - this.callbackAdapter.reportProgress(request.getBearerToken(), HandlerErrorCode.InvalidRequest, - OperationStatus.FAILED, OperationStatus.IN_PROGRESS, null, validationMessageBuilder.toString()); return ProgressEvent.defaultFailureHandler(new TerminalException(validationMessageBuilder.toString(), e), HandlerErrorCode.InvalidRequest); } } + CallbackT callbackContext = request.getCallbackContext(); // last mile proxy creation with passed-in credentials (unless we are operating // in a non-AWS model) AmazonWebServicesClientProxy awsClientProxy = null; if (request.getRequestData().getCallerCredentials() != null) { - awsClientProxy = new AmazonWebServicesClientProxy(requestContext == null, this.loggerProxy, + awsClientProxy = new AmazonWebServicesClientProxy(callbackContext == null, this.loggerProxy, request.getRequestData().getCallerCredentials(), () -> (long) context.getRemainingTimeInMillis(), DelayFactory.CONSTANT_DEFAULT_DELAY_FACTORY); } - boolean computeLocally = true; - ProgressEvent handlerResponse = null; - - while (computeLocally) { - // rebuild callback context on each invocation cycle - requestContext = request.getRequestContext(); - CallbackT callbackContext = (requestContext != null) ? requestContext.getCallbackContext() : null; - - handlerResponse = wrapInvocationAndHandleErrors(awsClientProxy, resourceHandlerRequest, request, callbackContext); + ProgressEvent handlerResponse = wrapInvocationAndHandleErrors(awsClientProxy, + resourceHandlerRequest, request, callbackContext); - // report the progress status back to configured endpoint on - // mutating/potentially asynchronous actions - - if (isMutatingAction) { - this.callbackAdapter.reportProgress(request.getBearerToken(), handlerResponse.getErrorCode(), - handlerResponse.getStatus(), OperationStatus.IN_PROGRESS, handlerResponse.getResourceModel(), - handlerResponse.getMessage()); - } else if (handlerResponse.getStatus() == OperationStatus.IN_PROGRESS) { - throw new TerminalException("READ and LIST handlers must return synchronously."); - } - // When the handler responses IN_PROGRESS with a callback delay, we trigger a - // callback to re-invoke - // the handler for the Resource type to implement stabilization checks and - // long-poll creation checks - computeLocally = scheduleReinvocation(request, handlerResponse, context); + if (handlerResponse.getStatus() == OperationStatus.IN_PROGRESS && !isMutatingAction) { + throw new TerminalException("READ and LIST handlers must return synchronously."); } return handlerResponse; @@ -479,22 +369,16 @@ public void handleRequest(final InputStream inputStream, final OutputStream outp } - private Response createProgressResponse(final ProgressEvent progressEvent, - final String bearerToken) { - - Response response = new Response<>(); - response.setMessage(progressEvent.getMessage()); - response.setOperationStatus(progressEvent.getStatus()); - response.setResourceModel(progressEvent.getResourceModel()); - response.setErrorCode(progressEvent.getErrorCode()); - response.setBearerToken(bearerToken); - response.setResourceModels(progressEvent.getResourceModels()); - response.setNextToken(progressEvent.getNextToken()); + private void writeResponse(final OutputStream outputStream, final ProgressEvent response) + throws IOException { + ResourceT model = response.getResourceModel(); + if (model != null) { + JSONObject modelObject = new JSONObject(this.serializer.serialize(model)); + ResourceTypeSchema.load(provideResourceSchemaJSONObject()).removeWriteOnlyProperties(modelObject); + ResourceT sanitizedModel = this.serializer.deserializeStrict(modelObject.toString(), getModelTypeReference()); - return response; - } - - private void writeResponse(final OutputStream outputStream, final Response response) throws IOException { + response.setResourceModel(sanitizedModel); + } String output = this.serializer.serialize(response); outputStream.write(output.getBytes(StandardCharsets.UTF_8)); @@ -522,64 +406,6 @@ private void validateModel(final JSONObject modelObject) throws ValidationExcept this.validator.validateObject(serializedModel, resourceSchemaJSONObject); } - /** - * Managed scheduling of handler re-invocations. - * - * @param request the original request to the function - * @param handlerResponse the previous response from handler - * @param context LambdaContext granting runtime metadata - * @return boolean indicating whether to continue invoking locally, or exit for - * async reinvoke - */ - private boolean scheduleReinvocation(final HandlerRequest request, - final ProgressEvent handlerResponse, - final Context context) { - - if (handlerResponse.getStatus() != OperationStatus.IN_PROGRESS) { - // no reinvoke required - return false; - } - - RequestContext reinvocationContext = new RequestContext<>(); - RequestContext requestContext = request.getRequestContext(); - - int counter = 1; - if (requestContext != null) { - counter += requestContext.getInvocation(); - } - reinvocationContext.setInvocation(counter); - - reinvocationContext.setCallbackContext(handlerResponse.getCallbackContext()); - request.setRequestContext(reinvocationContext); - - // when a handler requests a sub-minute callback delay, and if the lambda - // invocation - // has enough runtime (with 20% buffer), we can reschedule from a thread wait - // otherwise we re-invoke through CloudWatchEvents which have a granularity of - // minutes - // This also guarantees a maximum of a minute of execution time per local - // reinvocation - if ((handlerResponse.getCallbackDelaySeconds() < 60) && context - .getRemainingTimeInMillis() > Math.abs(handlerResponse.getCallbackDelaySeconds()) * 1200 + INVOCATION_TIMEOUT_MS) { - log(String.format("Scheduling re-invoke locally after %s seconds, with Context {%s}", - handlerResponse.getCallbackDelaySeconds(), reinvocationContext.toString())); - sleepUninterruptibly(handlerResponse.getCallbackDelaySeconds(), TimeUnit.SECONDS); - return true; - } - - log(String.format("Scheduling re-invoke with Context {%s}", reinvocationContext.toString())); - try { - int callbackDelayMinutes = Math.abs(handlerResponse.getCallbackDelaySeconds() / 60); - this.scheduler.rescheduleAfterMinutes(context.getInvokedFunctionArn(), callbackDelayMinutes, request); - } catch (final Throwable e) { - handlerResponse.setMessage("Failed to schedule re-invoke."); - handlerResponse.setStatus(OperationStatus.FAILED); - handlerResponse.setErrorCode(HandlerErrorCode.InternalFailure); - } - - return false; - } - /** * Transforms the incoming request to the subset of typed models which the * handler implementor needs diff --git a/src/main/java/software/amazon/cloudformation/proxy/HandlerRequest.java b/src/main/java/software/amazon/cloudformation/proxy/HandlerRequest.java index a5d698bb..cc8ada67 100644 --- a/src/main/java/software/amazon/cloudformation/proxy/HandlerRequest.java +++ b/src/main/java/software/amazon/cloudformation/proxy/HandlerRequest.java @@ -29,10 +29,10 @@ public class HandlerRequest { private String bearerToken; private String nextToken; private String region; - private String responseEndpoint; private String resourceType; private String resourceTypeVersion; private RequestData requestData; private String stackId; + private CallbackT callbackContext; private RequestContext requestContext; } diff --git a/src/main/java/software/amazon/cloudformation/proxy/RequestData.java b/src/main/java/software/amazon/cloudformation/proxy/RequestData.java index d63affb8..d6b1cbc1 100644 --- a/src/main/java/software/amazon/cloudformation/proxy/RequestData.java +++ b/src/main/java/software/amazon/cloudformation/proxy/RequestData.java @@ -22,7 +22,6 @@ @NoArgsConstructor public class RequestData { private Credentials callerCredentials; - private Credentials platformCredentials; private Credentials providerCredentials; private String providerLogGroupName; private String logicalResourceId; diff --git a/src/test/java/software/amazon/cloudformation/LambdaWrapperTest.java b/src/test/java/software/amazon/cloudformation/LambdaWrapperTest.java index 9adfc9bd..8b104bad 100644 --- a/src/test/java/software/amazon/cloudformation/LambdaWrapperTest.java +++ b/src/test/java/software/amazon/cloudformation/LambdaWrapperTest.java @@ -16,11 +16,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.*; import com.amazonaws.AmazonServiceException; import com.amazonaws.services.lambda.runtime.Context; @@ -35,9 +32,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.time.Instant; -import java.util.Arrays; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.json.JSONObject; import org.junit.jupiter.api.BeforeEach; @@ -45,12 +40,9 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import org.mockito.ArgumentCaptor; -import org.mockito.ArgumentMatchers; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import software.amazon.awssdk.http.SdkHttpClient; -import software.amazon.awssdk.services.cloudformation.model.OperationStatusCheckFailedException; import software.amazon.cloudformation.exceptions.ResourceAlreadyExistsException; import software.amazon.cloudformation.exceptions.ResourceNotFoundException; import software.amazon.cloudformation.exceptions.TerminalException; @@ -58,11 +50,9 @@ import software.amazon.cloudformation.loggers.CloudWatchLogPublisher; import software.amazon.cloudformation.loggers.LogPublisher; import software.amazon.cloudformation.metrics.MetricsPublisher; -import software.amazon.cloudformation.proxy.CallbackAdapter; import software.amazon.cloudformation.proxy.Credentials; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.HandlerRequest; -import software.amazon.cloudformation.proxy.HandlerResponse; import software.amazon.cloudformation.proxy.OperationStatus; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.RequestData; @@ -71,25 +61,15 @@ import software.amazon.cloudformation.resource.Serializer; import software.amazon.cloudformation.resource.Validator; import software.amazon.cloudformation.resource.exceptions.ValidationException; -import software.amazon.cloudformation.scheduler.CloudWatchScheduler; @ExtendWith(MockitoExtension.class) public class LambdaWrapperTest { private static final String TEST_DATA_BASE_PATH = "src/test/java/software/amazon/cloudformation/data/%s"; - @Mock - private CallbackAdapter callbackAdapter; - - @Mock - private CredentialsProvider platformCredentialsProvider; - @Mock private CredentialsProvider providerLoggingCredentialsProvider; - @Mock - private MetricsPublisher platformMetricsPublisher; - @Mock private MetricsPublisher providerMetricsPublisher; @@ -99,9 +79,6 @@ public class LambdaWrapperTest { @Mock private LogPublisher platformEventsLogger; - @Mock - private CloudWatchScheduler scheduler; - @Mock private SchemaValidator validator; @@ -118,9 +95,8 @@ public class LambdaWrapperTest { @BeforeEach public void initWrapper() { - wrapper = new WrapperOverride(callbackAdapter, platformCredentialsProvider, providerLoggingCredentialsProvider, - platformEventsLogger, providerEventsLogger, platformMetricsPublisher, - providerMetricsPublisher, scheduler, validator, httpClient); + wrapper = new WrapperOverride(providerLoggingCredentialsProvider, platformEventsLogger, providerEventsLogger, + providerMetricsPublisher, validator, httpClient); } public static InputStream loadRequestStream(final String fileName) { @@ -142,25 +118,20 @@ private Context getLambdaContext() { } private void verifyInitialiseRuntime() { - verify(platformCredentialsProvider).setCredentials(any(Credentials.class)); verify(providerLoggingCredentialsProvider).setCredentials(any(Credentials.class)); - verify(callbackAdapter).refreshClient(); - verify(platformMetricsPublisher).refreshClient(); verify(providerMetricsPublisher).refreshClient(); - verify(scheduler).refreshClient(); } - private void verifyHandlerResponse(final OutputStream out, final HandlerResponse expected) throws IOException { + private void verifyHandlerResponse(final OutputStream out, final ProgressEvent expected) + throws IOException { final Serializer serializer = new Serializer(); - final HandlerResponse< - TestModel> handlerResponse = serializer.deserialize(out.toString(), new TypeReference>() { + final ProgressEvent handlerResponse = serializer.deserialize(out.toString(), + new TypeReference>() { }); - assertThat(handlerResponse.getBearerToken()).isEqualTo(expected.getBearerToken()); assertThat(handlerResponse.getErrorCode()).isEqualTo(expected.getErrorCode()); assertThat(handlerResponse.getNextToken()).isEqualTo(expected.getNextToken()); - assertThat(handlerResponse.getOperationStatus()).isEqualTo(expected.getOperationStatus()); - assertThat(handlerResponse.getStabilizationData()).isEqualTo(expected.getStabilizationData()); + assertThat(handlerResponse.getStatus()).isEqualTo(expected.getStatus()); assertThat(handlerResponse.getResourceModel()).isEqualTo(expected.getResourceModel()); } @@ -187,14 +158,10 @@ public void invokeHandler_nullResponse_returnsFailure(final String requestDataPa verifyInitialiseRuntime(); // validation failure metric should be published for final error handling - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), - any(TerminalException.class), any(HandlerErrorCode.class)); verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), any(TerminalException.class), any(HandlerErrorCode.class)); // all metrics should be published even on terminal failure - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); verify(providerMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); @@ -203,16 +170,13 @@ public void invokeHandler_nullResponse_returnsFailure(final String requestDataPa verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); } - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - verify(providerEventsLogger).refreshClient(); verify(providerEventsLogger, times(2)).publishLogEvent(any()); verifyNoMoreInteractions(providerEventsLogger); // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("Handler failed to provide a response.").build()); + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("Handler failed to provide a response.").build()); // assert handler receives correct injections assertThat(wrapper.awsClientProxy).isNotNull(); @@ -243,22 +207,14 @@ private void invokeHandler_without_customerLoggingCredentials(final String reque wrapper.handleRequest(in, out, context); // verify initialiseRuntime was called and initialised dependencies - verify(platformCredentialsProvider).setCredentials(any(Credentials.class)); verify(providerLoggingCredentialsProvider, times(0)).setCredentials(any(Credentials.class)); - verify(callbackAdapter).refreshClient(); - verify(platformMetricsPublisher).refreshClient(); verify(providerMetricsPublisher, times(0)).refreshClient(); - verify(scheduler).refreshClient(); // validation failure metric should be published for final error handling - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), - any(TerminalException.class), any(HandlerErrorCode.class)); verify(providerMetricsPublisher, times(0)).publishExceptionMetric(any(Instant.class), any(), any(TerminalException.class), any(HandlerErrorCode.class)); // all metrics should be published even on terminal failure - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); verify(providerMetricsPublisher, times(0)).publishInvocationMetric(any(Instant.class), eq(action)); verify(providerMetricsPublisher, times(0)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); @@ -267,14 +223,11 @@ private void invokeHandler_without_customerLoggingCredentials(final String reque verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); } - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - verifyNoMoreInteractions(providerEventsLogger); // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("Handler failed to provide a response.").build()); + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("Handler failed to provide a response.").build()); } } @@ -300,24 +253,14 @@ public void invokeHandler_handlerFailed_returnsFailure(final String requestDataP // verify initialiseRuntime was called and initialised dependencies verifyInitialiseRuntime(); - // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); - - // validation failure metric should not be published - verifyNoMoreInteractions(platformMetricsPublisher); - // verify that model validation occurred for CREATE/UPDATE/DELETE if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) { verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); } - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("Custom Fault").build()); + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("Custom Fault").build()); // assert handler receives correct injections assertThat(wrapper.awsClientProxy).isNotNull(); @@ -339,8 +282,7 @@ public void invokeHandler_withNullInput() throws IOException { try (final InputStream in = null; final OutputStream out = new ByteArrayOutputStream()) { final Context context = getLambdaContext(); wrapper.handleRequest(in, out, context); - verifyNoMoreInteractions(callbackAdapter, platformMetricsPublisher, platformEventsLogger, providerMetricsPublisher, - providerEventsLogger); + verifyNoMoreInteractions(providerMetricsPublisher, providerEventsLogger); } } @@ -370,24 +312,13 @@ public void invokeHandler_CompleteSynchronously_returnsSuccess(final String requ // verify initialiseRuntime was called and initialised dependencies verifyInitialiseRuntime(); - // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); - - // validation failure metric should not be published - verifyNoMoreInteractions(platformMetricsPublisher); - // verify that model validation occurred for CREATE/UPDATE/DELETE if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) { verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); } - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.SUCCESS).build()); + verifyHandlerResponse(out, ProgressEvent.builder().status(OperationStatus.SUCCESS).build()); // assert handler receives correct injections assertThat(wrapper.awsClientProxy).isNotNull(); @@ -414,7 +345,7 @@ public void invokeHandler_DependenciesInitialised_CompleteSynchronously_returnsS wrapper.setTransformResponse(resourceHandlerRequest); // use a request context in our payload to bypass certain callbacks - try (final InputStream in = loadRequestStream("create.with-request-context.request.json"); + try (final InputStream in = loadRequestStream("create.with-callback-context.request.json"); final OutputStream out = new ByteArrayOutputStream()) { final Context context = getLambdaContext(); @@ -426,12 +357,8 @@ public void invokeHandler_DependenciesInitialised_CompleteSynchronously_returnsS assertThat(wrapper.loggerProxy).isNotNull(); assertThat(wrapper.metricsPublisherProxy).isNotNull(); assertThat(wrapper.lambdaLogger).isNotNull(); - assertThat(wrapper.platformCredentialsProvider).isNotNull(); assertThat(wrapper.providerCredentialsProvider).isNotNull(); - assertThat(wrapper.cloudFormationProvider).isNotNull(); - assertThat(wrapper.platformCloudWatchProvider).isNotNull(); assertThat(wrapper.providerCloudWatchProvider).isNotNull(); - assertThat(wrapper.platformCloudWatchEventsProvider).isNotNull(); assertThat(wrapper.cloudWatchLogsProvider).isNotNull(); assertThat(wrapper.validator).isNotNull(); } @@ -463,8 +390,6 @@ public void invokeHandler_InProgress_returnsInProgress(final String requestDataP verifyInitialiseRuntime(); // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); verify(providerMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); @@ -472,32 +397,19 @@ public void invokeHandler_InProgress_returnsInProgress(final String requestDataP if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) { verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); - // re-invocation via CloudWatch should occur - verify(scheduler, times(1)).rescheduleAfterMinutes(anyString(), eq(0), - ArgumentMatchers.>any()); - - // CloudFormation should receive a callback invocation - verify(callbackAdapter, times(1)).reportProgress(eq("123456"), isNull(), eq(OperationStatus.IN_PROGRESS), - eq(OperationStatus.IN_PROGRESS), eq(TestModel.builder().property1("abc").property2(123).build()), isNull()); - // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.IN_PROGRESS) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); + verifyHandlerResponse(out, ProgressEvent.builder().status(OperationStatus.IN_PROGRESS) + .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); } else { verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.FAILED) - .errorCode(HandlerErrorCode.InternalFailure.name()) - .message("READ and LIST handlers must return synchronously.").build()); + ProgressEvent.builder().status(OperationStatus.FAILED) + .errorCode(HandlerErrorCode.InternalFailure).message("READ and LIST handlers must return synchronously.") + .build()); verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), eq(action), any(TerminalException.class), eq(HandlerErrorCode.InternalFailure)); - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), eq(action), - any(TerminalException.class), eq(HandlerErrorCode.InternalFailure)); } // validation failure metric should not be published - verifyNoMoreInteractions(platformMetricsPublisher); verifyNoMoreInteractions(providerMetricsPublisher); - verifyNoMoreInteractions(scheduler); // assert handler receives correct injections assertThat(wrapper.awsClientProxy).isNotNull(); @@ -508,8 +420,8 @@ public void invokeHandler_InProgress_returnsInProgress(final String requestDataP } @ParameterizedTest - @CsvSource({ "create.with-request-context.request.json,CREATE", "update.with-request-context.request.json,UPDATE", - "delete.with-request-context.request.json,DELETE" }) + @CsvSource({ "create.with-callback-context.request.json,CREATE", "update.with-callback-context.request.json,UPDATE", + "delete.with-callback-context.request.json,DELETE" }) public void reInvokeHandler_InProgress_returnsInProgress(final String requestDataPath, final String actionAsString) throws IOException { final Action action = Action.valueOf(actionAsString); @@ -535,14 +447,10 @@ public void reInvokeHandler_InProgress_returnsInProgress(final String requestDat verifyInitialiseRuntime(); // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); - verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); verify(providerMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(action), anyLong()); // validation failure metric should not be published - verifyNoMoreInteractions(platformMetricsPublisher); verifyNoMoreInteractions(providerMetricsPublisher); // verify that model validation occurred for CREATE/UPDATE/DELETE @@ -550,22 +458,9 @@ public void reInvokeHandler_InProgress_returnsInProgress(final String requestDat verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); } - // re-invocation via CloudWatch should occur - verify(scheduler, times(1)).rescheduleAfterMinutes(anyString(), eq(1), - ArgumentMatchers.>any()); - - // this was a re-invocation, so a cleanup is required - verify(scheduler, times(1)).cleanupCloudWatchEvents(eq("reinvoke-handler-4754ac8a-623b-45fe-84bc-f5394118a8be"), - eq("reinvoke-target-4754ac8a-623b-45fe-84bc-f5394118a8be")); - - // CloudFormation should receive a callback invocation - verify(callbackAdapter, times(1)).reportProgress(eq("123456"), isNull(), eq(OperationStatus.IN_PROGRESS), - eq(OperationStatus.IN_PROGRESS), eq(TestModel.builder().property1("abc").property2(123).build()), isNull()); - // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.IN_PROGRESS) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); + verifyHandlerResponse(out, ProgressEvent.builder().status(OperationStatus.IN_PROGRESS) + .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); } } @@ -587,48 +482,28 @@ public void invokeHandler_SchemaValidationFailure(final String requestDataPath, verifyInitialiseRuntime(); // validation failure metric should be published but no others - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), eq(action), - any(Exception.class), any(HandlerErrorCode.class)); verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), eq(action), any(Exception.class), any(HandlerErrorCode.class)); // all metrics should be published, even for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(action)); - // duration metric only published when the provider handler is invoked - verifyNoMoreInteractions(platformMetricsPublisher); - // verify that model validation occurred for CREATE/UPDATE/DELETE if (action == Action.CREATE || action == Action.UPDATE || action == Action.DELETE) { verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); } - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - - // first report to acknowledge the task - verify(callbackAdapter).reportProgress(any(), any(), eq(OperationStatus.IN_PROGRESS), eq(OperationStatus.PENDING), - any(), any()); - - // second report to record validation failure - verify(callbackAdapter).reportProgress(any(), any(), eq(OperationStatus.FAILED), eq(OperationStatus.IN_PROGRESS), - any(), any()); - verifyNoMoreInteractions(callbackAdapter); - // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().bearerToken("123456").errorCode("InvalidRequest") - .operationStatus(OperationStatus.FAILED).message("Model validation failed with unknown cause.").build()); + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InvalidRequest) + .status(OperationStatus.FAILED).message("Model validation failed with unknown cause.").build()); } } @Test public void invokeHandler_invalidModelTypes_causesSchemaValidationFailure() throws IOException { // use actual validator to verify behaviour - final WrapperOverride wrapper = new WrapperOverride(callbackAdapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, platformEventsLogger, - providerEventsLogger, platformMetricsPublisher, - providerMetricsPublisher, scheduler, new Validator() { + final WrapperOverride wrapper = new WrapperOverride(providerLoggingCredentialsProvider, platformEventsLogger, + providerEventsLogger, providerMetricsPublisher, new Validator() { }, httpClient); wrapper.setTransformResponse(resourceHandlerRequest); @@ -641,8 +516,8 @@ providerMetricsPublisher, scheduler, new Validator() { // verify output response verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("InvalidRequest") - .operationStatus(OperationStatus.FAILED) + ProgressEvent.builder().errorCode(HandlerErrorCode.InvalidRequest) + .status(OperationStatus.FAILED) .message("Model validation failed (#/property1: expected type: String, found: JSONArray)").build()); } } @@ -650,10 +525,8 @@ providerMetricsPublisher, scheduler, new Validator() { @Test public void invokeHandler_extraneousModelFields_causesSchemaValidationFailure() throws IOException { // use actual validator to verify behaviour - final WrapperOverride wrapper = new WrapperOverride(callbackAdapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, platformEventsLogger, - providerEventsLogger, platformMetricsPublisher, - providerMetricsPublisher, scheduler, new Validator() { + final WrapperOverride wrapper = new WrapperOverride(providerLoggingCredentialsProvider, platformEventsLogger, + providerEventsLogger, providerMetricsPublisher, new Validator() { }, httpClient); wrapper.setTransformResponse(resourceHandlerRequest); @@ -665,36 +538,18 @@ providerMetricsPublisher, scheduler, new Validator() { wrapper.handleRequest(in, out, context); // validation failure metric should be published but no others - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), eq(Action.CREATE), - any(Exception.class), any(HandlerErrorCode.class)); - verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), eq(Action.CREATE), any(Exception.class), any(HandlerErrorCode.class)); // all metrics should be published, even for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); // verify initialiseRuntime was called and initialised dependencies verifyInitialiseRuntime(); - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - - // first report to acknowledge the task - verify(callbackAdapter).reportProgress(any(), any(), eq(OperationStatus.IN_PROGRESS), eq(OperationStatus.PENDING), - any(), any()); - - // second report to record validation failure - verify(callbackAdapter).reportProgress(any(), any(), eq(OperationStatus.FAILED), eq(OperationStatus.IN_PROGRESS), - any(), any()); - - verifyNoMoreInteractions(callbackAdapter); - // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().bearerToken("123456").errorCode("InvalidRequest") - .operationStatus(OperationStatus.FAILED) + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InvalidRequest) + .status(OperationStatus.FAILED) .message("Model validation failed (#: extraneous key [fieldCausesValidationError] is not permitted)").build()); } } @@ -722,26 +577,8 @@ public void invokeHandler_withMalformedRequest_causesSchemaValidationFailure() t wrapper.handleRequest(in, out, context); // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().bearerToken("123456") - .operationStatus(OperationStatus.SUCCESS).resourceModel(TestModel.builder().build()).build()); - } - } - - @Test - public void invokeHandler_withoutPlatformCredentials_returnsFailure() throws IOException { - // without platform credentials the handler is unable to do - // basic SDK initialization and any such request should fail fast - try (final InputStream in = loadRequestStream("create.request-without-platform-credentials.json"); - final OutputStream out = new ByteArrayOutputStream()) { - final Context context = getLambdaContext(); - - wrapper.handleRequest(in, out, context); - - // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("Missing required platform credentials") - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); + verifyHandlerResponse(out, ProgressEvent.builder().status(OperationStatus.SUCCESS) + .resourceModel(TestModel.builder().build()).build()); } } @@ -757,8 +594,6 @@ public void invokeHandler_withoutCallerCredentials_passesNoAWSProxy() throws IOE .status(OperationStatus.SUCCESS).resourceModel(model).build(); wrapper.setInvokeHandlerResponse(pe); - // without platform credentials the handler is unable to do - // basic SDK initialization and any such request should fail fast try (final InputStream in = loadRequestStream("create.request-without-caller-credentials.json"); final OutputStream out = new ByteArrayOutputStream()) { final Context context = getLambdaContext(); @@ -766,9 +601,8 @@ public void invokeHandler_withoutCallerCredentials_passesNoAWSProxy() throws IOE wrapper.handleRequest(in, out, context); // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.SUCCESS) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); + verifyHandlerResponse(out, ProgressEvent.builder().status(OperationStatus.SUCCESS) + .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); // proxy should be null by virtue of having not had callerCredentials passed in assertThat(wrapper.awsClientProxy).isNull(); @@ -787,8 +621,6 @@ public void invokeHandler_withDefaultInjection_returnsSuccess() throws IOExcepti .status(OperationStatus.SUCCESS).resourceModel(model).build(); wrapper.setInvokeHandlerResponse(pe); - // without platform credentials the handler is unable to do - // basic SDK initialization and any such request should fail fast try (final InputStream in = loadRequestStream("create.request.json"); final OutputStream out = new ByteArrayOutputStream()) { final Context context = getLambdaContext(); @@ -796,9 +628,8 @@ public void invokeHandler_withDefaultInjection_returnsSuccess() throws IOExcepti wrapper.handleRequest(in, out, context); // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.SUCCESS) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); + verifyHandlerResponse(out, ProgressEvent.builder().status(OperationStatus.SUCCESS) + .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); // proxy uses caller credentials and will be injected assertThat(wrapper.awsClientProxy).isNotNull(); @@ -817,46 +648,14 @@ public void invokeHandler_withDefaultInjection_returnsInProgress() throws IOExce .status(OperationStatus.IN_PROGRESS).resourceModel(model).build(); wrapper.setInvokeHandlerResponse(pe); - // without platform credentials the handler is unable to do - // basic SDK initialization and any such request should fail fast try (final InputStream in = loadRequestStream("create.request.json"); final OutputStream out = new ByteArrayOutputStream()) { final Context context = getLambdaContext(); wrapper.handleRequest(in, out, context); // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.IN_PROGRESS) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); - } - } - - @Test - public void invokeHandler_failToRescheduleInvocation() throws IOException { - final TestModel model = new TestModel(); - model.setProperty1("abc"); - model.setProperty2(123); - doThrow(new AmazonServiceException("some error")).when(scheduler).rescheduleAfterMinutes(anyString(), anyInt(), - ArgumentMatchers.>any()); - wrapper.setTransformResponse(resourceHandlerRequest); - - // respond with in progress status to trigger callback invocation - final ProgressEvent pe = ProgressEvent.builder() - .status(OperationStatus.IN_PROGRESS).resourceModel(model).build(); - wrapper.setInvokeHandlerResponse(pe); - - try (final InputStream in = loadRequestStream("create.request.json"); - final OutputStream out = new ByteArrayOutputStream()) { - final Context context = getLambdaContext(); - - wrapper.handleRequest(in, out, context); - - // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED) - .message("some error (Service: null; Status Code: 0; Error Code: null; Request ID: null)") - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); + verifyHandlerResponse(out, ProgressEvent.builder().status(OperationStatus.IN_PROGRESS) + .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); } } @@ -867,247 +666,12 @@ public void invokeHandler_clientsRefreshedOnEveryInvoke() throws IOException { wrapper.handleRequest(in, out, context); } - verify(callbackAdapter, times(1)).refreshClient(); - verify(platformMetricsPublisher, times(1)).refreshClient(); - verify(scheduler, times(1)).refreshClient(); - // invoke the same wrapper instance again to ensure client is refreshed context = getLambdaContext(); try (InputStream in = loadRequestStream("create.request.json"); OutputStream out = new ByteArrayOutputStream()) { wrapper.handleRequest(in, out, context); } - verify(callbackAdapter, times(2)).refreshClient(); - verify(platformMetricsPublisher, times(2)).refreshClient(); - verify(scheduler, times(2)).refreshClient(); - } - - @Test - public void invokeHandler_platformCredentialsRefreshedOnEveryInvoke() throws IOException { - Context context = getLambdaContext(); - try (InputStream in = loadRequestStream("create.request.json"); OutputStream out = new ByteArrayOutputStream()) { - wrapper.handleRequest(in, out, context); - } - - final Credentials expected = new Credentials("32IEHAHFIAG538KYASAI", "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal"); - verify(platformCredentialsProvider, times(1)).setCredentials(eq(expected)); - // invoke the same wrapper instance again to ensure client is refreshed - context = getLambdaContext(); - try (InputStream in = loadRequestStream("create.request.with-new-credentials.json"); - OutputStream out = new ByteArrayOutputStream()) { - wrapper.handleRequest(in, out, context); - } - - final Credentials expectedNew = new Credentials("GT530IJDHALYZQSZZ8XG", "UeJEwC/dqcYEn2viFd5TjKjR5TaMOfdeHrlLXxQL", - "469gs8raWJCaZcItXhGJ7dt3urI13fOTcde6ibhuHJz6r6bRRCWvLYGvCsqrN8WUClYL9lxZHymrWXvZ9xN0GoI2LFdcAAinZk5t"); - - verify(platformCredentialsProvider, times(1)).setCredentials(eq(expectedNew)); - } - - @Test - public void invokeHandler_withNoResponseEndpoint_returnsFailure() throws IOException { - final TestModel model = new TestModel(); - - // an InProgress response is always re-scheduled. - // If no explicit time is supplied, a 1-minute interval is used - final ProgressEvent pe = ProgressEvent.builder() - .status(OperationStatus.SUCCESS).resourceModel(model).build(); - wrapper.setInvokeHandlerResponse(pe); - wrapper.setTransformResponse(resourceHandlerRequest); - - // our ObjectMapper implementation will ignore extraneous fields rather than - // fail them - // this slightly loosens the coupling between caller (CloudFormation) and - // handlers. - try (final InputStream in = loadRequestStream("no-response-endpoint.request.json"); - final OutputStream out = new ByteArrayOutputStream()) { - final Context context = getLambdaContext(); - - wrapper.handleRequest(in, out, context); - - // malformed input exception is published - verify(lambdaLogger, times(1)).log(anyString()); - verifyNoMoreInteractions(platformMetricsPublisher); - - // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("No callback endpoint received") - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); - } - } - - @Test - public void invokeHandler_localReinvokeWithSufficientRemainingTime() throws IOException { - final TestModel model = TestModel.builder().property1("abc").property2(123).build(); - - // an InProgress response is always re-scheduled. - // If no explicit time is supplied, a 1-minute interval is used - final ProgressEvent pe1 = ProgressEvent.builder().status(OperationStatus.IN_PROGRESS) // iterate - // locally once - .callbackDelaySeconds(5).resourceModel(model).build(); - final ProgressEvent pe2 = ProgressEvent.builder().status(OperationStatus.SUCCESS) // then exit loop - .resourceModel(model).build(); - wrapper.enqueueResponses(Arrays.asList(pe1, pe2)); - - wrapper.setTransformResponse(resourceHandlerRequest); - - try (final InputStream in = loadRequestStream("create.with-request-context.request.json"); - final OutputStream out = new ByteArrayOutputStream()) { - - final Context context = getLambdaContext(); - // give enough time to invoke again locally - when(context.getRemainingTimeInMillis()).thenReturn(75000); - - wrapper.handleRequest(in, out, context); - - // verify initialiseRuntime was called and initialised dependencies - verifyInitialiseRuntime(); - - // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(platformMetricsPublisher, times(2)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); - - verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(providerMetricsPublisher, times(2)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); - - // validation failure metric should not be published - verifyNoMoreInteractions(platformMetricsPublisher); - verifyNoMoreInteractions(providerMetricsPublisher); - - // verify that model validation occurred for CREATE/UPDATE/DELETE - verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); - - // this was a re-invocation, so a cleanup is required - verify(scheduler, times(1)).cleanupCloudWatchEvents(eq("reinvoke-handler-4754ac8a-623b-45fe-84bc-f5394118a8be"), - eq("reinvoke-target-4754ac8a-623b-45fe-84bc-f5394118a8be")); - - // re-invocation via CloudWatch should NOT occur for <60 when Lambda remaining - // time allows - verifyNoMoreInteractions(scheduler); - - final ArgumentCaptor bearerTokenCaptor = ArgumentCaptor.forClass(String.class); - final ArgumentCaptor errorCodeCaptor = ArgumentCaptor.forClass(HandlerErrorCode.class); - final ArgumentCaptor operationStatusCaptor = ArgumentCaptor.forClass(OperationStatus.class); - final ArgumentCaptor resourceModelCaptor = ArgumentCaptor.forClass(TestModel.class); - final ArgumentCaptor statusMessageCaptor = ArgumentCaptor.forClass(String.class); - - verify(callbackAdapter, times(2)).reportProgress(bearerTokenCaptor.capture(), errorCodeCaptor.capture(), - operationStatusCaptor.capture(), operationStatusCaptor.capture(), resourceModelCaptor.capture(), - statusMessageCaptor.capture()); - - final List bearerTokens = bearerTokenCaptor.getAllValues(); - final List errorCodes = errorCodeCaptor.getAllValues(); - final List operationStatuses = operationStatusCaptor.getAllValues(); - final List resourceModels = resourceModelCaptor.getAllValues(); - final List statusMessages = statusMessageCaptor.getAllValues(); - - // CloudFormation should receive 2 callback invocation; once for IN_PROGRESS, - // once for COMPLETION - assertThat(bearerTokens).containsExactly("123456", "123456"); - assertThat(errorCodes).containsExactly(null, null); - assertThat(resourceModels).containsExactly(TestModel.builder().property1("abc").property2(123).build(), - TestModel.builder().property1("abc").property2(123).build()); - assertThat(statusMessages).containsExactly(null, null); - assertThat(operationStatuses).containsExactly(OperationStatus.IN_PROGRESS, OperationStatus.IN_PROGRESS, - OperationStatus.SUCCESS, OperationStatus.IN_PROGRESS); - - // verify final output response is for success response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.SUCCESS) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); - } - } - - @Test - public void invokeHandler_localReinvokeWithSufficientRemainingTimeForFirstIterationOnly_SchedulesViaCloudWatch() - throws IOException { - final TestModel model = TestModel.builder().property1("abc").property2(123).build(); - - // an InProgress response is always re-scheduled. - // If no explicit time is supplied, a 1-minute interval is used - final ProgressEvent pe1 = ProgressEvent.builder().status(OperationStatus.IN_PROGRESS) // iterate - // locally once - .callbackDelaySeconds(5).resourceModel(model).build(); - final ProgressEvent pe2 = ProgressEvent.builder().status(OperationStatus.IN_PROGRESS) // second - // iteration - // will exceed - // runtime - .callbackDelaySeconds(5).resourceModel(model).build(); - wrapper.enqueueResponses(Arrays.asList(pe1, pe2)); - - wrapper.setTransformResponse(resourceHandlerRequest); - - try (final InputStream in = loadRequestStream("create.with-request-context.request.json"); - final OutputStream out = new ByteArrayOutputStream()) { - - final Context context = getLambdaContext(); - // first remaining time allows for a local reinvocation, whereas the latter will - // force the second invocation to be via CWE - when(context.getRemainingTimeInMillis()).thenReturn(70000, 5000); - - wrapper.handleRequest(in, out, context); - - // verify initialiseRuntime was called and initialised dependencies - verifyInitialiseRuntime(); - - // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(platformMetricsPublisher, times(2)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); - - verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(providerMetricsPublisher, times(2)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); - - // validation failure metric should not be published - verifyNoMoreInteractions(platformMetricsPublisher); - verifyNoMoreInteractions(providerMetricsPublisher); - - // verify that model validation occurred for CREATE/UPDATE/DELETE - verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); - - // re-invocation via CloudWatch should occur for the second iteration - verify(scheduler, times(1)).rescheduleAfterMinutes(anyString(), eq(0), - ArgumentMatchers.>any()); - - // this was a re-invocation, so a cleanup is required - verify(scheduler, times(1)).cleanupCloudWatchEvents(eq("reinvoke-handler-4754ac8a-623b-45fe-84bc-f5394118a8be"), - eq("reinvoke-target-4754ac8a-623b-45fe-84bc-f5394118a8be")); - - final ArgumentCaptor bearerTokenCaptor = ArgumentCaptor.forClass(String.class); - final ArgumentCaptor errorCodeCaptor = ArgumentCaptor.forClass(HandlerErrorCode.class); - final ArgumentCaptor operationStatusCaptor = ArgumentCaptor.forClass(OperationStatus.class); - final ArgumentCaptor resourceModelCaptor = ArgumentCaptor.forClass(TestModel.class); - final ArgumentCaptor statusMessageCaptor = ArgumentCaptor.forClass(String.class); - - verify(callbackAdapter, times(2)).reportProgress(bearerTokenCaptor.capture(), errorCodeCaptor.capture(), - operationStatusCaptor.capture(), operationStatusCaptor.capture(), resourceModelCaptor.capture(), - statusMessageCaptor.capture()); - - final List bearerTokens = bearerTokenCaptor.getAllValues(); - final List errorCodes = errorCodeCaptor.getAllValues(); - final List operationStatuses = operationStatusCaptor.getAllValues(); - final List resourceModels = resourceModelCaptor.getAllValues(); - final List statusMessages = statusMessageCaptor.getAllValues(); - - // CloudFormation should receive 2 callback invocation; both for IN_PROGRESS - assertThat(bearerTokens).containsExactly("123456", "123456"); - assertThat(errorCodes).containsExactly(null, null); - assertThat(resourceModels).containsExactly(TestModel.builder().property1("abc").property2(123).build(), - TestModel.builder().property1("abc").property2(123).build()); - assertThat(statusMessages).containsExactly(null, null); - assertThat(operationStatuses).containsExactly(OperationStatus.IN_PROGRESS, OperationStatus.IN_PROGRESS, - OperationStatus.IN_PROGRESS, OperationStatus.IN_PROGRESS); - - // verify final output response is for second IN_PROGRESS response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").operationStatus(OperationStatus.IN_PROGRESS) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); - } } @Test @@ -1127,29 +691,20 @@ public void invokeHandler_throwsAmazonServiceException_returnsServiceException() verifyInitialiseRuntime(); // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); - verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); verify(providerMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); // failure metric should be published - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), - any(AmazonServiceException.class), any(HandlerErrorCode.class)); - verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), any(AmazonServiceException.class), any(HandlerErrorCode.class)); // verify that model validation occurred for CREATE/UPDATE/DELETE verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - // verify output response verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("GeneralServiceException") - .operationStatus(OperationStatus.FAILED) + ProgressEvent.builder().errorCode(HandlerErrorCode.GeneralServiceException) + .status(OperationStatus.FAILED) .message("some error (Service: null; Status Code: 0; Error Code: null; Request ID: null)").build()); } } @@ -1171,29 +726,20 @@ public void invokeHandler_throwsResourceAlreadyExistsException_returnsAlreadyExi verifyInitialiseRuntime(); // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); - verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); verify(providerMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); // failure metric should be published - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), - any(ResourceAlreadyExistsException.class), any(HandlerErrorCode.class)); - verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), any(ResourceAlreadyExistsException.class), any(HandlerErrorCode.class)); // verify that model validation occurred for CREATE/UPDATE/DELETE verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - // verify output response verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("AlreadyExists") - .operationStatus(OperationStatus.FAILED) + ProgressEvent.builder().errorCode(HandlerErrorCode.AlreadyExists) + .status(OperationStatus.FAILED) .message("Resource of type 'AWS::Test::TestModel' with identifier 'id-1234' already exists.").build()); } } @@ -1215,29 +761,20 @@ public void invokeHandler_throwsResourceNotFoundException_returnsNotFound() thro verifyInitialiseRuntime(); // all metrics should be published, once for a single invocation - verify(platformMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); - verify(platformMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); - verify(providerMetricsPublisher, times(1)).publishInvocationMetric(any(Instant.class), eq(Action.CREATE)); verify(providerMetricsPublisher, times(1)).publishDurationMetric(any(Instant.class), eq(Action.CREATE), anyLong()); // failure metric should be published - verify(platformMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), - any(ResourceNotFoundException.class), any(HandlerErrorCode.class)); - verify(providerMetricsPublisher, times(1)).publishExceptionMetric(any(Instant.class), any(), any(ResourceNotFoundException.class), any(HandlerErrorCode.class)); // verify that model validation occurred for CREATE/UPDATE/DELETE verify(validator, times(1)).validateObject(any(JSONObject.class), any(JSONObject.class)); - // no re-invocation via CloudWatch should occur - verifyNoMoreInteractions(scheduler); - // verify output response verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("NotFound") - .operationStatus(OperationStatus.FAILED) + ProgressEvent.builder().errorCode(HandlerErrorCode.NotFound) + .status(OperationStatus.FAILED) .message("Resource of type 'AWS::Test::TestModel' with identifier 'id-1234' was not found.").build()); } } @@ -1246,8 +783,8 @@ public void invokeHandler_throwsResourceNotFoundException_returnsNotFound() thro public void invokeHandler_metricPublisherThrowable_returnsFailureResponse() throws IOException { // simulate runtime Errors in the metrics publisher (such as dependency // resolution conflicts) - doThrow(new Error("not an Exception")).when(platformMetricsPublisher).publishInvocationMetric(any(), any()); - doThrow(new Error("not an Exception")).when(platformMetricsPublisher).publishExceptionMetric(any(), any(), any(), any()); + doThrow(new Error("not an Exception")).when(providerMetricsPublisher).publishInvocationMetric(any(), any()); + doThrow(new Error("not an Exception")).when(providerMetricsPublisher).publishExceptionMetric(any(), any(), any(), any()); try (final InputStream in = loadRequestStream("create.request.json"); final OutputStream out = new ByteArrayOutputStream()) { @@ -1263,13 +800,12 @@ public void invokeHandler_metricPublisherThrowable_returnsFailureResponse() thro verifyInitialiseRuntime(); // no further calls to metrics publisher should occur - verifyNoMoreInteractions(platformMetricsPublisher); verifyNoMoreInteractions(providerMetricsPublisher); // verify output response verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("not an Exception") + ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("not an Exception") .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); } } @@ -1287,8 +823,9 @@ public void invokeHandler_withInvalidPayload_returnsFailureResponse() throws IOE // verify output response verifyHandlerResponse(out, - HandlerResponse.builder().errorCode("InternalFailure").operationStatus(OperationStatus.FAILED) - .message("A JSONObject text must begin with '{' at 0 [character 1 line 1]").build()); + ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("A JSONObject text must begin with '{' at 0 [character 1 line 1]") + .build()); } } @@ -1304,8 +841,8 @@ public void invokeHandler_withNullInputStream_returnsFailureResponse() throws IO } // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("No request object received").build()); + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("No request object received").build()); } } @@ -1322,8 +859,8 @@ public void invokeHandler_withEmptyPayload_returnsFailure() throws IOException { } // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message("Invalid request object received").build()); + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("Invalid request object received").build()); } } @@ -1340,8 +877,8 @@ public void invokeHandler_withEmptyResourceProperties_returnsFailure() throws IO } // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().errorCode("InternalFailure").bearerToken("123456") - .operationStatus(OperationStatus.FAILED).message("Invalid resource properties object received").build()); + verifyHandlerResponse(out, ProgressEvent.builder().errorCode(HandlerErrorCode.InternalFailure) + .status(OperationStatus.FAILED).message("Invalid resource properties object received").build()); } } @@ -1354,10 +891,9 @@ public void stringifiedPayload_validation_successful() throws IOException { // may have quoted values where the JSON Serialization is still able to // construct a valid POJO SchemaValidator validator = new Validator(); - final WrapperOverride wrapper = new WrapperOverride(callbackAdapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, platformEventsLogger, - providerEventsLogger, platformMetricsPublisher, - providerMetricsPublisher, scheduler, validator, httpClient); + final WrapperOverride wrapper = new WrapperOverride(providerLoggingCredentialsProvider, platformEventsLogger, + providerEventsLogger, providerMetricsPublisher, validator, + httpClient); // explicit fault response is treated as an unsuccessful synchronous completion final ProgressEvent pe = ProgressEvent.builder() @@ -1373,44 +909,9 @@ public void stringifiedPayload_validation_successful() throws IOException { wrapper.handleRequest(in, out, context); // verify output response - verifyHandlerResponse(out, HandlerResponse.builder().bearerToken("123456").message("Handler was invoked") - .operationStatus(OperationStatus.SUCCESS).build()); - - } - } - - @Test - public void handleRequest_apiThrowsOperationStatusException_returnsFailedStatus() throws IOException { - final String errorMessage = "Unexpected status"; - final OperationStatusCheckFailedException exception = OperationStatusCheckFailedException.builder().message(errorMessage) - .build(); + verifyHandlerResponse(out, ProgressEvent.builder().message("Handler was invoked") + .status(OperationStatus.SUCCESS).build()); - // simulate runtime Errors in the callback adapter (such as multiple handlers - // are invoked - // for single task by CloudFormation) - doThrow(exception).when(callbackAdapter).reportProgress(any(), any(), eq(OperationStatus.IN_PROGRESS), - eq(OperationStatus.PENDING), any(), any()); - - try (final InputStream in = loadRequestStream("create.request.json"); - final OutputStream out = new ByteArrayOutputStream()) { - final Context context = getLambdaContext(); - - wrapper.handleRequest(in, out, context); - - // verify initialiseRuntime was called and initialised dependencies - verifyInitialiseRuntime(); - // only calls to callback adapter to acknowledge the task - verify(callbackAdapter).reportProgress(any(), any(), eq(OperationStatus.IN_PROGRESS), eq(OperationStatus.PENDING), - any(), any()); - - // no further calls to callback adapter should occur - verifyNoMoreInteractions(callbackAdapter); - - // verify output response - verifyHandlerResponse(out, - HandlerResponse.builder().bearerToken("123456").errorCode("InternalFailure") - .operationStatus(OperationStatus.FAILED).message(errorMessage) - .resourceModel(TestModel.builder().property1("abc").property2(123).build()).build()); } } diff --git a/src/test/java/software/amazon/cloudformation/WrapperOverride.java b/src/test/java/software/amazon/cloudformation/WrapperOverride.java index ccfa0b94..40458d41 100644 --- a/src/test/java/software/amazon/cloudformation/WrapperOverride.java +++ b/src/test/java/software/amazon/cloudformation/WrapperOverride.java @@ -15,8 +15,6 @@ package software.amazon.cloudformation; import com.fasterxml.jackson.core.type.TypeReference; -import java.io.ByteArrayInputStream; -import java.nio.charset.StandardCharsets; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -31,13 +29,11 @@ import software.amazon.cloudformation.loggers.LogPublisher; import software.amazon.cloudformation.metrics.MetricsPublisher; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; -import software.amazon.cloudformation.proxy.CallbackAdapter; import software.amazon.cloudformation.proxy.HandlerRequest; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.cloudformation.resource.SchemaValidator; import software.amazon.cloudformation.resource.Serializer; -import software.amazon.cloudformation.scheduler.CloudWatchScheduler; /** * Test class used for testing of LambdaWrapper functionality @@ -55,25 +51,19 @@ public WrapperOverride() { /** * This .ctor provided for testing */ - public WrapperOverride(final CallbackAdapter callbackAdapter, - final CredentialsProvider platformCredentialsProvider, - final CredentialsProvider providerLoggingCredentialsProvider, + public WrapperOverride(final CredentialsProvider providerLoggingCredentialsProvider, final LogPublisher platformEventsLogger, final CloudWatchLogPublisher providerEventsLogger, - final MetricsPublisher platformMetricsPublisher, final MetricsPublisher providerMetricsPublisher, - final CloudWatchScheduler scheduler, final SchemaValidator validator, final SdkHttpClient httpClient) { - super(callbackAdapter, platformCredentialsProvider, providerLoggingCredentialsProvider, providerEventsLogger, - platformEventsLogger, platformMetricsPublisher, providerMetricsPublisher, scheduler, validator, new Serializer(), - httpClient); + super(providerLoggingCredentialsProvider, platformEventsLogger, providerEventsLogger, providerMetricsPublisher, validator, + new Serializer(), httpClient); } @Override protected JSONObject provideResourceSchemaJSONObject() { - return new JSONObject(new JSONTokener(new ByteArrayInputStream("{ \"properties\": { \"property1\": { \"type\": \"string\" }, \"property2\": { \"type\": \"integer\" } }, \"additionalProperties\": false }" - .getBytes(StandardCharsets.UTF_8)))); + return new JSONObject(new JSONTokener(this.getClass().getResourceAsStream("wrapper-override.json"))); } @Override diff --git a/src/test/java/software/amazon/cloudformation/data/create.request-with-stringified-resource.json b/src/test/java/software/amazon/cloudformation/data/create.request-with-stringified-resource.json index a76ada91..fb88410f 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request-with-stringified-resource.json +++ b/src/test/java/software/amazon/cloudformation/data/create.request-with-stringified-resource.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/create.request-without-caller-credentials.json b/src/test/java/software/amazon/cloudformation/data/create.request-without-caller-credentials.json index 9ade6040..6ea673f7 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request-without-caller-credentials.json +++ b/src/test/java/software/amazon/cloudformation/data/create.request-without-caller-credentials.json @@ -8,11 +8,6 @@ "resourceTypeVersion": "1.0", "requestContext": {}, "requestData": { - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/create.request-without-logging-credentials.json b/src/test/java/software/amazon/cloudformation/data/create.request-without-logging-credentials.json index 6ece680a..5c0c23f7 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request-without-logging-credentials.json +++ b/src/test/java/software/amazon/cloudformation/data/create.request-without-logging-credentials.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "logicalResourceId": "myBucket", "resourceProperties": { "property1": "abc", diff --git a/src/test/java/software/amazon/cloudformation/data/create.request.json b/src/test/java/software/amazon/cloudformation/data/create.request.json index 29d9d6e9..49976366 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request.json +++ b/src/test/java/software/amazon/cloudformation/data/create.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-model-fields.json b/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-model-fields.json index 9ed2cd44..7b8764f2 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-model-fields.json +++ b/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-model-fields.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-request-fields.json b/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-request-fields.json index 9931a033..492e1c57 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-request-fields.json +++ b/src/test/java/software/amazon/cloudformation/data/create.request.with-extraneous-request-fields.json @@ -15,11 +15,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/create.request.with-new-credentials.json b/src/test/java/software/amazon/cloudformation/data/create.request.with-new-credentials.json index 45a54368..259218fd 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request.with-new-credentials.json +++ b/src/test/java/software/amazon/cloudformation/data/create.request.with-new-credentials.json @@ -13,11 +13,6 @@ "secretAccessKey": "2VTH81RmywQPwF0n0f7g2KyGaARd7YKs71/C3y9J0", "sessionToken": "pmHYfNFZAaJkvLGYW1s3mWFVlUu9ygNJcvBZS0gF7gmYe5jTqNYjSjtgwgdOSFt1yB4ETRp2TgGnjpgXPTz7espt3Au4nnxh1Mto" }, - "platformCredentials": { - "accessKeyId": "GT530IJDHALYZQSZZ8XG", - "secretAccessKey": "UeJEwC/dqcYEn2viFd5TjKjR5TaMOfdeHrlLXxQL", - "sessionToken": "469gs8raWJCaZcItXhGJ7dt3urI13fOTcde6ibhuHJz6r6bRRCWvLYGvCsqrN8WUClYL9lxZHymrWXvZ9xN0GoI2LFdcAAinZk5t" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/create.request-without-platform-credentials.json b/src/test/java/software/amazon/cloudformation/data/create.with-callback-context.request.json similarity index 91% rename from src/test/java/software/amazon/cloudformation/data/create.request-without-platform-credentials.json rename to src/test/java/software/amazon/cloudformation/data/create.with-callback-context.request.json index 49976366..01ce361c 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.request-without-platform-credentials.json +++ b/src/test/java/software/amazon/cloudformation/data/create.with-callback-context.request.json @@ -6,7 +6,9 @@ "responseEndpoint": "https://cloudformation.us-west-2.amazonaws.com", "resourceType": "AWS::Test::TestModel", "resourceTypeVersion": "1.0", - "requestContext": {}, + "callbackContext": { + "contextPropertyA": "Value" + }, "requestData": { "callerCredentials": { "accessKeyId": "IASAYK835GAIFHAHEI23", @@ -20,10 +22,7 @@ }, "providerLogGroupName": "providerLoggingGroupName", "logicalResourceId": "myBucket", - "resourceProperties": { - "property1": "abc", - "property2": 123 - }, + "resourceProperties": {}, "systemTags": { "aws:cloudformation:stack-id": "SampleStack" }, diff --git a/src/test/java/software/amazon/cloudformation/data/create.with-request-context.request.json b/src/test/java/software/amazon/cloudformation/data/create.with-request-context.request.json index c3357f80..b2595364 100644 --- a/src/test/java/software/amazon/cloudformation/data/create.with-request-context.request.json +++ b/src/test/java/software/amazon/cloudformation/data/create.with-request-context.request.json @@ -20,11 +20,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/delete.request.json b/src/test/java/software/amazon/cloudformation/data/delete.request.json index 2342cabb..f7225b3b 100644 --- a/src/test/java/software/amazon/cloudformation/data/delete.request.json +++ b/src/test/java/software/amazon/cloudformation/data/delete.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/delete.with-callback-context.request.json b/src/test/java/software/amazon/cloudformation/data/delete.with-callback-context.request.json new file mode 100644 index 00000000..9d141773 --- /dev/null +++ b/src/test/java/software/amazon/cloudformation/data/delete.with-callback-context.request.json @@ -0,0 +1,28 @@ +{ + "awsAccountId": "123456789012", + "bearerToken": "123456", + "region": "us-east-1", + "action": "DELETE", + "responseEndpoint": "https://cloudformation.us-west-2.amazonaws.com", + "resourceType": "AWS::Test::TestModel", + "resourceTypeVersion": "1.0", + "callbackContext": { + "contextPropertyA": "Value" + }, + "requestData": { + "callerCredentials": { + "accessKeyId": "IASAYK835GAIFHAHEI23", + "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", + "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" + }, + "providerCredentials": { + "accessKeyId": "HDI0745692Y45IUTYR78", + "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", + "sessionToken": "842HYOFIQAEUDF78R8T7IU43HSADYGIFHBJSDHFA87SDF9PYvN1CEYASDUYFT5TQ97YASIHUDFAIUEYRISDKJHFAYSUDTFSDFADS" + }, + "providerLogGroupName": "providerLoggingGroupName", + "logicalResourceId": "myBucket", + "resourceProperties": {} + }, + "stackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968" +} diff --git a/src/test/java/software/amazon/cloudformation/data/delete.with-request-context.request.json b/src/test/java/software/amazon/cloudformation/data/delete.with-request-context.request.json index 5051bac6..b690d30b 100644 --- a/src/test/java/software/amazon/cloudformation/data/delete.with-request-context.request.json +++ b/src/test/java/software/amazon/cloudformation/data/delete.with-request-context.request.json @@ -20,11 +20,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/empty.resource.request.json b/src/test/java/software/amazon/cloudformation/data/empty.resource.request.json index a0a60ece..2f9a6577 100644 --- a/src/test/java/software/amazon/cloudformation/data/empty.resource.request.json +++ b/src/test/java/software/amazon/cloudformation/data/empty.resource.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "logicalResourceId": "myBucket", "systemTags": { "aws:cloudformation:stack-id": "SampleStack" diff --git a/src/test/java/software/amazon/cloudformation/data/list.request.json b/src/test/java/software/amazon/cloudformation/data/list.request.json index 2c850ee3..45bfd95f 100644 --- a/src/test/java/software/amazon/cloudformation/data/list.request.json +++ b/src/test/java/software/amazon/cloudformation/data/list.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/malformed.request.json b/src/test/java/software/amazon/cloudformation/data/malformed.request.json index fb310987..c36dfef2 100644 --- a/src/test/java/software/amazon/cloudformation/data/malformed.request.json +++ b/src/test/java/software/amazon/cloudformation/data/malformed.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/no-response-endpoint.request.json b/src/test/java/software/amazon/cloudformation/data/no-response-endpoint.request.json index 17fe0f97..b717899e 100644 --- a/src/test/java/software/amazon/cloudformation/data/no-response-endpoint.request.json +++ b/src/test/java/software/amazon/cloudformation/data/no-response-endpoint.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/read.request.json b/src/test/java/software/amazon/cloudformation/data/read.request.json index 648b5240..aa9fc2d1 100644 --- a/src/test/java/software/amazon/cloudformation/data/read.request.json +++ b/src/test/java/software/amazon/cloudformation/data/read.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/update.request.json b/src/test/java/software/amazon/cloudformation/data/update.request.json index 304a196e..bfa64bb5 100644 --- a/src/test/java/software/amazon/cloudformation/data/update.request.json +++ b/src/test/java/software/amazon/cloudformation/data/update.request.json @@ -13,11 +13,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/data/update.with-callback-context.request.json b/src/test/java/software/amazon/cloudformation/data/update.with-callback-context.request.json new file mode 100644 index 00000000..a4a723ec --- /dev/null +++ b/src/test/java/software/amazon/cloudformation/data/update.with-callback-context.request.json @@ -0,0 +1,38 @@ +{ + "awsAccountId": "123456789012", + "bearerToken": "123456", + "region": "us-east-1", + "action": "UPDATE", + "responseEndpoint": "https://cloudformation.us-west-2.amazonaws.com", + "resourceType": "AWS::Test::TestModel", + "resourceTypeVersion": "1.0", + "callbackContext": { + "contextPropertyA": "Value" + }, + "requestData": { + "callerCredentials": { + "accessKeyId": "IASAYK835GAIFHAHEI23", + "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", + "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" + }, + "providerCredentials": { + "accessKeyId": "HDI0745692Y45IUTYR78", + "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", + "sessionToken": "842HYOFIQAEUDF78R8T7IU43HSADYGIFHBJSDHFA87SDF9PYvN1CEYASDUYFT5TQ97YASIHUDFAIUEYRISDKJHFAYSUDTFSDFADS" + }, + "providerLogGroupName": "providerLoggingGroupName", + "logicalResourceId": "myBucket", + "resourceProperties": {}, + "previousResourceProperties": {}, + "systemTags": { + "aws:cloudformation:stack-id": "SampleStack" + }, + "stackTags": { + "tag1": "abc" + }, + "previousStackTags": { + "tag1": "def" + } + }, + "stackId": "arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968" +} diff --git a/src/test/java/software/amazon/cloudformation/data/update.with-request-context.request.json b/src/test/java/software/amazon/cloudformation/data/update.with-request-context.request.json index c304b3bd..45649587 100644 --- a/src/test/java/software/amazon/cloudformation/data/update.with-request-context.request.json +++ b/src/test/java/software/amazon/cloudformation/data/update.with-request-context.request.json @@ -20,11 +20,6 @@ "secretAccessKey": "66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0", "sessionToken": "lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg" }, - "platformCredentials": { - "accessKeyId": "32IEHAHFIAG538KYASAI", - "secretAccessKey": "0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66", - "sessionToken": "gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal" - }, "providerCredentials": { "accessKeyId": "HDI0745692Y45IUTYR78", "secretAccessKey": "4976TUYVI234/5GW87ERYG823RF87GY9EIUH452I3", diff --git a/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogHelperTest.java b/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogHelperTest.java index 4aaceed0..ec418ae3 100644 --- a/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogHelperTest.java +++ b/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogHelperTest.java @@ -24,13 +24,11 @@ import static org.mockito.Mockito.when; import com.amazonaws.services.lambda.runtime.LambdaLogger; import com.google.common.collect.ImmutableList; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; import software.amazon.awssdk.services.cloudwatchlogs.model.CreateLogGroupRequest; import software.amazon.awssdk.services.cloudwatchlogs.model.CreateLogStreamRequest; @@ -38,7 +36,6 @@ import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogGroupsResponse; import software.amazon.awssdk.services.cloudwatchlogs.model.LogGroup; import software.amazon.cloudformation.injection.CloudWatchLogsProvider; -import software.amazon.cloudformation.injection.CloudWatchProvider; import software.amazon.cloudformation.proxy.MetricsPublisherProxy; @ExtendWith(MockitoExtension.class) @@ -56,20 +53,8 @@ public class CloudWatchLogHelperTest { @Mock private MetricsPublisherProxy metricsPublisherProxy; - @Mock - private CloudWatchProvider platformCloudWatchProvider; - - @Mock - private CloudWatchClient platformCloudWatchClient; - private static final String LOG_GROUP_NAME = "log-group-name"; - @AfterEach - public void afterEach() { - verifyNoMoreInteractions(platformCloudWatchProvider); - verifyNoMoreInteractions(platformCloudWatchClient); - } - @Test public void testWithExistingLogGroup() { final CloudWatchLogHelper cloudWatchLogHelper = new CloudWatchLogHelper(cloudWatchLogsProvider, LOG_GROUP_NAME, diff --git a/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogPublisherTest.java b/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogPublisherTest.java index 0ec91e57..acb2df6c 100644 --- a/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogPublisherTest.java +++ b/src/test/java/software/amazon/cloudformation/loggers/CloudWatchLogPublisherTest.java @@ -23,17 +23,14 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import com.amazonaws.services.lambda.runtime.LambdaLogger; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import software.amazon.awssdk.services.cloudwatch.CloudWatchClient; import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest; import software.amazon.cloudformation.injection.CloudWatchLogsProvider; -import software.amazon.cloudformation.injection.CloudWatchProvider; import software.amazon.cloudformation.proxy.MetricsPublisherProxy; @ExtendWith(MockitoExtension.class) @@ -51,21 +48,9 @@ public class CloudWatchLogPublisherTest { @Mock private MetricsPublisherProxy metricsPublisherProxy; - @Mock - private CloudWatchProvider platformCloudWatchProvider; - - @Mock - private CloudWatchClient platformCloudWatchClient; - private static final String LOG_GROUP_NAME = "log-group-name"; private static final String LOG_STREAM_NAME = "log-stream-name"; - @AfterEach - public void afterEach() { - verifyNoMoreInteractions(platformCloudWatchProvider); - verifyNoMoreInteractions(platformCloudWatchClient); - } - @Test public void testPublishLogEventsHappyCase() { final CloudWatchLogPublisher logPublisher = new CloudWatchLogPublisher(cloudWatchLogsProvider, LOG_GROUP_NAME, diff --git a/src/test/java/software/amazon/cloudformation/metrics/MetricsPublisherImplTest.java b/src/test/java/software/amazon/cloudformation/metrics/MetricsPublisherImplTest.java index 45521fa6..631b85d2 100644 --- a/src/test/java/software/amazon/cloudformation/metrics/MetricsPublisherImplTest.java +++ b/src/test/java/software/amazon/cloudformation/metrics/MetricsPublisherImplTest.java @@ -45,15 +45,9 @@ public class MetricsPublisherImplTest { @Mock private Logger loggerProxy; - @Mock - private CloudWatchProvider platformCloudWatchProvider; - @Mock private CloudWatchProvider providerCloudWatchProvider; - @Mock - private CloudWatchClient platformCloudWatchClient; - @Mock private CloudWatchClient providerCloudWatchClient; @@ -62,40 +56,28 @@ public class MetricsPublisherImplTest { @BeforeEach public void beforeEach() { - when(platformCloudWatchProvider.get()).thenReturn(platformCloudWatchClient); when(providerCloudWatchProvider.get()).thenReturn(providerCloudWatchClient); - when(platformCloudWatchClient.putMetricData(any(PutMetricDataRequest.class))) - .thenReturn(mock(PutMetricDataResponse.class)); when(providerCloudWatchClient.putMetricData(any(PutMetricDataRequest.class))) .thenReturn(mock(PutMetricDataResponse.class)); } @AfterEach public void afterEach() { - verifyNoMoreInteractions(platformCloudWatchProvider); - verifyNoMoreInteractions(platformCloudWatchClient); verifyNoMoreInteractions(providerCloudWatchProvider); verifyNoMoreInteractions(providerCloudWatchClient); } @Test public void testPublishDurationMetric() { - final MetricsPublisherImpl platformMetricsPublisher = new MetricsPublisherImpl(platformCloudWatchProvider, loggerProxy, - awsAccountId, resourceTypeName); - platformMetricsPublisher.refreshClient(); - final MetricsPublisherImpl providerMetricsPublisher = new MetricsPublisherImpl(providerCloudWatchProvider, loggerProxy, awsAccountId, resourceTypeName); providerMetricsPublisher.refreshClient(); final Instant instant = Instant.parse("2019-06-04T17:50:00Z"); - platformMetricsPublisher.publishDurationMetric(instant, Action.UPDATE, 123456); providerMetricsPublisher.publishDurationMetric(instant, Action.UPDATE, 123456); final ArgumentCaptor argument1 = ArgumentCaptor.forClass(PutMetricDataRequest.class); - final ArgumentCaptor argument2 = ArgumentCaptor.forClass(PutMetricDataRequest.class); - verify(platformCloudWatchClient).putMetricData(argument1.capture()); - verify(providerCloudWatchClient).putMetricData(argument2.capture()); + verify(providerCloudWatchClient).putMetricData(argument1.capture()); final PutMetricDataRequest request = argument1.getValue(); assertThat(request.namespace()) @@ -113,23 +95,16 @@ public void testPublishDurationMetric() { @Test public void testPublishExceptionMetric() { - final MetricsPublisherImpl platformMetricsPublisher = new MetricsPublisherImpl(platformCloudWatchProvider, loggerProxy, - awsAccountId, resourceTypeName); - platformMetricsPublisher.refreshClient(); - final MetricsPublisherImpl providerMetricsPublisher = new MetricsPublisherImpl(providerCloudWatchProvider, loggerProxy, awsAccountId, resourceTypeName); providerMetricsPublisher.refreshClient(); final Instant instant = Instant.parse("2019-06-03T17:50:00Z"); final RuntimeException e = new RuntimeException("some error"); - platformMetricsPublisher.publishExceptionMetric(instant, Action.CREATE, e, HandlerErrorCode.InternalFailure); - providerMetricsPublisher.publishDurationMetric(instant, Action.UPDATE, 123456); + providerMetricsPublisher.publishExceptionMetric(instant, Action.CREATE, e, HandlerErrorCode.InternalFailure); final ArgumentCaptor argument1 = ArgumentCaptor.forClass(PutMetricDataRequest.class); - final ArgumentCaptor argument2 = ArgumentCaptor.forClass(PutMetricDataRequest.class); - verify(platformCloudWatchClient).putMetricData(argument1.capture()); - verify(providerCloudWatchClient).putMetricData(argument2.capture()); + verify(providerCloudWatchClient).putMetricData(argument1.capture()); final PutMetricDataRequest request = argument1.getValue(); assertThat(request.namespace()) @@ -149,22 +124,15 @@ public void testPublishExceptionMetric() { @Test public void testPublishInvocationMetric() { - final MetricsPublisherImpl platformMetricsPublisher = new MetricsPublisherImpl(platformCloudWatchProvider, loggerProxy, - awsAccountId, resourceTypeName); - platformMetricsPublisher.refreshClient(); - final MetricsPublisherImpl providerMetricsPublisher = new MetricsPublisherImpl(providerCloudWatchProvider, loggerProxy, awsAccountId, resourceTypeName); providerMetricsPublisher.refreshClient(); final Instant instant = Instant.parse("2019-06-04T17:50:00Z"); - platformMetricsPublisher.publishInvocationMetric(instant, Action.UPDATE); - providerMetricsPublisher.publishDurationMetric(instant, Action.UPDATE, 123456); + providerMetricsPublisher.publishInvocationMetric(instant, Action.UPDATE); final ArgumentCaptor argument1 = ArgumentCaptor.forClass(PutMetricDataRequest.class); - final ArgumentCaptor argument2 = ArgumentCaptor.forClass(PutMetricDataRequest.class); - verify(platformCloudWatchClient).putMetricData(argument1.capture()); - verify(providerCloudWatchClient).putMetricData(argument2.capture()); + verify(providerCloudWatchClient).putMetricData(argument1.capture()); final PutMetricDataRequest request = argument1.getValue(); assertThat(request.namespace()) diff --git a/src/test/java/software/amazon/cloudformation/proxy/End2EndCallChainTest.java b/src/test/java/software/amazon/cloudformation/proxy/End2EndCallChainTest.java index 6785ada5..facd9f4a 100644 --- a/src/test/java/software/amazon/cloudformation/proxy/End2EndCallChainTest.java +++ b/src/test/java/software/amazon/cloudformation/proxy/End2EndCallChainTest.java @@ -32,7 +32,6 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import org.mockito.stubbing.Answer; import software.amazon.awssdk.auth.credentials.AwsSessionCredentials; import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; @@ -40,14 +39,7 @@ import software.amazon.awssdk.awscore.exception.AwsServiceException; import software.amazon.awssdk.http.SdkHttpClient; import software.amazon.awssdk.http.SdkHttpResponse; -import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient; -import software.amazon.awssdk.services.cloudwatchevents.model.PutRuleRequest; -import software.amazon.awssdk.services.cloudwatchevents.model.PutRuleResponse; -import software.amazon.awssdk.services.cloudwatchevents.model.PutTargetsRequest; -import software.amazon.awssdk.services.cloudwatchevents.model.PutTargetsResponse; import software.amazon.cloudformation.Action; -import software.amazon.cloudformation.Response; -import software.amazon.cloudformation.injection.CloudWatchEventsProvider; import software.amazon.cloudformation.injection.CredentialsProvider; import software.amazon.cloudformation.loggers.CloudWatchLogPublisher; import software.amazon.cloudformation.loggers.LogPublisher; @@ -65,21 +57,15 @@ import software.amazon.cloudformation.proxy.service.ThrottleException; import software.amazon.cloudformation.resource.Serializer; import software.amazon.cloudformation.resource.Validator; -import software.amazon.cloudformation.scheduler.CloudWatchScheduler; @TestMethodOrder(MethodOrderer.OrderAnnotation.class) public class End2EndCallChainTest { - - static final AwsServiceException.Builder builder = mock(AwsServiceException.Builder.class); - // // The same that is asserted inside the ServiceClient // private final AwsSessionCredentials MockCreds = AwsSessionCredentials.create("accessKeyId", "secretKey", "token"); private final Credentials credentials = new Credentials(MockCreds.accessKeyId(), MockCreds.secretAccessKey(), MockCreds.sessionToken()); - @SuppressWarnings("unchecked") - private final CallbackAdapter adapter = mock(CallbackAdapter.class); @Test public void happyCase() { @@ -150,10 +136,8 @@ private HandlerRequest prepareRequest(Model model) th request.setRegion("us-east-2"); request.setResourceType("AWS::Code::Repository"); request.setStackId(UUID.randomUUID().toString()); - request.setResponseEndpoint("https://cloudformation.amazonaws.com"); RequestData data = new RequestData<>(); data.setResourceProperties(model); - data.setPlatformCredentials(credentials); data.setCallerCredentials(credentials); request.setRequestData(data); return request; @@ -200,7 +184,6 @@ public void notFound() throws Exception { final InputStream stream = prepareStream(serializer, request); final ByteArrayOutputStream output = new ByteArrayOutputStream(2048); final LoggerProxy loggerProxy = mock(LoggerProxy.class); - final CredentialsProvider platformCredentialsProvider = prepareMockProvider(); final CredentialsProvider providerLoggingCredentialsProvider = prepareMockProvider(); final Context cxt = mock(Context.class); // bail out immediately @@ -225,29 +208,19 @@ public AwsErrorDetails awsErrorDetails() { when(client.describeRepository(eq(describeRequest))).thenThrow(notFound); final SdkHttpClient httpClient = mock(SdkHttpClient.class); - final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(adapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, + final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(providerLoggingCredentialsProvider, mock(CloudWatchLogPublisher.class), mock(LogPublisher.class), mock(MetricsPublisher.class), - mock(MetricsPublisher.class), - new CloudWatchScheduler(new CloudWatchEventsProvider(platformCredentialsProvider, - httpClient) { - @Override - public CloudWatchEventsClient get() { - return mock(CloudWatchEventsClient.class); - } - }, loggerProxy, serializer), new Validator(), serializer, - client, httpClient); + new Validator(), serializer, client, httpClient); wrapper.handleRequest(stream, output, cxt); verify(client).describeRepository(eq(describeRequest)); - Response response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), - new TypeReference>() { + ProgressEvent response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), + new TypeReference>() { }); assertThat(response).isNotNull(); - assertThat(response.getOperationStatus()).isEqualTo(OperationStatus.FAILED); - assertThat(response.getBearerToken()).isEqualTo("dwezxdfgfgh"); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); assertThat(response.getMessage()).contains("NotFound"); } @@ -260,7 +233,6 @@ public void createHandler() throws Exception { final InputStream stream = prepareStream(serializer, request); final ByteArrayOutputStream output = new ByteArrayOutputStream(2048); final LoggerProxy loggerProxy = mock(LoggerProxy.class); - final CredentialsProvider platformCredentialsProvider = prepareMockProvider(); final CredentialsProvider providerLoggingCredentialsProvider = prepareMockProvider(); final Context cxt = mock(Context.class); // bail out immediately @@ -284,30 +256,20 @@ public void createHandler() throws Exception { when(client.describeRepository(eq(describeRequest))).thenReturn(describeResponse); final SdkHttpClient httpClient = mock(SdkHttpClient.class); - final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(adapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, + final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(providerLoggingCredentialsProvider, mock(CloudWatchLogPublisher.class), mock(LogPublisher.class), mock(MetricsPublisher.class), - mock(MetricsPublisher.class), - new CloudWatchScheduler(new CloudWatchEventsProvider(platformCredentialsProvider, - httpClient) { - @Override - public CloudWatchEventsClient get() { - return mock(CloudWatchEventsClient.class); - } - }, loggerProxy, serializer), new Validator(), serializer, - client, httpClient); + new Validator(), serializer, client, httpClient); wrapper.handleRequest(stream, output, cxt); verify(client).createRepository(eq(createRequest)); verify(client).describeRepository(eq(describeRequest)); - Response response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), - new TypeReference>() { + ProgressEvent response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), + new TypeReference>() { }); assertThat(response).isNotNull(); - assertThat(response.getOperationStatus()).isEqualTo(OperationStatus.SUCCESS); - assertThat(response.getBearerToken()).isEqualTo("dwezxdfgfgh"); + assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS); assertThat(request.getRequestData()).isNotNull(); Model responseModel = response.getResourceModel(); assertThat(responseModel.getRepoName()).isEqualTo("repository"); @@ -322,8 +284,6 @@ public void createHandlerAlreadyExists() throws Exception { final Serializer serializer = new Serializer(); final InputStream stream = prepareStream(serializer, request); final ByteArrayOutputStream output = new ByteArrayOutputStream(2048); - final LoggerProxy loggerProxy = mock(LoggerProxy.class); - final CredentialsProvider platformCredentialsProvider = prepareMockProvider(); final CredentialsProvider providerLoggingCredentialsProvider = prepareMockProvider(); final Context cxt = mock(Context.class); // bail out immediately @@ -351,29 +311,19 @@ public AwsErrorDetails awsErrorDetails() { when(client.createRepository(eq(createRequest))).thenThrow(exists); final SdkHttpClient httpClient = mock(SdkHttpClient.class); - final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(adapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, + final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(providerLoggingCredentialsProvider, mock(CloudWatchLogPublisher.class), mock(LogPublisher.class), mock(MetricsPublisher.class), - mock(MetricsPublisher.class), - new CloudWatchScheduler(new CloudWatchEventsProvider(platformCredentialsProvider, - httpClient) { - @Override - public CloudWatchEventsClient get() { - return mock(CloudWatchEventsClient.class); - } - }, loggerProxy, serializer), new Validator(), serializer, - client, httpClient); + new Validator(), serializer, client, httpClient); wrapper.handleRequest(stream, output, cxt); verify(client).createRepository(eq(createRequest)); - Response response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), - new TypeReference>() { + ProgressEvent response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), + new TypeReference>() { }); assertThat(response).isNotNull(); - assertThat(response.getOperationStatus()).isEqualTo(OperationStatus.FAILED); - assertThat(response.getBearerToken()).isEqualTo("dwezxdfgfgh"); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); assertThat(request.getRequestData()).isNotNull(); Model responseModel = response.getResourceModel(); assertThat(responseModel.getRepoName()).isEqualTo("repository"); @@ -389,7 +339,6 @@ public void createHandlerThrottleException() throws Exception { final InputStream stream = prepareStream(serializer, request); ByteArrayOutputStream output = new ByteArrayOutputStream(2048); final LoggerProxy loggerProxy = mock(LoggerProxy.class); - final CredentialsProvider platformCredentialsProvider = prepareMockProvider(); final CredentialsProvider providerLoggingCredentialsProvider = prepareMockProvider(); final Context cxt = mock(Context.class); // bail out very slowly @@ -416,50 +365,30 @@ public AwsErrorDetails awsErrorDetails() { }; when(client.describeRepository(eq(describeRequest))).thenThrow(throttleException); - final ByteArrayOutputStream cweRequestOutput = new ByteArrayOutputStream(2048); - CloudWatchEventsClient cloudWatchEventsClient = mock(CloudWatchEventsClient.class); - when(cloudWatchEventsClient.putRule(any(PutRuleRequest.class))).thenReturn(mock(PutRuleResponse.class)); - when(cloudWatchEventsClient.putTargets(any(PutTargetsRequest.class))) - .thenAnswer((Answer) invocationOnMock -> { - PutTargetsRequest putTargetsRequest = invocationOnMock.getArgument(0, PutTargetsRequest.class); - cweRequestOutput.write(putTargetsRequest.targets().get(0).input().getBytes(StandardCharsets.UTF_8)); - return PutTargetsResponse.builder().build(); - }); - final SdkHttpClient httpClient = mock(SdkHttpClient.class); - final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(adapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, + final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(providerLoggingCredentialsProvider, mock(CloudWatchLogPublisher.class), mock(LogPublisher.class), mock(MetricsPublisher.class), - mock(MetricsPublisher.class), - new CloudWatchScheduler(new CloudWatchEventsProvider(platformCredentialsProvider, - httpClient) { - @Override - public CloudWatchEventsClient get() { - return cloudWatchEventsClient; - } - }, loggerProxy, serializer), new Validator(), serializer, - client, httpClient); - - // Bail early for the handshake. Expect cloudwatch events + new Validator(), serializer, client, httpClient); + + // Bail early for the handshake. Reinvoke handler again wrapper.handleRequest(stream, output, cxt); - // Replay Cloudwatch events stream - // refresh the stream + ProgressEvent event = serializer.deserialize(output.toString("UTF8"), + new TypeReference>() { + }); + request.setCallbackContext(event.getCallbackContext()); output = new ByteArrayOutputStream(2048); - wrapper.handleRequest(new ByteArrayInputStream(cweRequestOutput.toByteArray()), output, cxt); + wrapper.handleRequest(prepareStream(serializer, request), output, cxt); // Handshake mode 1 try, Throttle retries 4 times (1, 0s), (2, 3s), (3, 6s), (4, // 9s) verify(client, times(5)).describeRepository(eq(describeRequest)); - verify(cloudWatchEventsClient, times(1)).putRule(any(PutRuleRequest.class)); - verify(cloudWatchEventsClient, times(1)).putTargets(any(PutTargetsRequest.class)); - Response response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), - new TypeReference>() { + ProgressEvent response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), + new TypeReference>() { }); assertThat(response).isNotNull(); - assertThat(response.getOperationStatus()).isEqualTo(OperationStatus.FAILED); - assertThat(response.getBearerToken()).isEqualTo("dwezxdfgfgh"); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); assertThat(response.getMessage()).contains("Exceeded"); } @@ -472,7 +401,6 @@ public void createHandlerThottleExceptionEarlyInProgressBailout() throws Excepti final InputStream stream = prepareStream(serializer, request); final ByteArrayOutputStream output = new ByteArrayOutputStream(2048); final LoggerProxy loggerProxy = mock(LoggerProxy.class); - final CredentialsProvider platformCredentialsProvider = prepareMockProvider(); final CredentialsProvider providerLoggingCredentialsProvider = prepareMockProvider(); final Context cxt = mock(Context.class); // bail out immediately @@ -500,30 +428,20 @@ public AwsErrorDetails awsErrorDetails() { when(client.describeRepository(eq(describeRequest))).thenThrow(throttleException); final SdkHttpClient httpClient = mock(SdkHttpClient.class); - final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(adapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, + final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(providerLoggingCredentialsProvider, mock(CloudWatchLogPublisher.class), mock(LogPublisher.class), mock(MetricsPublisher.class), - mock(MetricsPublisher.class), - new CloudWatchScheduler(new CloudWatchEventsProvider(platformCredentialsProvider, - httpClient) { - @Override - public CloudWatchEventsClient get() { - return mock(CloudWatchEventsClient.class); - } - }, loggerProxy, serializer), new Validator(), serializer, - client, httpClient); + new Validator(), serializer, client, httpClient); wrapper.handleRequest(stream, output, cxt); // only 1 call (1, 0s), the next attempt is at 3s which exceed 50 ms remaining verify(client).describeRepository(eq(describeRequest)); - Response response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), - new TypeReference>() { + ProgressEvent response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), + new TypeReference>() { }); assertThat(response).isNotNull(); - assertThat(response.getOperationStatus()).isEqualTo(OperationStatus.IN_PROGRESS); - assertThat(response.getBearerToken()).isEqualTo("dwezxdfgfgh"); + assertThat(response.getStatus()).isEqualTo(OperationStatus.IN_PROGRESS); } @Order(40) @@ -535,7 +453,6 @@ public void accessDenied() throws Exception { final InputStream stream = prepareStream(serializer, request); final ByteArrayOutputStream output = new ByteArrayOutputStream(2048); final LoggerProxy loggerProxy = mock(LoggerProxy.class); - final CredentialsProvider platformCredentialsProvider = prepareMockProvider(); final CredentialsProvider providerLoggingCredentialsProvider = prepareMockProvider(); final Context cxt = mock(Context.class); // bail out immediately @@ -563,29 +480,19 @@ public AwsErrorDetails awsErrorDetails() { when(client.describeRepository(eq(describeRequest))).thenThrow(accessDenied); final SdkHttpClient httpClient = mock(SdkHttpClient.class); - final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(adapter, platformCredentialsProvider, - providerLoggingCredentialsProvider, + final ServiceHandlerWrapper wrapper = new ServiceHandlerWrapper(providerLoggingCredentialsProvider, mock(CloudWatchLogPublisher.class), mock(LogPublisher.class), mock(MetricsPublisher.class), - mock(MetricsPublisher.class), - new CloudWatchScheduler(new CloudWatchEventsProvider(platformCredentialsProvider, - httpClient) { - @Override - public CloudWatchEventsClient get() { - return mock(CloudWatchEventsClient.class); - } - }, loggerProxy, serializer), new Validator(), serializer, - client, httpClient); + new Validator(), serializer, client, httpClient); wrapper.handleRequest(stream, output, cxt); verify(client).describeRepository(eq(describeRequest)); - Response response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), - new TypeReference>() { + ProgressEvent response = serializer.deserialize(output.toString(StandardCharsets.UTF_8.name()), + new TypeReference>() { }); assertThat(response).isNotNull(); - assertThat(response.getOperationStatus()).isEqualTo(OperationStatus.FAILED); - assertThat(response.getBearerToken()).isEqualTo("dwezxdfgfgh"); + assertThat(response.getStatus()).isEqualTo(OperationStatus.FAILED); assertThat(response.getMessage()).contains("AccessDenied"); } diff --git a/src/test/java/software/amazon/cloudformation/proxy/handler/ServiceHandlerWrapper.java b/src/test/java/software/amazon/cloudformation/proxy/handler/ServiceHandlerWrapper.java index 315cd4cd..e130b7cc 100644 --- a/src/test/java/software/amazon/cloudformation/proxy/handler/ServiceHandlerWrapper.java +++ b/src/test/java/software/amazon/cloudformation/proxy/handler/ServiceHandlerWrapper.java @@ -27,7 +27,6 @@ import software.amazon.cloudformation.loggers.LogPublisher; import software.amazon.cloudformation.metrics.MetricsPublisher; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; -import software.amazon.cloudformation.proxy.CallbackAdapter; import software.amazon.cloudformation.proxy.HandlerErrorCode; import software.amazon.cloudformation.proxy.HandlerRequest; import software.amazon.cloudformation.proxy.LoggerProxy; @@ -37,27 +36,21 @@ import software.amazon.cloudformation.proxy.service.ServiceClient; import software.amazon.cloudformation.resource.SchemaValidator; import software.amazon.cloudformation.resource.Serializer; -import software.amazon.cloudformation.scheduler.CloudWatchScheduler; public class ServiceHandlerWrapper extends LambdaWrapper { private final ServiceClient serviceClient; - public ServiceHandlerWrapper(final CallbackAdapter callbackAdapter, - final CredentialsProvider platformCredentialsProvider, - final CredentialsProvider providerLoggingCredentialsProvider, + public ServiceHandlerWrapper(final CredentialsProvider providerLoggingCredentialsProvider, final CloudWatchLogPublisher providerEventsLogger, final LogPublisher platformEventsLogger, - final MetricsPublisher platformMetricsPublisher, final MetricsPublisher providerMetricsPublisher, - final CloudWatchScheduler scheduler, final SchemaValidator validator, final Serializer serializer, final ServiceClient client, final SdkHttpClient httpClient) { - super(callbackAdapter, platformCredentialsProvider, providerLoggingCredentialsProvider, providerEventsLogger, - platformEventsLogger, platformMetricsPublisher, providerMetricsPublisher, scheduler, validator, serializer, - httpClient); + super(providerLoggingCredentialsProvider, platformEventsLogger, providerEventsLogger, providerMetricsPublisher, validator, + serializer, httpClient); this.serviceClient = client; } @@ -86,10 +79,10 @@ protected ResourceHandlerRequest transform(final HandlerRequestbuilder().clientRequestToken(request.getBearerToken()) - .desiredResourceState(desiredResourceState).previousResourceState(previousResourceState) - .desiredResourceTags(getDesiredResourceTags(request)).systemTags(systemTags) - .logicalResourceIdentifier(request.getRequestData().getLogicalResourceId()).nextToken(request.getNextToken()).build(); + return ResourceHandlerRequest.builder().desiredResourceState(desiredResourceState) + .previousResourceState(previousResourceState).desiredResourceTags(getDesiredResourceTags(request)) + .systemTags(systemTags).logicalResourceIdentifier(request.getRequestData().getLogicalResourceId()) + .nextToken(request.getNextToken()).build(); } @Override diff --git a/src/test/java/software/amazon/cloudformation/proxy/service/CreateRequest.java b/src/test/java/software/amazon/cloudformation/proxy/service/CreateRequest.java index dd47c57b..ac77b5e3 100644 --- a/src/test/java/software/amazon/cloudformation/proxy/service/CreateRequest.java +++ b/src/test/java/software/amazon/cloudformation/proxy/service/CreateRequest.java @@ -19,6 +19,7 @@ import software.amazon.awssdk.awscore.AwsRequest; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkPojo; @lombok.Getter @lombok.EqualsAndHashCode(callSuper = false) @@ -48,7 +49,7 @@ public List> sdkFields() { @lombok.Getter @lombok.EqualsAndHashCode(callSuper = true) @lombok.ToString(callSuper = true) - public static class Builder extends BuilderImpl { + public static class Builder extends BuilderImpl implements SdkPojo { private String repoName; private String userName; @@ -72,5 +73,14 @@ public Builder overrideConfiguration(AwsRequestOverrideConfiguration awsRequestO super.overrideConfiguration(awsRequestOverrideConfig); return this; } + + @Override + public List> sdkFields() { + return Collections.emptyList(); + } + } + + public static Builder builder() { + return new Builder(); } } diff --git a/src/test/java/software/amazon/cloudformation/proxy/service/CreateResponse.java b/src/test/java/software/amazon/cloudformation/proxy/service/CreateResponse.java index 9be3a2da..76b54a76 100644 --- a/src/test/java/software/amazon/cloudformation/proxy/service/CreateResponse.java +++ b/src/test/java/software/amazon/cloudformation/proxy/service/CreateResponse.java @@ -18,6 +18,7 @@ import java.util.List; import software.amazon.awssdk.awscore.AwsResponse; import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkPojo; @lombok.Getter @lombok.EqualsAndHashCode(callSuper = true) @@ -42,7 +43,7 @@ public List> sdkFields() { return Collections.emptyList(); } - public static class Builder extends BuilderImpl { + public static class Builder extends BuilderImpl implements SdkPojo { private String repoName; private String error; @@ -60,5 +61,13 @@ public Builder error(String name) { this.error = name; return this; } + + public List> sdkFields() { + return Collections.emptyList(); + } + } + + public static Builder builder() { + return new Builder(); } } diff --git a/src/test/java/software/amazon/cloudformation/proxy/service/DescribeRequest.java b/src/test/java/software/amazon/cloudformation/proxy/service/DescribeRequest.java index fe6ee132..9dff68d6 100644 --- a/src/test/java/software/amazon/cloudformation/proxy/service/DescribeRequest.java +++ b/src/test/java/software/amazon/cloudformation/proxy/service/DescribeRequest.java @@ -19,6 +19,7 @@ import software.amazon.awssdk.awscore.AwsRequest; import software.amazon.awssdk.awscore.AwsRequestOverrideConfiguration; import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkPojo; @lombok.Getter @lombok.EqualsAndHashCode(callSuper = false) @@ -45,7 +46,7 @@ public Builder toBuilder() { @lombok.Getter @lombok.EqualsAndHashCode(callSuper = true) @lombok.ToString(callSuper = true) - public static class Builder extends BuilderImpl { + public static class Builder extends BuilderImpl implements SdkPojo { private String repoName; @Override @@ -63,5 +64,14 @@ public Builder overrideConfiguration(AwsRequestOverrideConfiguration awsRequestO super.overrideConfiguration(awsRequestOverrideConfig); return this; } + + @Override + public List> sdkFields() { + return Collections.emptyList(); + } + } + + public static Builder builder() { + return new Builder(); } } diff --git a/src/test/java/software/amazon/cloudformation/proxy/service/DescribeResponse.java b/src/test/java/software/amazon/cloudformation/proxy/service/DescribeResponse.java index 828e81ec..e842dc2f 100644 --- a/src/test/java/software/amazon/cloudformation/proxy/service/DescribeResponse.java +++ b/src/test/java/software/amazon/cloudformation/proxy/service/DescribeResponse.java @@ -19,6 +19,7 @@ import java.util.List; import software.amazon.awssdk.awscore.AwsResponse; import software.amazon.awssdk.core.SdkField; +import software.amazon.awssdk.core.SdkPojo; @lombok.Getter @lombok.EqualsAndHashCode(callSuper = true) @@ -45,7 +46,7 @@ public List> sdkFields() { return Collections.emptyList(); } - public static class Builder extends BuilderImpl { + public static class Builder extends BuilderImpl implements SdkPojo { private String repoName; private String repoArn; private Date createdWhen; @@ -69,5 +70,14 @@ public Builder createdWhen(Date when) { createdWhen = when; return this; } + + @Override + public List> sdkFields() { + return Collections.emptyList(); + } + } + + public static Builder builder() { + return new Builder(); } } diff --git a/src/test/java/software/amazon/cloudformation/resource/SerializerTest.java b/src/test/java/software/amazon/cloudformation/resource/SerializerTest.java index b0467701..d4f7f6a7 100644 --- a/src/test/java/software/amazon/cloudformation/resource/SerializerTest.java +++ b/src/test/java/software/amazon/cloudformation/resource/SerializerTest.java @@ -29,7 +29,6 @@ import software.amazon.cloudformation.TestContext; import software.amazon.cloudformation.TestModel; import software.amazon.cloudformation.proxy.HandlerRequest; -import software.amazon.cloudformation.proxy.RequestContext; import software.amazon.cloudformation.proxy.RequestData; public class SerializerTest { @@ -71,32 +70,19 @@ public void testDeserialize_AccuratePayload() throws IOException { assertThat(r.getAwsAccountId()).isEqualTo("123456789012"); assertThat(r.getBearerToken()).isEqualTo("123456"); assertThat(r.getRegion()).isEqualTo("us-east-1"); - assertThat(r.getRequestContext()).isNotNull(); + assertThat(r.getBearerToken()).isEqualTo("123456"); assertThat(r.getRequestData()).isNotNull(); - assertThat(r.getResponseEndpoint()).isEqualTo("https://cloudformation.us-west-2.amazonaws.com"); assertThat(r.getResourceType()).isEqualTo("AWS::Test::TestModel"); assertThat(r.getResourceTypeVersion()).isEqualTo("1.0"); assertThat(r.getStackId()) .isEqualTo("arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968"); - - final RequestContext requestContext = r.getRequestContext(); - assertThat(requestContext.getCloudWatchEventsRuleName()).isNull(); - assertThat(requestContext.getCloudWatchEventsTargetId()).isNull(); - assertThat(requestContext.getInvocation()).isEqualTo(0); - assertThat(requestContext.getCallbackContext()).isNull(); + assertThat(r.getCallbackContext()).isNull(); final RequestData requestData = r.getRequestData(); assertThat(requestData.getCallerCredentials()).isNotNull(); assertThat(requestData.getCallerCredentials().getAccessKeyId()).isEqualTo("IASAYK835GAIFHAHEI23"); assertThat(requestData.getCallerCredentials().getSecretAccessKey()).isEqualTo("66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0"); - assertThat(requestData.getCallerCredentials().getSessionToken()) - .isEqualTo("lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg"); - assertThat(requestData.getPlatformCredentials()).isNotNull(); - assertThat(requestData.getPlatformCredentials().getAccessKeyId()).isEqualTo("32IEHAHFIAG538KYASAI"); - assertThat(requestData.getPlatformCredentials().getSecretAccessKey()) - .isEqualTo("0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66"); - assertThat(requestData.getPlatformCredentials().getSessionToken()) - .isEqualTo("gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal"); + assertThat(requestData.getCallerCredentials().getSessionToken()); assertThat(requestData.getLogicalResourceId()).isEqualTo("myBucket"); assertThat(requestData.getStackTags()).containsExactly(entry("tag1", "abc")); assertThat(requestData.getPreviousResourceProperties()).isNull(); @@ -123,17 +109,11 @@ public void testDeserialize_ExtranousRequestFields_AreIncluded() throws IOExcept assertThat(r.getRegion()).isEqualTo("us-east-1"); assertThat(r.getRequestContext()).isNotNull(); assertThat(r.getRequestData()).isNotNull(); - assertThat(r.getResponseEndpoint()).isEqualTo("https://cloudformation.us-west-2.amazonaws.com"); assertThat(r.getResourceType()).isEqualTo("AWS::Test::TestModel"); assertThat(r.getResourceTypeVersion()).isEqualTo("1.0"); assertThat(r.getStackId()) .isEqualTo("arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968"); - - final RequestContext requestContext = r.getRequestContext(); - assertThat(requestContext.getCloudWatchEventsRuleName()).isNull(); - assertThat(requestContext.getCloudWatchEventsTargetId()).isNull(); - assertThat(requestContext.getInvocation()).isEqualTo(0); - assertThat(requestContext.getCallbackContext()).isNull(); + assertThat(r.getCallbackContext()).isNull(); final RequestData requestData = r.getRequestData(); assertThat(requestData.getCallerCredentials()).isNotNull(); @@ -141,12 +121,6 @@ public void testDeserialize_ExtranousRequestFields_AreIncluded() throws IOExcept assertThat(requestData.getCallerCredentials().getSecretAccessKey()).isEqualTo("66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0"); assertThat(requestData.getCallerCredentials().getSessionToken()) .isEqualTo("lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg"); - assertThat(requestData.getPlatformCredentials()).isNotNull(); - assertThat(requestData.getPlatformCredentials().getAccessKeyId()).isEqualTo("32IEHAHFIAG538KYASAI"); - assertThat(requestData.getPlatformCredentials().getSecretAccessKey()) - .isEqualTo("0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66"); - assertThat(requestData.getPlatformCredentials().getSessionToken()) - .isEqualTo("gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal"); assertThat(requestData.getLogicalResourceId()).isEqualTo("myBucket"); assertThat(requestData.getStackTags()).containsExactly(entry("tag1", "abc")); @@ -173,31 +147,19 @@ public void testDeserialize_ExtranousModelFields_AreAllowed() throws IOException assertThat(r.getBearerToken()).isEqualTo("123456"); assertThat(r.getRegion()).isEqualTo("us-east-1"); assertThat(r.getRequestContext()).isNotNull(); + assertThat(r.getCallbackContext()).isNull(); assertThat(r.getRequestData()).isNotNull(); - assertThat(r.getResponseEndpoint()).isEqualTo("https://cloudformation.us-west-2.amazonaws.com"); assertThat(r.getResourceType()).isEqualTo("AWS::Test::TestModel"); assertThat(r.getResourceTypeVersion()).isEqualTo("1.0"); assertThat(r.getStackId()) .isEqualTo("arn:aws:cloudformation:us-east-1:123456789012:stack/SampleStack/e722ae60-fe62-11e8-9a0e-0ae8cc519968"); - final RequestContext requestContext = r.getRequestContext(); - assertThat(requestContext.getCloudWatchEventsRuleName()).isNull(); - assertThat(requestContext.getCloudWatchEventsTargetId()).isNull(); - assertThat(requestContext.getInvocation()).isEqualTo(0); - assertThat(requestContext.getCallbackContext()).isNull(); - final RequestData requestData = r.getRequestData(); assertThat(requestData.getCallerCredentials()).isNotNull(); assertThat(requestData.getCallerCredentials().getAccessKeyId()).isEqualTo("IASAYK835GAIFHAHEI23"); assertThat(requestData.getCallerCredentials().getSecretAccessKey()).isEqualTo("66iOGPN5LnpZorcLr8Kh25u8AbjHVllv5/poh2O0"); assertThat(requestData.getCallerCredentials().getSessionToken()) .isEqualTo("lameHS2vQOknSHWhdFYTxm2eJc1JMn9YBNI4nV4mXue945KPL6DHfW8EsUQT5zwssYEC1NvYP9yD6Y5s5lKR3chflOHPFsIe6eqg"); - assertThat(requestData.getPlatformCredentials()).isNotNull(); - assertThat(requestData.getPlatformCredentials().getAccessKeyId()).isEqualTo("32IEHAHFIAG538KYASAI"); - assertThat(requestData.getPlatformCredentials().getSecretAccessKey()) - .isEqualTo("0O2hop/5vllVHjbA8u52hK8rLcroZpnL5NPGOi66"); - assertThat(requestData.getPlatformCredentials().getSessionToken()) - .isEqualTo("gqe6eIsFPHOlfhc3RKl5s5Y6Dy9PYvN1CEYsswz5TQUsE8WfHD6LPK549euXm4Vn4INBY9nMJ1cJe2mxTYFdhWHSnkOQv2SHemal"); assertThat(requestData.getLogicalResourceId()).isEqualTo("myBucket"); assertThat(requestData.getStackTags()).containsExactly(entry("tag1", "abc")); diff --git a/src/test/resources/software/amazon/cloudformation/proxy/handler/model.json b/src/test/resources/software/amazon/cloudformation/proxy/handler/model.json index d485a189..1fe72db6 100644 --- a/src/test/resources/software/amazon/cloudformation/proxy/handler/model.json +++ b/src/test/resources/software/amazon/cloudformation/proxy/handler/model.json @@ -30,5 +30,8 @@ ], "createOnlyProperties": [ "/properties/RepoName" + ], + "primaryIdentifier": [ + "/properties/RepoName" ] } diff --git a/src/test/resources/software/amazon/cloudformation/wrapper-override.json b/src/test/resources/software/amazon/cloudformation/wrapper-override.json new file mode 100644 index 00000000..d2ebeaa3 --- /dev/null +++ b/src/test/resources/software/amazon/cloudformation/wrapper-override.json @@ -0,0 +1,16 @@ +{ + "typeName": "Test::Resource::Type", + "description": "Description", + "properties": { + "property1": { + "type": "string" + }, + "property2": { + "type": "integer" + } + }, + "additionalProperties": false, + "primaryIdentifier": [ + "/properties/property1" + ] +} diff --git a/tests/test_codegen.py b/tests/test_codegen.py index deaa0db9..57ead838 100644 --- a/tests/test_codegen.py +++ b/tests/test_codegen.py @@ -8,7 +8,13 @@ import pytest from rpdk.core.exceptions import InternalError, SysExitRecommendedError from rpdk.core.project import Project -from rpdk.java.codegen import JavaArchiveNotFoundError, JavaLanguagePlugin +from rpdk.java.codegen import ( + InvalidMavenPOMError, + JavaArchiveNotFoundError, + JavaLanguagePlugin, + JavaPluginNotFoundError, + JavaPluginVersionNotSupportedError, +) RESOURCE = "DZQWCC" @@ -84,6 +90,36 @@ def test_generate(project): assert not test_file.is_file() +def test_protocol_version_is_set(project): + assert project.settings["protocolVersion"] == "2.0.0" + + +def test_generate_low_protocol_version_is_updated(project): + project.settings["protocolVersion"] = "1.0.0" + project.generate() + assert project.settings["protocolVersion"] == "2.0.0" + + +def update_pom_with_plugin_version(project, version_id): + pom_tree = ET.parse(project.root / "pom.xml") + root = pom_tree.getroot() + namespace = {"mvn": "http://maven.apache.org/POM/4.0.0"} + version = root.find( + "./mvn:dependencies/mvn:dependency" + "/[mvn:artifactId='aws-cloudformation-rpdk-java-plugin']/mvn:version", + namespace, + ) + version.text = version_id + pom_tree.write(project.root / "pom.xml") + + +def test_generate_with_not_support_version(project): + update_pom_with_plugin_version(project, "1.0.0") + + with pytest.raises(JavaPluginVersionNotSupportedError): + project.generate() + + def make_target(project, count): target = project.root / "target" target.mkdir(exist_ok=True) @@ -114,6 +150,33 @@ def test__find_jar_two(project): project._plugin._find_jar(project) +def make_pom_xml_without_plugin(project): + pom_tree = ET.parse(project.root / "pom.xml") + root = pom_tree.getroot() + namespace = {"mvn": "http://maven.apache.org/POM/4.0.0"} + plugin = root.find( + ".//mvn:dependency/[mvn:artifactId='aws-cloudformation-rpdk-java-plugin']", + namespace, + ) + dependencies = root.find("mvn:dependencies", namespace) + dependencies.remove(plugin) + pom_tree.write(project.root / "pom.xml") + + +def test__get_plugin_version_not_found(project): + make_pom_xml_without_plugin(project) + with pytest.raises(JavaPluginNotFoundError): + project._plugin._get_java_plugin_dependency_version(project) + + +def test__get_plugin_version_invalid_pom(project): + pom = open(project.root / "pom.xml", "w") + pom.write("invalid pom") + pom.close() + with pytest.raises(InvalidMavenPOMError): + project._plugin._get_java_plugin_dependency_version(project) + + def test_package(project): project.load_schema() project.generate()