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