Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions java/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.classpath.txt
target
.classpath
.project
.idea
.settings
.vscode
*.iml

# CDK asset staging directory
.cdk.staging
cdk.out

34 changes: 34 additions & 0 deletions java/infrastructure/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"app": "mvn -e -q compile exec:java",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"target",
"pom.xml"
]
},
"context": {
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
"@aws-cdk/core:stackRelativeExports": true,
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
"@aws-cdk/aws-lambda:recognizeVersionProps": true,
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true,
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
]
}
}
59 changes: 59 additions & 0 deletions java/infrastructure/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>

<groupId>com.cwworkshop</groupId>
<artifactId>cwworkshop</artifactId>
<version>0.1</version>

<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<cdk.version>2.34.2</cdk.version>
<constructs.version>[10.0.0,11.0.0)</constructs.version>
<junit.version>5.7.1</junit.version>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
</plugin>

<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<mainClass>com.cwworkshop.WorkshopApp</mainClass>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<!-- AWS Cloud Development Kit -->
<dependency>
<groupId>software.amazon.awscdk</groupId>
<artifactId>aws-cdk-lib</artifactId>
<version>${cdk.version}</version>
</dependency>

<dependency>
<groupId>software.constructs</groupId>
<artifactId>constructs</artifactId>
<version>${constructs.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.cwworkshop;

import java.util.Arrays;
import java.util.List;

import software.amazon.awscdk.BundlingOptions;
import software.amazon.awscdk.DockerVolume;
import software.amazon.awscdk.services.lambda.Runtime;

import static java.util.Collections.singletonList;
import static software.amazon.awscdk.BundlingOutput.ARCHIVED;

public class LambdaBundling {
public static BundlingOptions get(String packageName) {
String cmd = "mvn clean install " +
"&& cp /asset-input/target/%s.jar /asset-output/";

List<String> packagingInstructions = Arrays.asList(
"/bin/sh",
"-c",
String.format(cmd, packageName));

BundlingOptions builderOptions = BundlingOptions.builder()
.command(packagingInstructions)
.image(Runtime.JAVA_11.getBundlingImage())
.volumes(singletonList(
// Mount local .m2 repo to avoid download all the dependencies again inside the container
DockerVolume.builder()
.hostPath(System.getProperty("user.home") + "/.m2/")
.containerPath("/root/.m2/")
.build()))
.user("root")
.outputType(ARCHIVED)
.build();

return builderOptions;
}
}
34 changes: 34 additions & 0 deletions java/infrastructure/src/main/java/com/cwworkshop/WorkshopApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.cwworkshop;

import software.amazon.awscdk.App;
import software.amazon.awscdk.Environment;
import software.amazon.awscdk.StackProps;

import com.cwworkshop.api.APIStack;
import com.cwworkshop.integration.IntegrationStack;
import com.cwworkshop.recognition.RecognitionStack;
import com.cwworkshop.recognition.RecognitionStackProps;

public class WorkshopApp {
private static final String DEFAULT_REGION = "us-east-2";

public static void main(final String[] args) {
App app = new App();

Environment defaultEnv = Environment.builder().region(DEFAULT_REGION).build();
StackProps defaultProps = StackProps.builder().env(defaultEnv).build();

APIStack apiStack = new APIStack(app, "APIStack", defaultProps);
IntegrationStack integrationStack = new IntegrationStack(app, "IntegrationStack", defaultProps);

RecognitionStackProps recognitionStackProps = new RecognitionStackProps()
.sqsArn(apiStack.getUploadQueueArn())
.sqsUrl(apiStack.getUploadQueueUrl())
.snsArn(integrationStack.getSnsArn())
.withEnv(defaultEnv);

new RecognitionStack(app, "RekognitionStack", recognitionStackProps);

app.synth();
}
}
95 changes: 95 additions & 0 deletions java/infrastructure/src/main/java/com/cwworkshop/api/APIStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.cwworkshop.api;

import software.constructs.Construct;

import java.util.Map;

import com.cwworkshop.LambdaBundling;

import software.amazon.awscdk.Duration;
import software.amazon.awscdk.RemovalPolicy;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.s3.Bucket;
import software.amazon.awscdk.services.s3.EventType;
import software.amazon.awscdk.services.s3.assets.AssetOptions;
import software.amazon.awscdk.services.s3.notifications.SnsDestination;
import software.amazon.awscdk.services.sns.Topic;
import software.amazon.awscdk.services.sns.subscriptions.SqsSubscription;
import software.amazon.awscdk.services.sqs.Queue;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.apigateway.LambdaIntegration;
import software.amazon.awscdk.services.apigateway.RestApi;
import software.amazon.awscdk.services.lambda.Code;

public class APIStack extends Stack {

private String uploadQueueUrl;
private String uploadQueueArn;

public String getUploadQueueUrl() {
return this.uploadQueueUrl;
}

public String getUploadQueueArn() {
return this.uploadQueueArn;
}

public APIStack(final Construct scope, final String id) {
this(scope, id, null);
}

public APIStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);

final Bucket bucket = Bucket.Builder.create(this, "CW-Workshop-Images")
.removalPolicy(RemovalPolicy.DESTROY)
.build();

final Function imageGetAndSaveLambda = Function.Builder.create(this, "ImageGetAndSaveLambda")
.functionName("ImageGetAndSaveLambda")
.runtime(Runtime.JAVA_11)
.memorySize(1024)
.timeout(Duration.seconds(60))
.code(Code.fromAsset(
"../software/api",
AssetOptions.builder().bundling(LambdaBundling.get("api")).build()))
.handler("api.Handler")
.environment(Map.of("BUCKET_NAME", bucket.getBucketName()))
.build();

bucket.grantReadWrite(imageGetAndSaveLambda);

final RestApi api = RestApi.Builder.create(this, "REST_API")
.restApiName("Image Upload Service")
.description("CW workshop - upload image for workshop.")
.build();

final LambdaIntegration getImageIntegration = LambdaIntegration.Builder.create(imageGetAndSaveLambda)
.requestTemplates(Map.of("application/json", "{ \"statusCode\": \"200\" }"))
.build();

api.getRoot().addMethod("GET", getImageIntegration);

final Queue uploadQueue = Queue.Builder.create(this, "uploaded_image_queue")
.visibilityTimeout(Duration.seconds(30))
.build();

this.uploadQueueUrl = uploadQueue.getQueueUrl();
this.uploadQueueArn = uploadQueue.getQueueArn();

final SqsSubscription sqsSubscription = SqsSubscription.Builder.create(uploadQueue)
.rawMessageDelivery(true)
.build();

final Topic uploadEventTopic = Topic.Builder.create(this, "uploaded_image_topic")
.build();

uploadEventTopic.addSubscription(sqsSubscription);

bucket.addEventNotification(
EventType.OBJECT_CREATED_PUT,
new SnsDestination(uploadEventTopic));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.cwworkshop.integration;

import com.cwworkshop.LambdaBundling;

import software.constructs.Construct;

import software.amazon.awscdk.Duration;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.services.sns.Topic;
import software.amazon.awscdk.services.sns.subscriptions.SqsSubscription;
import software.amazon.awscdk.services.sqs.Queue;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;
import software.amazon.awscdk.services.lambda.eventsources.SqsEventSource;
import software.amazon.awscdk.services.s3.assets.AssetOptions;
import software.amazon.awscdk.services.lambda.Code;

public class IntegrationStack extends Stack {
private String rekognizedEventTopicArn;

public String getSnsArn() {
return this.rekognizedEventTopicArn;
}

public IntegrationStack(final Construct scope, final String id) {
this(scope, id, null);
}

public IntegrationStack(final Construct scope, final String id, final StackProps props) {
super(scope, id, props);

final Queue rekognizedQueue = Queue.Builder.create(this, "rekognized_image_queue")
.visibilityTimeout(Duration.seconds(30))
.build();

final SqsSubscription sqsSubscription = SqsSubscription.Builder.create(rekognizedQueue)
.rawMessageDelivery(true)
.build();

final Topic rekognizedEventTopic = Topic.Builder.create(this, "rekognized_image_topic")
.build();

this.rekognizedEventTopicArn = rekognizedEventTopic.getTopicArn();
rekognizedEventTopic.addSubscription(sqsSubscription);

final Function integrationLambda = Function.Builder.create(this, "IntegrationLambda")
.runtime(Runtime.JAVA_11)
.timeout(Duration.seconds(60))
.memorySize(1024)
.handler("integration.Handler")
.code(Code.fromAsset(
"../software/integration",
AssetOptions.builder().bundling(LambdaBundling.get("integration")).build()))
.build();

final SqsEventSource invokeEventSource = SqsEventSource.Builder.create(rekognizedQueue).build();
integrationLambda.addEventSource(invokeEventSource);
}
}
Loading