From 45ad76f5156951bbe631f3b89fcfd3e3a9d3bc5e Mon Sep 17 00:00:00 2001 From: Andrew Goldberg Date: Thu, 9 Jan 2020 14:04:01 -0800 Subject: [PATCH] Add custom stabilization timeout in handlers schema (#61) * split CUD and RL operation handler definitions * make unit tests less brittle and update README * update README/schema docs * allow timeoutInMinutes for read and list for consistency --- README.md | 13 ++++++ .../schema/provider.definition.schema.v1.json | 7 ++++ .../resource/ValidatorTest.java | 40 +++++++++++++++++++ 3 files changed, 60 insertions(+) diff --git a/README.md b/README.md index 61e53e4..6fdd21c 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ We have taken an opinion on certain aspects of the core JSON Schema and introduc ### New Schema-Level Properties #### insertionOrder + Array types can define a boolean `insertionOrder`, which specifies whether the order in which elements are specified should be honored when processing a diff between two sets of properties. If `insertionOrder` is true, then a change in order of the elements will constitute a diff. The default for `insertionOrder` is true. Together with the `uniqueItems` property (which is native to JSON Schema), complex array types can be defined, as in the following table: @@ -143,6 +144,18 @@ Together with the `uniqueItems` property (which is native to JSON Schema), compl * **`properties` and `patternProperties`** it is not valid to use both properties and patternProperties together in the same shape, as a shape should not contain both defined and undefined values. In order to implement this, the set of undefined values should itself be a subshape. * **`items` and `additionalItems`** the `items` in an array may only have one schema and may not use a list of schemas, as an ordered tuple of different objects is confusing for both developers and customers. This should be expressed as key:value object pairs. Similarly, `additionalItems` is not allowed. +## handlers + +The `handlers` section of the schema allows you to specify which CRUDL operations (create, read, update, delete, list) are available for your resource, as well as some additional metadata about each handler. + +### permissions + +For each handler, you should define a list of API `permissions` required to perform the operation. Currently, this is used to generate IAM policy templates and is assumed to be AWS API permissions, but you may list 3rd party APIs here as well. + +### timeoutInMinutes + +For each handler, you may define a `timeoutInMinutes` property, which defines the *maximum* timeout of the operation. This timeout is used by the invoker of the handler (such as CloudFormation) to stop listening and cancel the operation. Note that the handler may of course decide to timeout and return a failure prior to this max timeout period. Currently, this value is only used for `Create`, `Update`, and `Delete` handlers, while `Read` and `List` handlers are expected to return synchronously within 30 seconds. + ## License This library is licensed under the Apache 2.0 License. diff --git a/src/main/resources/schema/provider.definition.schema.v1.json b/src/main/resources/schema/provider.definition.schema.v1.json index 12174ba..13b5aef 100644 --- a/src/main/resources/schema/provider.definition.schema.v1.json +++ b/src/main/resources/schema/provider.definition.schema.v1.json @@ -19,6 +19,13 @@ "type": "string" }, "additionalItems": false + }, + "timeoutInMinutes": { + "description": "Defines the timeout for the entire operation to be interpreted by the invoker of the handler. The default is 120 (2 hours).", + "type": "integer", + "minimum": 2, + "maximum": 720, + "default": 120 } }, "additionalProperties": false, diff --git a/src/test/java/software/amazon/cloudformation/resource/ValidatorTest.java b/src/test/java/software/amazon/cloudformation/resource/ValidatorTest.java index 0461922..3d601d8 100644 --- a/src/test/java/software/amazon/cloudformation/resource/ValidatorTest.java +++ b/src/test/java/software/amazon/cloudformation/resource/ValidatorTest.java @@ -325,6 +325,46 @@ public void validateDefinition_invalidHandlerSection_shouldThrow() { .withNoCause().withMessage("#/handlers/read: required key [permissions] not found"); } + @ParameterizedTest + @ValueSource(ints = { 1, 721 }) + public void validateDefinition_invalidTimeout_shouldThrow(final int timeout) { + // modifying the valid-with-handlers.json to add invalid timeout + final JSONObject definition = new JSONObject(new JSONTokener(this.getClass() + .getResourceAsStream("/valid-with-handlers.json"))); + + final JSONObject createDefinition = definition.getJSONObject("handlers").getJSONObject("create"); + createDefinition.put("timeoutInMinutes", timeout); + + final String keyword = timeout == 1 ? "minimum" : "maximum"; + + assertThatExceptionOfType(ValidationException.class).isThrownBy(() -> validator.validateResourceDefinition(definition)) + .withNoCause().withMessageContaining("#/handlers/create/timeoutInMinutes").withMessageContaining(keyword); + } + + @ParameterizedTest + @ValueSource(ints = { 2, 120, 720 }) + public void validateDefinition_withTimeout_shouldNotThrow(final int timeout) { + final JSONObject definition = new JSONObject(new JSONTokener(this.getClass() + .getResourceAsStream("/valid-with-handlers.json"))); + + final JSONObject createDefinition = definition.getJSONObject("handlers").getJSONObject("create"); + createDefinition.put("timeoutInMinutes", timeout); + + validator.validateResourceDefinition(definition); + } + + @ParameterizedTest + @ValueSource(strings = { "create", "update", "delete", "read", "list" }) + public void validateDefinition_timeoutAllowed_shouldNotThrow(final String handlerType) { + final JSONObject definition = new JSONObject(new JSONTokener(this.getClass() + .getResourceAsStream("/valid-with-handlers.json"))); + + final JSONObject handlerDefinition = definition.getJSONObject("handlers").getJSONObject(handlerType); + handlerDefinition.put("timeoutInMinutes", 30); + + validator.validateResourceDefinition(definition); + } + @Test public void validateDefinition_validHandlerSection_shouldNotThrow() { final JSONObject definition = new JSONObject(new JSONTokener(this.getClass()