diff --git a/README.md b/README.md
index 1b4e448..25b198b 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,6 @@
-## My Project
+## AWS CloudFormation Resource Providers Lookout for Vision
-TODO: Fill this README out!
-
-Be sure to:
-
-* Change the title in this README
-* Edit your repository description on GitHub
-
-## Security
-
-See [CONTRIBUTING](CONTRIBUTING.md#security-issue-notifications) for more information.
+The CloudFormation Resource Provider Package for Amazon Lookout for Vision.
## License
diff --git a/aws-lookoutvision-project/.gitignore b/aws-lookoutvision-project/.gitignore
new file mode 100644
index 0000000..faa9259
--- /dev/null
+++ b/aws-lookoutvision-project/.gitignore
@@ -0,0 +1,23 @@
+# macOS
+.DS_Store
+._*
+
+# Maven outputs
+.classpath
+
+# IntelliJ
+*.iml
+.idea
+out.java
+out/
+.settings
+.project
+
+# auto-generated files
+target/
+
+# our logs
+rpdk.log*
+
+# contains credentials
+sam-tests/
diff --git a/aws-lookoutvision-project/.rpdk-config b/aws-lookoutvision-project/.rpdk-config
new file mode 100644
index 0000000..ad8841a
--- /dev/null
+++ b/aws-lookoutvision-project/.rpdk-config
@@ -0,0 +1,25 @@
+{
+ "artifact_type": "RESOURCE",
+ "typeName": "AWS::LookoutVision::Project",
+ "language": "java",
+ "runtime": "java8",
+ "entrypoint": "software.amazon.lookoutvision.project.HandlerWrapper::handleRequest",
+ "testEntrypoint": "software.amazon.lookoutvision.project.HandlerWrapper::testEntrypoint",
+ "settings": {
+ "version": false,
+ "subparser_name": null,
+ "verbose": 0,
+ "force": false,
+ "type_name": null,
+ "artifact_type": null,
+ "namespace": [
+ "software",
+ "amazon",
+ "lookoutvision",
+ "project"
+ ],
+ "codegen_template_path": "guided_aws",
+ "protocolVersion": "2.0.0"
+ },
+ "executableEntrypoint": "software.amazon.lookoutvision.project.HandlerWrapperExecutable"
+}
diff --git a/aws-lookoutvision-project/README.md b/aws-lookoutvision-project/README.md
new file mode 100644
index 0000000..e1285c3
--- /dev/null
+++ b/aws-lookoutvision-project/README.md
@@ -0,0 +1,14 @@
+# AWS::LookoutVision::Project
+
+1. Write the JSON schema describing your resource, `aws-lookoutvision-project.json`
+1. Implement your resource handlers.
+
+This package has two main components:
+1. The JSON schema describing an Amazon Lookout for Vision Project, `aws-lookoutvision-project.json`
+1. The resource handlers that actually create, delete, update, read, and list Amazon Lookout for Vision Projects.
+
+The RPDK will automatically generate the correct resource model from the schema whenever the project is built via Maven. You can also do this manually with the following command: `cfn generate`.
+
+> Please don't modify files under `target/generated-sources/rpdk`, as they will be automatically overwritten.
+
+The code uses [Lombok](https://projectlombok.org/), and [you may have to install IDE integrations](https://projectlombok.org/setup/overview) to enable auto-complete for Lombok-annotated classes.
diff --git a/aws-lookoutvision-project/aws-lookoutvision-project.json b/aws-lookoutvision-project/aws-lookoutvision-project.json
new file mode 100644
index 0000000..03ad9ef
--- /dev/null
+++ b/aws-lookoutvision-project/aws-lookoutvision-project.json
@@ -0,0 +1,105 @@
+{
+ "typeName": "AWS::LookoutVision::Project",
+ "description": "An example resource schema demonstrating some basic constructs and validation rules.",
+ "sourceUrl": "https://github.com/aws-cloudformation/aws-cloudformation-rpdk.git",
+ "definitions": {
+ "InitechDateFormat": {
+ "$comment": "Use the `definitions` block to provide shared resource property schemas",
+ "type": "string",
+ "format": "date-time"
+ },
+ "Memo": {
+ "type": "object",
+ "properties": {
+ "Heading": {
+ "type": "string"
+ },
+ "Body": {
+ "type": "string"
+ }
+ },
+ "additionalProperties": false
+ }
+ },
+ "properties": {
+ "TPSCode": {
+ "description": "A TPS Code is automatically generated on creation and assigned as the unique identifier.",
+ "type": "string",
+ "pattern": "^[A-Z]{3,5}[0-9]{8}-[0-9]{4}$"
+ },
+ "Title": {
+ "description": "The title of the TPS report is a mandatory element.",
+ "type": "string",
+ "minLength": 20,
+ "maxLength": 250
+ },
+ "CoverSheetIncluded": {
+ "description": "Required for all TPS Reports submitted after 2/19/1999",
+ "type": "boolean"
+ },
+ "DueDate": {
+ "$ref": "#/definitions/InitechDateFormat"
+ },
+ "ApprovalDate": {
+ "$ref": "#/definitions/InitechDateFormat"
+ },
+ "Memo": {
+ "$ref": "#/definitions/Memo"
+ },
+ "SecondCopyOfMemo": {
+ "description": "In case you didn't get the first one.",
+ "$ref": "#/definitions/Memo"
+ },
+ "TestCode": {
+ "type": "string",
+ "enum": [
+ "NOT_STARTED",
+ "CANCELLED"
+ ]
+ },
+ "Authors": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "TestCode",
+ "Title"
+ ],
+ "readOnlyProperties": [
+ "/properties/TPSCode"
+ ],
+ "primaryIdentifier": [
+ "/properties/TPSCode"
+ ],
+ "handlers": {
+ "create": {
+ "permissions": [
+ "initech:CreateReport"
+ ]
+ },
+ "read": {
+ "permissions": [
+ "initech:DescribeReport"
+ ]
+ },
+ "update": {
+ "permissions": [
+ "initech:UpdateReport"
+ ]
+ },
+ "delete": {
+ "permissions": [
+ "initech:DeleteReport"
+ ]
+ },
+ "list": {
+ "permissions": [
+ "initech:ListReports"
+ ]
+ }
+ }
+}
diff --git a/aws-lookoutvision-project/docs/README.md b/aws-lookoutvision-project/docs/README.md
new file mode 100644
index 0000000..d579f84
--- /dev/null
+++ b/aws-lookoutvision-project/docs/README.md
@@ -0,0 +1,133 @@
+# AWS::LookoutVision::Project
+
+An example resource schema demonstrating some basic constructs and validation rules.
+
+## Syntax
+
+To declare this entity in your AWS CloudFormation template, use the following syntax:
+
+### JSON
+
+
+{
+ "Type" : "AWS::LookoutVision::Project",
+ "Properties" : {
+ "Title " : String ,
+ "CoverSheetIncluded " : Boolean ,
+ "DueDate " : String ,
+ "ApprovalDate " : String ,
+ "Memo " : Memo ,
+ "SecondCopyOfMemo " : Memo ,
+ "TestCode " : String ,
+ "Authors " : [ String, ... ]
+ }
+}
+
+
+### YAML
+
+
+Type: AWS::LookoutVision::Project
+Properties:
+ Title : String
+ CoverSheetIncluded : Boolean
+ DueDate : String
+ ApprovalDate : String
+ Memo : Memo
+ SecondCopyOfMemo : Memo
+ TestCode : String
+ Authors :
+ - String
+
+
+## Properties
+
+#### Title
+
+The title of the TPS report is a mandatory element.
+
+_Required_: Yes
+
+_Type_: String
+
+_Minimum_: 20
+
+_Maximum_: 250
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### CoverSheetIncluded
+
+Required for all TPS Reports submitted after 2/19/1999
+
+_Required_: No
+
+_Type_: Boolean
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### DueDate
+
+_Required_: No
+
+_Type_: String
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### ApprovalDate
+
+_Required_: No
+
+_Type_: String
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### Memo
+
+_Required_: No
+
+_Type_: Memo
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### SecondCopyOfMemo
+
+_Required_: No
+
+_Type_: Memo
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### TestCode
+
+_Required_: Yes
+
+_Type_: String
+
+_Allowed Values_: NOT_STARTED
| CANCELLED
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### Authors
+
+_Required_: No
+
+_Type_: List of String
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+## Return Values
+
+### Ref
+
+When you pass the logical ID of this resource to the intrinsic `Ref` function, Ref returns the TPSCode.
+
+### Fn::GetAtt
+
+The `Fn::GetAtt` intrinsic function returns a value for a specified attribute of this type. The following are the available attributes and sample return values.
+
+For more information about using the `Fn::GetAtt` intrinsic function, see [Fn::GetAtt](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-getatt.html).
+
+#### TPSCode
+
+A TPS Code is automatically generated on creation and assigned as the unique identifier.
diff --git a/aws-lookoutvision-project/docs/memo.md b/aws-lookoutvision-project/docs/memo.md
new file mode 100644
index 0000000..eff36a0
--- /dev/null
+++ b/aws-lookoutvision-project/docs/memo.md
@@ -0,0 +1,39 @@
+# AWS::LookoutVision::Project Memo
+
+## Syntax
+
+To declare this entity in your AWS CloudFormation template, use the following syntax:
+
+### JSON
+
+
+{
+ "Heading " : String ,
+ "Body " : String
+}
+
+
+### YAML
+
+
+Heading : String
+Body : String
+
+
+## Properties
+
+#### Heading
+
+_Required_: No
+
+_Type_: String
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
+
+#### Body
+
+_Required_: No
+
+_Type_: String
+
+_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
diff --git a/aws-lookoutvision-project/example_inputs/inputs_1_create.json b/aws-lookoutvision-project/example_inputs/inputs_1_create.json
new file mode 100644
index 0000000..ca70b0e
--- /dev/null
+++ b/aws-lookoutvision-project/example_inputs/inputs_1_create.json
@@ -0,0 +1,11 @@
+{
+ "TPSCode": "...",
+ "Title": "...",
+ "CoverSheetIncluded": "...",
+ "DueDate": "...",
+ "ApprovalDate": "...",
+ "Memo": "...",
+ "SecondCopyOfMemo": "...",
+ "TestCode": "...",
+ "Authors": "..."
+}
diff --git a/aws-lookoutvision-project/example_inputs/inputs_1_invalid.json b/aws-lookoutvision-project/example_inputs/inputs_1_invalid.json
new file mode 100644
index 0000000..ca70b0e
--- /dev/null
+++ b/aws-lookoutvision-project/example_inputs/inputs_1_invalid.json
@@ -0,0 +1,11 @@
+{
+ "TPSCode": "...",
+ "Title": "...",
+ "CoverSheetIncluded": "...",
+ "DueDate": "...",
+ "ApprovalDate": "...",
+ "Memo": "...",
+ "SecondCopyOfMemo": "...",
+ "TestCode": "...",
+ "Authors": "..."
+}
diff --git a/aws-lookoutvision-project/example_inputs/inputs_1_update.json b/aws-lookoutvision-project/example_inputs/inputs_1_update.json
new file mode 100644
index 0000000..ca70b0e
--- /dev/null
+++ b/aws-lookoutvision-project/example_inputs/inputs_1_update.json
@@ -0,0 +1,11 @@
+{
+ "TPSCode": "...",
+ "Title": "...",
+ "CoverSheetIncluded": "...",
+ "DueDate": "...",
+ "ApprovalDate": "...",
+ "Memo": "...",
+ "SecondCopyOfMemo": "...",
+ "TestCode": "...",
+ "Authors": "..."
+}
diff --git a/aws-lookoutvision-project/lombok.config b/aws-lookoutvision-project/lombok.config
new file mode 100644
index 0000000..7a21e88
--- /dev/null
+++ b/aws-lookoutvision-project/lombok.config
@@ -0,0 +1 @@
+lombok.addLombokGeneratedAnnotation = true
diff --git a/aws-lookoutvision-project/pom.xml b/aws-lookoutvision-project/pom.xml
new file mode 100644
index 0000000..2f53028
--- /dev/null
+++ b/aws-lookoutvision-project/pom.xml
@@ -0,0 +1,222 @@
+
+
+ 4.0.0
+
+ software.amazon.lookoutvision.project
+ aws-lookoutvision-project-handler
+ aws-lookoutvision-project-handler
+ 1.0-SNAPSHOT
+ jar
+
+
+ 1.8
+ 1.8
+ UTF-8
+ UTF-8
+
+
+
+
+
+ software.amazon.cloudformation
+ aws-cloudformation-rpdk-java-plugin
+ [2.0.0,3.0.0)
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.4
+ provided
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.13.3
+
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.13.3
+
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ 2.13.3
+
+
+
+
+ org.assertj
+ assertj-core
+ 3.12.2
+ test
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ 5.5.0-M1
+ test
+
+
+
+ org.mockito
+ mockito-core
+ 3.6.0
+ test
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 3.6.0
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ -Xlint:all,-options,-processing
+ -Werror
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.3
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.6.0
+
+
+ generate
+ generate-sources
+
+ exec
+
+
+ cfn
+ generate
+ ${project.basedir}
+
+
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.0.0
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+ ${project.basedir}/target/generated-sources/rpdk
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-resources-plugin
+ 2.4
+
+
+ maven-surefire-plugin
+ 3.0.0-M3
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ 0.8.4
+
+
+ **/BaseConfiguration*
+ **/BaseHandler*
+ **/HandlerWrapper*
+ **/ResourceModel*
+
+
+
+
+
+ prepare-agent
+
+
+
+ report
+ test
+
+ report
+
+
+
+ jacoco-check
+
+ check
+
+
+
+
+ PACKAGE
+
+
+ BRANCH
+ COVEREDRATIO
+ 0.8
+
+
+ INSTRUCTION
+ COVEREDRATIO
+ 0.8
+
+
+
+
+
+
+
+
+
+
+
+ ${project.basedir}
+
+ aws-lookoutvision-project.json
+
+
+
+
+
diff --git a/aws-lookoutvision-project/resource-role.yaml b/aws-lookoutvision-project/resource-role.yaml
new file mode 100644
index 0000000..a2d4956
--- /dev/null
+++ b/aws-lookoutvision-project/resource-role.yaml
@@ -0,0 +1,35 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Description: >
+ This CloudFormation template creates a role assumed by CloudFormation
+ during CRUDL operations to mutate resources on behalf of the customer.
+
+Resources:
+ ExecutionRole:
+ Type: AWS::IAM::Role
+ Properties:
+ MaxSessionDuration: 8400
+ AssumeRolePolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Principal:
+ Service: resources.cloudformation.amazonaws.com
+ Action: sts:AssumeRole
+ Path: "/"
+ Policies:
+ - PolicyName: ResourceTypePolicy
+ PolicyDocument:
+ Version: '2012-10-17'
+ Statement:
+ - Effect: Allow
+ Action:
+ - "initech:CreateReport"
+ - "initech:DeleteReport"
+ - "initech:DescribeReport"
+ - "initech:ListReports"
+ - "initech:UpdateReport"
+ Resource: "*"
+Outputs:
+ ExecutionRoleArn:
+ Value:
+ Fn::GetAtt: ExecutionRole.Arn
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/BaseHandlerStd.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/BaseHandlerStd.java
new file mode 100644
index 0000000..e04de41
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/BaseHandlerStd.java
@@ -0,0 +1,34 @@
+package software.amazon.lookoutvision.project;
+
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+// Placeholder for the functionality that could be shared across Create/Read/Update/Delete/List Handlers
+
+public abstract class BaseHandlerStd extends BaseHandler {
+ @Override
+ public final ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+ return handleRequest(
+ proxy,
+ request,
+ callbackContext != null ? callbackContext : new CallbackContext(),
+ proxy.newProxy(ClientBuilder::getClient),
+ logger
+ );
+ }
+
+ protected abstract ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger);
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/CallbackContext.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/CallbackContext.java
new file mode 100644
index 0000000..362c56f
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/CallbackContext.java
@@ -0,0 +1,10 @@
+package software.amazon.lookoutvision.project;
+
+import software.amazon.cloudformation.proxy.StdCallbackContext;
+
+@lombok.Getter
+@lombok.Setter
+@lombok.ToString
+@lombok.EqualsAndHashCode(callSuper = true)
+public class CallbackContext extends StdCallbackContext {
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ClientBuilder.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ClientBuilder.java
new file mode 100644
index 0000000..d0061cb
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ClientBuilder.java
@@ -0,0 +1,25 @@
+package software.amazon.lookoutvision.project;
+
+import software.amazon.awssdk.core.SdkClient;
+// TODO: replace all usage of SdkClient with your service client type, e.g; YourServiceClient
+// import software.amazon.awssdk.services.yourservice.YourServiceClient;
+// import software.amazon.cloudformation.LambdaWrapper;
+
+public class ClientBuilder {
+ /*
+ TODO: uncomment the following, replacing YourServiceClient with your service client name
+ It is recommended to use static HTTP client so less memory is consumed
+ e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/master/aws-logs-loggroup/src/main/java/software/amazon/logs/loggroup/ClientBuilder.java#L9
+
+ public static YourServiceClient getClient() {
+ return YourServiceClient.builder()
+ .httpClient(LambdaWrapper.HTTP_CLIENT)
+ .build();
+ }
+ */
+
+ // TODO: remove this implementation once you have uncommented the above
+ public static SdkClient getClient() {
+ return null;
+ }
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/Configuration.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/Configuration.java
new file mode 100644
index 0000000..0fcca3d
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/Configuration.java
@@ -0,0 +1,8 @@
+package software.amazon.lookoutvision.project;
+
+class Configuration extends BaseConfiguration {
+
+ public Configuration() {
+ super("aws-lookoutvision-project.json");
+ }
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/CreateHandler.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/CreateHandler.java
new file mode 100644
index 0000000..5f80f6f
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/CreateHandler.java
@@ -0,0 +1,117 @@
+package software.amazon.lookoutvision.project;
+
+// TODO: replace all usage of SdkClient with your service client type, e.g; YourServiceAsyncClient
+// import software.amazon.awssdk.services.yourservice.YourServiceAsyncClient;
+
+import software.amazon.awssdk.awscore.AwsResponse;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+
+public class CreateHandler extends BaseHandlerStd {
+ private Logger logger;
+
+ protected ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger) {
+
+ this.logger = logger;
+
+ // TODO: Adjust Progress Chain according to your implementation
+ // https://github.com/aws-cloudformation/cloudformation-cli-java-plugin/blob/master/src/main/java/software/amazon/cloudformation/proxy/CallChain.java
+
+ return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
+
+ // STEP 1 [check if resource already exists]
+ // if target API does not support 'ResourceAlreadyExistsException' then following check is required
+ // for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
+ .then(progress ->
+ // STEP 1.0 [initialize a proxy context]
+ // If your service API is not idempotent, meaning it does not distinguish duplicate create requests against some identifier (e.g; resource Name)
+ // and instead returns a 200 even though a resource already exists, you must first check if the resource exists here
+ // NOTE: If your service API throws 'ResourceAlreadyExistsException' for create requests this method is not necessary
+ proxy.initiate("AWS-LookoutVision-Project::Create::PreExistanceCheck", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
+
+ // STEP 1.1 [TODO: construct a body of a request]
+ .translateToServiceRequest(Translator::translateToReadRequest)
+
+ // STEP 1.2 [TODO: make an api call]
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+
+ // TODO: add custom read resource logic
+
+ logger.log(String.format("%s has successfully been read.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+
+ // STEP 1.3 [TODO: handle exception]
+ .handleError((awsRequest, exception, client, model, context) -> {
+ // TODO: uncomment when ready to implement
+ // if (exception instanceof CfnNotFoundException)
+ // return ProgressEvent.progress(model, context);
+ // throw exception;
+ return ProgressEvent.progress(model, context);
+ })
+ .progress()
+ )
+
+ // STEP 2 [create/stabilize progress chain - required for resource creation]
+ .then(progress ->
+ // If your service API throws 'ResourceAlreadyExistsException' for create requests then CreateHandler can return just proxy.initiate construction
+ // STEP 2.0 [initialize a proxy context]
+ // Implement client invocation of the create request through the proxyClient, which is already initialised with
+ // caller credentials, correct region and retry settings
+ proxy.initiate("AWS-LookoutVision-Project::Create", proxyClient,progress.getResourceModel(), progress.getCallbackContext())
+
+ // STEP 2.1 [TODO: construct a body of a request]
+ .translateToServiceRequest(Translator::translateToCreateRequest)
+
+ // STEP 2.2 [TODO: make an api call]
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+ try {
+
+ // TODO: put your create resource code here
+
+ } catch (final AwsServiceException e) {
+ /*
+ * While the handler contract states that the handler must always return a progress event,
+ * you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
+ * Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
+ * to more specific error codes
+ */
+ throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
+ }
+
+ logger.log(String.format("%s successfully created.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+
+ // STEP 2.3 [TODO: stabilize step is not necessarily required but typically involves describing the resource until it is in a certain status, though it can take many forms]
+ // for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
+ // If your resource requires some form of stabilization (e.g. service does not provide strong consistency), you will need to ensure that your code
+ // accounts for any potential issues, so that a subsequent read/update requests will not cause any conflicts (e.g. NotFoundException/InvalidRequestException)
+ .stabilize((awsRequest, awsResponse, client, model, context) -> {
+ // TODO: put your stabilization code here
+
+ final boolean stabilized = true;
+ logger.log(String.format("%s [%s] has been stabilized.", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier()));
+ return stabilized;
+ })
+ .progress()
+ )
+
+ // STEP 3 [TODO: describe call/chain to return the resource model]
+ .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger));
+ }
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/DeleteHandler.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/DeleteHandler.java
new file mode 100644
index 0000000..a417c84
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/DeleteHandler.java
@@ -0,0 +1,114 @@
+package software.amazon.lookoutvision.project;
+
+// TODO: replace all usage of SdkClient with your service client type, e.g; YourServiceAsyncClient
+// import software.amazon.awssdk.services.yourservice.YourServiceAsyncClient;
+
+import software.amazon.awssdk.awscore.AwsResponse;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+public class DeleteHandler extends BaseHandlerStd {
+ private Logger logger;
+
+ protected ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger) {
+
+ this.logger = logger;
+
+ // TODO: Adjust Progress Chain according to your implementation
+ // https://github.com/aws-cloudformation/cloudformation-cli-java-plugin/blob/master/src/main/java/software/amazon/cloudformation/proxy/CallChain.java
+
+ return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
+
+ // STEP 1 [check if resource already exists]
+ // for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
+ // if target API does not support 'ResourceNotFoundException' then following check is required
+ .then(progress ->
+ // STEP 1.0 [initialize a proxy context]
+ // If your service API does not return ResourceNotFoundException on delete requests against some identifier (e.g; resource Name)
+ // and instead returns a 200 even though a resource already deleted, you must first check if the resource exists here
+ // NOTE: If your service API throws 'ResourceNotFoundException' for delete requests this method is not necessary
+ proxy.initiate("AWS-LookoutVision-Project::Delete::PreDeletionCheck", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
+
+ // STEP 1.1 [initialize a proxy context]
+ .translateToServiceRequest(Translator::translateToReadRequest)
+
+ // STEP 1.2 [TODO: make an api call]
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+
+ // TODO: add custom read resource logic
+
+ logger.log(String.format("%s has successfully been read.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+
+ // STEP 1.3 [TODO: handle exception]
+ .handleError((awsRequest, exception, client, model, context) -> {
+ // TODO: uncomment when ready to implement
+ // if (exception instanceof ResourceNotFoundException)
+ // return ProgressEvent.success(model, context);
+ // throw exception;
+ return ProgressEvent.progress(model, context);
+ })
+ .progress()
+ )
+
+ // STEP 2.0 [delete/stabilize progress chain - required for resource deletion]
+ .then(progress ->
+ // If your service API throws 'ResourceNotFoundException' for delete requests then DeleteHandler can return just proxy.initiate construction
+ // STEP 2.0 [initialize a proxy context]
+ // Implement client invocation of the delete request through the proxyClient, which is already initialised with
+ // caller credentials, correct region and retry settings
+ proxy.initiate("AWS-LookoutVision-Project::Delete", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
+
+ // STEP 2.1 [TODO: construct a body of a request]
+ .translateToServiceRequest(Translator::translateToDeleteRequest)
+
+ // STEP 2.2 [TODO: make an api call]
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+ try {
+
+ // TODO: put your delete resource code here
+
+ } catch (final AwsServiceException e) {
+ /*
+ * While the handler contract states that the handler must always return a progress event,
+ * you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
+ * Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
+ * to more specific error codes
+ */
+ throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
+ }
+
+ logger.log(String.format("%s successfully deleted.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+
+ // STEP 2.3 [TODO: stabilize step is not necessarily required but typically involves describing the resource until it is in a certain status, though it can take many forms]
+ // for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
+ .stabilize((awsRequest, awsResponse, client, model, context) -> {
+ // TODO: put your stabilization code here
+
+ final boolean stabilized = true;
+ logger.log(String.format("%s [%s] deletion has stabilized: %s", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier(), stabilized));
+ return stabilized;
+ })
+ .progress()
+ )
+
+ // STEP 3 [TODO: return the successful progress event without resource model]
+ .then(progress -> ProgressEvent.defaultSuccessHandler(null));
+ }
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ListHandler.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ListHandler.java
new file mode 100644
index 0000000..1a411a5
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ListHandler.java
@@ -0,0 +1,43 @@
+package software.amazon.lookoutvision.project;
+
+import software.amazon.awssdk.awscore.AwsRequest;
+import software.amazon.awssdk.awscore.AwsResponse;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.OperationStatus;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ListHandler extends BaseHandler {
+
+ @Override
+ public ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final Logger logger) {
+
+ final List models = new ArrayList<>();
+
+ // STEP 1 [TODO: construct a body of a request]
+ final AwsRequest awsRequest = Translator.translateToListRequest(request.getNextToken());
+
+ // STEP 2 [TODO: make an api call]
+ AwsResponse awsResponse = null; // proxy.injectCredentialsAndInvokeV2(awsRequest, ClientBuilder.getClient()::describeLogGroups);
+
+ // STEP 3 [TODO: get a token for the next page]
+ String nextToken = null;
+
+ // STEP 4 [TODO: construct resource models]
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/master/aws-logs-loggroup/src/main/java/software/amazon/logs/loggroup/ListHandler.java#L19-L21
+
+ return ProgressEvent.builder()
+ .resourceModels(models)
+ .nextToken(nextToken)
+ .status(OperationStatus.SUCCESS)
+ .build();
+ }
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ReadHandler.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ReadHandler.java
new file mode 100644
index 0000000..f885280
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/ReadHandler.java
@@ -0,0 +1,67 @@
+package software.amazon.lookoutvision.project;
+
+// TODO: replace all usage of SdkClient with your service client type, e.g; YourServiceAsyncClient
+// import software.amazon.awssdk.services.yourservice.YourServiceAsyncClient;
+
+import software.amazon.awssdk.awscore.AwsResponse;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+public class ReadHandler extends BaseHandlerStd {
+ private Logger logger;
+
+ protected ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger) {
+
+ this.logger = logger;
+
+ // TODO: Adjust Progress Chain according to your implementation
+ // https://github.com/aws-cloudformation/cloudformation-cli-java-plugin/blob/master/src/main/java/software/amazon/cloudformation/proxy/CallChain.java
+
+ // STEP 1 [initialize a proxy context]
+ return proxy.initiate("AWS-LookoutVision-Project::Read", proxyClient, request.getDesiredResourceState(), callbackContext)
+
+ // STEP 2 [TODO: construct a body of a request]
+ .translateToServiceRequest(Translator::translateToReadRequest)
+
+ // STEP 3 [TODO: make an api call]
+ // Implement client invocation of the read request through the proxyClient, which is already initialised with
+ // caller credentials, correct region and retry settings
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+ try {
+
+ // TODO: add custom read resource logic
+ // If describe request does not return ResourceNotFoundException, you must throw ResourceNotFoundException based on
+ // awsResponse values
+
+ } catch (final AwsServiceException e) { // ResourceNotFoundException
+ /*
+ * While the handler contract states that the handler must always return a progress event,
+ * you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
+ * Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
+ * to more specific error codes
+ */
+ throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e); // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/commit/2077c92299aeb9a68ae8f4418b5e932b12a8b186#diff-5761e3a9f732dc1ef84103dc4bc93399R56-R63
+ }
+
+ logger.log(String.format("%s has successfully been read.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+
+ // STEP 4 [TODO: gather all properties of the resource]
+ // Implement client invocation of the read request through the proxyClient, which is already initialised with
+ // caller credentials, correct region and retry settings
+ .done(awsResponse -> ProgressEvent.defaultSuccessHandler(Translator.translateFromReadResponse(awsResponse)));
+ }
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/Translator.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/Translator.java
new file mode 100644
index 0000000..5b3bfec
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/Translator.java
@@ -0,0 +1,124 @@
+package software.amazon.lookoutvision.project;
+
+import com.google.common.collect.Lists;
+import software.amazon.awssdk.awscore.AwsRequest;
+import software.amazon.awssdk.awscore.AwsResponse;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * This class is a centralized placeholder for
+ * - api request construction
+ * - object translation to/from aws sdk
+ * - resource model construction for read/list handlers
+ */
+
+public class Translator {
+
+ /**
+ * Request to create a resource
+ * @param model resource model
+ * @return awsRequest the aws service request to create a resource
+ */
+ static AwsRequest translateToCreateRequest(final ResourceModel model) {
+ final AwsRequest awsRequest = null;
+ // TODO: construct a request
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L39-L43
+ return awsRequest;
+ }
+
+ /**
+ * Request to read a resource
+ * @param model resource model
+ * @return awsRequest the aws service request to describe a resource
+ */
+ static AwsRequest translateToReadRequest(final ResourceModel model) {
+ final AwsRequest awsRequest = null;
+ // TODO: construct a request
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L20-L24
+ return awsRequest;
+ }
+
+ /**
+ * Translates resource object from sdk into a resource model
+ * @param awsResponse the aws service describe resource response
+ * @return model resource model
+ */
+ static ResourceModel translateFromReadResponse(final AwsResponse awsResponse) {
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L58-L73
+ return ResourceModel.builder()
+ //.someProperty(response.property())
+ .build();
+ }
+
+ /**
+ * Request to delete a resource
+ * @param model resource model
+ * @return awsRequest the aws service request to delete a resource
+ */
+ static AwsRequest translateToDeleteRequest(final ResourceModel model) {
+ final AwsRequest awsRequest = null;
+ // TODO: construct a request
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L33-L37
+ return awsRequest;
+ }
+
+ /**
+ * Request to update properties of a previously created resource
+ * @param model resource model
+ * @return awsRequest the aws service request to modify a resource
+ */
+ static AwsRequest translateToFirstUpdateRequest(final ResourceModel model) {
+ final AwsRequest awsRequest = null;
+ // TODO: construct a request
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L45-L50
+ return awsRequest;
+ }
+
+ /**
+ * Request to update some other properties that could not be provisioned through first update request
+ * @param model resource model
+ * @return awsRequest the aws service request to modify a resource
+ */
+ static AwsRequest translateToSecondUpdateRequest(final ResourceModel model) {
+ final AwsRequest awsRequest = null;
+ // TODO: construct a request
+ return awsRequest;
+ }
+
+ /**
+ * Request to list resources
+ * @param nextToken token passed to the aws service list resources request
+ * @return awsRequest the aws service request to list resources within aws account
+ */
+ static AwsRequest translateToListRequest(final String nextToken) {
+ final AwsRequest awsRequest = null;
+ // TODO: construct a request
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L26-L31
+ return awsRequest;
+ }
+
+ /**
+ * Translates resource objects from sdk into a resource model (primary identifier only)
+ * @param awsResponse the aws service describe resource response
+ * @return list of resource models
+ */
+ static List translateFromListRequest(final AwsResponse awsResponse) {
+ // e.g. https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-logs/blob/2077c92299aeb9a68ae8f4418b5e932b12a8b186/aws-logs-loggroup/src/main/java/com/aws/logs/loggroup/Translator.java#L75-L82
+ return streamOfOrEmpty(Lists.newArrayList())
+ .map(resource -> ResourceModel.builder()
+ // include only primary identifier
+ .build())
+ .collect(Collectors.toList());
+ }
+
+ private static Stream streamOfOrEmpty(final Collection collection) {
+ return Optional.ofNullable(collection)
+ .map(Collection::stream)
+ .orElseGet(Stream::empty);
+ }
+}
diff --git a/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/UpdateHandler.java b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/UpdateHandler.java
new file mode 100644
index 0000000..6c9221a
--- /dev/null
+++ b/aws-lookoutvision-project/src/main/java/software/amazon/lookoutvision/project/UpdateHandler.java
@@ -0,0 +1,140 @@
+package software.amazon.lookoutvision.project;
+
+// TODO: replace all usage of SdkClient with your service client type, e.g; YourServiceAsyncClient
+// import software.amazon.awssdk.services.yourservice.YourServiceAsyncClient;
+
+import software.amazon.awssdk.awscore.AwsResponse;
+import software.amazon.awssdk.awscore.exception.AwsServiceException;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+
+public class UpdateHandler extends BaseHandlerStd {
+ private Logger logger;
+
+ protected ProgressEvent handleRequest(
+ final AmazonWebServicesClientProxy proxy,
+ final ResourceHandlerRequest request,
+ final CallbackContext callbackContext,
+ final ProxyClient proxyClient,
+ final Logger logger) {
+
+ this.logger = logger;
+
+ // TODO: Adjust Progress Chain according to your implementation
+ // https://github.com/aws-cloudformation/cloudformation-cli-java-plugin/blob/master/src/main/java/software/amazon/cloudformation/proxy/CallChain.java
+
+ return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
+
+ // STEP 1 [check if resource already exists]
+ // for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
+ // if target API does not support 'ResourceNotFoundException' then following check is required
+ .then(progress ->
+ // STEP 1.0 [initialize a proxy context]
+ // If your service API does not return ResourceNotFoundException on update requests against some identifier (e.g; resource Name)
+ // and instead returns a 200 even though a resource does not exist, you must first check if the resource exists here
+ // NOTE: If your service API throws 'ResourceNotFoundException' for update requests this method is not necessary
+ proxy.initiate("AWS-LookoutVision-Project::Update::PreUpdateCheck", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
+
+ // STEP 1.1 [initialize a proxy context]
+ .translateToServiceRequest(Translator::translateToReadRequest)
+
+ // STEP 1.2 [TODO: make an api call]
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+
+ // TODO: add custom read resource logic
+ // If describe request does not return ResourceNotFoundException, you must throw ResourceNotFoundException based on
+ // awsResponse values
+
+ logger.log(String.format("%s has successfully been read.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+ .progress()
+ )
+
+ // STEP 2 [first update/stabilize progress chain - required for resource update]
+ .then(progress ->
+ // STEP 2.0 [initialize a proxy context]
+ // Implement client invocation of the update request through the proxyClient, which is already initialised with
+ // caller credentials, correct region and retry settings
+ proxy.initiate("AWS-LookoutVision-Project::Update::first", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
+
+ // STEP 2.1 [TODO: construct a body of a request]
+ .translateToServiceRequest(Translator::translateToFirstUpdateRequest)
+
+ // STEP 2.2 [TODO: make an api call]
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+ try {
+
+ // TODO: put your update resource code here
+
+ } catch (final AwsServiceException e) {
+ /*
+ * While the handler contract states that the handler must always return a progress event,
+ * you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
+ * Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
+ * to more specific error codes
+ */
+ throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
+ }
+
+ logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+
+ // STEP 2.3 [TODO: stabilize step is not necessarily required but typically involves describing the resource until it is in a certain status, though it can take many forms]
+ // stabilization step may or may not be needed after each API call
+ // for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
+ .stabilize((awsRequest, awsResponse, client, model, context) -> {
+ // TODO: put your stabilization code here
+
+ final boolean stabilized = true;
+
+ logger.log(String.format("%s [%s] update has stabilized: %s", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier(), stabilized));
+ return stabilized;
+ })
+ .progress())
+
+ // If your resource is provisioned through multiple API calls, then the following pattern is required (and might take as many postUpdate callbacks as necessary)
+ // STEP 3 [second update/stabilize progress chain]
+ .then(progress ->
+ // STEP 3.0 [initialize a proxy context]
+ // If your resource is provisioned through multiple API calls, you will need to apply each subsequent update
+ // step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended.
+ proxy.initiate("AWS-LookoutVision-Project::Update::second", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
+
+ // STEP 3.1 [TODO: construct a body of a request]
+ .translateToServiceRequest(Translator::translateToSecondUpdateRequest)
+
+ // STEP 3.2 [TODO: make an api call]
+ .makeServiceCall((awsRequest, client) -> {
+ AwsResponse awsResponse = null;
+ try {
+
+ // TODO: put your post update resource code here
+
+ } catch (final AwsServiceException e) {
+ /*
+ * While the handler contract states that the handler must always return a progress event,
+ * you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
+ * Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
+ * to more specific error codes
+ */
+ throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
+ }
+
+ logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME));
+ return awsResponse;
+ })
+ .progress())
+
+ // STEP 4 [TODO: describe call/chain to return the resource model]
+ .then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger));
+ }
+}
diff --git a/aws-lookoutvision-project/src/resources/log4j2.xml b/aws-lookoutvision-project/src/resources/log4j2.xml
new file mode 100644
index 0000000..5657daf
--- /dev/null
+++ b/aws-lookoutvision-project/src/resources/log4j2.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/AbstractTestBase.java b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/AbstractTestBase.java
new file mode 100644
index 0000000..ff76f24
--- /dev/null
+++ b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/AbstractTestBase.java
@@ -0,0 +1,66 @@
+package software.amazon.lookoutvision.project;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Function;
+import software.amazon.awssdk.awscore.AwsRequest;
+import software.amazon.awssdk.awscore.AwsResponse;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.awssdk.core.ResponseBytes;
+import software.amazon.awssdk.core.ResponseInputStream;
+import software.amazon.awssdk.core.pagination.sync.SdkIterable;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Credentials;
+import software.amazon.cloudformation.proxy.LoggerProxy;
+import software.amazon.cloudformation.proxy.ProxyClient;
+
+public class AbstractTestBase {
+ protected static final Credentials MOCK_CREDENTIALS;
+ protected static final LoggerProxy logger;
+
+ static {
+ MOCK_CREDENTIALS = new Credentials("accessKey", "secretKey", "token");
+ logger = new LoggerProxy();
+ }
+ static ProxyClient MOCK_PROXY(
+ final AmazonWebServicesClientProxy proxy,
+ final SdkClient sdkClient) {
+ return new ProxyClient() {
+ @Override
+ public ResponseT
+ injectCredentialsAndInvokeV2(RequestT request, Function requestFunction) {
+ return proxy.injectCredentialsAndInvokeV2(request, requestFunction);
+ }
+
+ @Override
+ public
+ CompletableFuture
+ injectCredentialsAndInvokeV2Async(RequestT request, Function> requestFunction) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public >
+ IterableT
+ injectCredentialsAndInvokeIterableV2(RequestT request, Function requestFunction) {
+ return proxy.injectCredentialsAndInvokeIterableV2(request, requestFunction);
+ }
+
+ @Override
+ public ResponseInputStream
+ injectCredentialsAndInvokeV2InputStream(RequestT requestT, Function> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResponseBytes
+ injectCredentialsAndInvokeV2Bytes(RequestT requestT, Function> function) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public SdkClient client() {
+ return sdkClient;
+ }
+ };
+ }
+}
diff --git a/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/CreateHandlerTest.java b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/CreateHandlerTest.java
new file mode 100644
index 0000000..cbfd94e
--- /dev/null
+++ b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/CreateHandlerTest.java
@@ -0,0 +1,68 @@
+package software.amazon.lookoutvision.project;
+
+import java.time.Duration;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.OperationStatus;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+@ExtendWith(MockitoExtension.class)
+public class CreateHandlerTest extends AbstractTestBase {
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private ProxyClient proxyClient;
+
+ @Mock
+ SdkClient sdkClient;
+
+ @BeforeEach
+ public void setup() {
+ proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
+ sdkClient = mock(SdkClient.class);
+ proxyClient = MOCK_PROXY(proxy, sdkClient);
+ }
+
+ @AfterEach
+ public void tear_down() {
+ verify(sdkClient, atLeastOnce()).serviceName();
+ verifyNoMoreInteractions(sdkClient);
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final CreateHandler handler = new CreateHandler();
+
+ final ResourceModel model = ResourceModel.builder().build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ .desiredResourceState(model)
+ .build();
+
+ final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+}
diff --git a/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/DeleteHandlerTest.java b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/DeleteHandlerTest.java
new file mode 100644
index 0000000..34d615d
--- /dev/null
+++ b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/DeleteHandlerTest.java
@@ -0,0 +1,68 @@
+package software.amazon.lookoutvision.project;
+
+import java.time.Duration;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.OperationStatus;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+@ExtendWith(MockitoExtension.class)
+public class DeleteHandlerTest extends AbstractTestBase {
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private ProxyClient proxyClient;
+
+ @Mock
+ SdkClient sdkClient;
+
+ @BeforeEach
+ public void setup() {
+ proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
+ sdkClient = mock(SdkClient.class);
+ proxyClient = MOCK_PROXY(proxy, sdkClient);
+ }
+
+ @AfterEach
+ public void tear_down() {
+ verify(sdkClient, atLeastOnce()).serviceName();
+ verifyNoMoreInteractions(sdkClient);
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final DeleteHandler handler = new DeleteHandler();
+
+ final ResourceModel model = ResourceModel.builder().build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ .desiredResourceState(model)
+ .build();
+
+ final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isNull();
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+}
diff --git a/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/ListHandlerTest.java b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/ListHandlerTest.java
new file mode 100644
index 0000000..cc52f51
--- /dev/null
+++ b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/ListHandlerTest.java
@@ -0,0 +1,54 @@
+package software.amazon.lookoutvision.project;
+
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.Logger;
+import software.amazon.cloudformation.proxy.OperationStatus;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+
+@ExtendWith(MockitoExtension.class)
+public class ListHandlerTest {
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private Logger logger;
+
+ @BeforeEach
+ public void setup() {
+ proxy = mock(AmazonWebServicesClientProxy.class);
+ logger = mock(Logger.class);
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final ListHandler handler = new ListHandler();
+
+ final ResourceModel model = ResourceModel.builder().build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ .desiredResourceState(model)
+ .build();
+
+ final ProgressEvent response =
+ handler.handleRequest(proxy, request, null, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackContext()).isNull();
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isNull();
+ assertThat(response.getResourceModels()).isNotNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+}
diff --git a/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/ReadHandlerTest.java b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/ReadHandlerTest.java
new file mode 100644
index 0000000..a1c7883
--- /dev/null
+++ b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/ReadHandlerTest.java
@@ -0,0 +1,68 @@
+package software.amazon.lookoutvision.project;
+
+import java.time.Duration;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.OperationStatus;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+@ExtendWith(MockitoExtension.class)
+public class ReadHandlerTest extends AbstractTestBase {
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private ProxyClient proxyClient;
+
+ @Mock
+ SdkClient sdkClient;
+
+ @BeforeEach
+ public void setup() {
+ proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
+ sdkClient = mock(SdkClient.class);
+ proxyClient = MOCK_PROXY(proxy, sdkClient);
+ }
+
+ @AfterEach
+ public void tear_down() {
+ verify(sdkClient, atLeastOnce()).serviceName();
+ verifyNoMoreInteractions(sdkClient);
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final ReadHandler handler = new ReadHandler();
+
+ final ResourceModel model = ResourceModel.builder().build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ .desiredResourceState(model)
+ .build();
+
+ final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+}
diff --git a/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/UpdateHandlerTest.java b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/UpdateHandlerTest.java
new file mode 100644
index 0000000..c6eefdb
--- /dev/null
+++ b/aws-lookoutvision-project/src/test/java/software/amazon/lookoutvision/project/UpdateHandlerTest.java
@@ -0,0 +1,68 @@
+package software.amazon.lookoutvision.project;
+
+import java.time.Duration;
+import software.amazon.awssdk.core.SdkClient;
+import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
+import software.amazon.cloudformation.proxy.OperationStatus;
+import software.amazon.cloudformation.proxy.ProgressEvent;
+import software.amazon.cloudformation.proxy.ProxyClient;
+import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+@ExtendWith(MockitoExtension.class)
+public class UpdateHandlerTest extends AbstractTestBase {
+
+ @Mock
+ private AmazonWebServicesClientProxy proxy;
+
+ @Mock
+ private ProxyClient proxyClient;
+
+ @Mock
+ SdkClient sdkClient;
+
+ @BeforeEach
+ public void setup() {
+ proxy = new AmazonWebServicesClientProxy(logger, MOCK_CREDENTIALS, () -> Duration.ofSeconds(600).toMillis());
+ sdkClient = mock(SdkClient.class);
+ proxyClient = MOCK_PROXY(proxy, sdkClient);
+ }
+
+ @AfterEach
+ public void tear_down() {
+ verify(sdkClient, atLeastOnce()).serviceName();
+ verifyNoMoreInteractions(sdkClient);
+ }
+
+ @Test
+ public void handleRequest_SimpleSuccess() {
+ final UpdateHandler handler = new UpdateHandler();
+
+ final ResourceModel model = ResourceModel.builder().build();
+
+ final ResourceHandlerRequest request = ResourceHandlerRequest.builder()
+ .desiredResourceState(model)
+ .build();
+
+ final ProgressEvent response = handler.handleRequest(proxy, request, new CallbackContext(), proxyClient, logger);
+
+ assertThat(response).isNotNull();
+ assertThat(response.getStatus()).isEqualTo(OperationStatus.SUCCESS);
+ assertThat(response.getCallbackDelaySeconds()).isEqualTo(0);
+ assertThat(response.getResourceModel()).isEqualTo(request.getDesiredResourceState());
+ assertThat(response.getResourceModels()).isNull();
+ assertThat(response.getMessage()).isNull();
+ assertThat(response.getErrorCode()).isNull();
+ }
+}
diff --git a/aws-lookoutvision-project/template.yml b/aws-lookoutvision-project/template.yml
new file mode 100644
index 0000000..583cfc7
--- /dev/null
+++ b/aws-lookoutvision-project/template.yml
@@ -0,0 +1,23 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Transform: AWS::Serverless-2016-10-31
+Description: AWS SAM template for the AWS::LookoutVision::Project resource type
+
+Globals:
+ Function:
+ Timeout: 180 # docker start-up times can be long for SAM CLI
+ MemorySize: 256
+
+Resources:
+ TypeFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: software.amazon.lookoutvision.project.HandlerWrapper::handleRequest
+ Runtime: java8
+ CodeUri: ./target/aws-lookoutvision-project-handler-1.0-SNAPSHOT.jar
+
+ TestEntrypoint:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: software.amazon.lookoutvision.project.HandlerWrapper::testEntrypoint
+ Runtime: java8
+ CodeUri: ./target/aws-lookoutvision-project-handler-1.0-SNAPSHOT.jar