Get Endpoints #
Stepping back, we can see a problem now that our app is being deployed by our pipeline. There is no easy way to find the endpoints of our application (the TableViewer
and APIGateway
endpoints), so we can’t call it! Let’s add a little bit of code to expose these more obviously.
First edit CdkWorkshopStack.java
to get these values and expose them as properties of our stack:
package com.myorg;
import io.github.cdklabs.dynamotableviewer.TableViewer;
import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.CfnOutput;
import software.amazon.awscdk.services.apigateway.LambdaRestApi;
import software.amazon.awscdk.services.lambda.Code;
import software.amazon.awscdk.services.lambda.Function;
import software.amazon.awscdk.services.lambda.Runtime;
public class CdkWorkshopStack extends Stack {
public final CfnOutput hcViewerUrl;
public final CfnOutput hcEndpoint;
public CdkWorkshopStack(final Construct parent, final String id) {
this(parent, id, null);
}
public CdkWorkshopStack(final Construct parent, final String id, final StackProps props) {
super(parent, id, props);
// Defines a new lambda resource
final Function hello = Function.Builder.create(this, "HelloHandler")
.runtime(Runtime.NODEJS_14_X) // execution environment
.code(Code.fromAsset("lambda")) // code loaded from the "lambda" directory
.handler("hello.handler") // file is "hello", function is "handler"
.build();
// Defines our hitcounter resource
final HitCounter helloWithCounter = new HitCounter(this, "HelloHitCounter", HitCounterProps.builder()
.downstream(hello)
.build());
// Defines an API Gateway REST API resource backed by our "hello" function
final LambdaRestApi gateway = LambdaRestApi.Builder.create(this, "Endpoint")
.handler(helloWithCounter.getHandler())
.build();
// Defines a viewer for the HitCounts table
final TableViewer tv = TableViewer.Builder.create(this, "ViewerHitCount")
.title("Hello Hits")
.table(helloWithCounter.getTable())
.build();
hcViewerUrl = CfnOutput.Builder.create(this, "TableViewerUrl")
.value(tv.getEndpoint())
.build();
hcEndpoint = CfnOutput.Builder.create(this, "GatewayUrl")
.value(gateway.getUrl())
.build();
}
}
By adding outputs hcViewerUrl
and hcEnpoint
, we expose the necessary endpoints to our HitCounter application. We are using the core construct CfnOutput
to declare these as Cloudformation stack outputs (we will get to this in a minute).
Let’s commit these changes to our repo (git commit -am "MESSAGE" && git push
), and navigate to the Cloudformation console. You can see there are three stacks.
CDKToolkit
: The first is the integrated CDK stack (you should always see this on bootstrapped accounts). You can ignore this.WorkshopPipelineStack
: This is the stack that declares our pipeline. It isn’t the one we need right now.Deploy-WebService
: Here is our application! Select this, and under details, select theOutputs
tab. Here you should see four endpoints (two pairs of duplicate values). Two of them,EndpointXXXXXX
andViewerHitCounterViewerEndpointXXXXXXX
, are defaults generated by Cloudformation, and the other two are the outputs we declared ourselves.
If you click the TableViewerUrl
value, you should see our pretty hitcounter table that we created in the initial workshop.
Add Validation Test #
Now we have our application deployed, but no CD pipeline is complete without tests!
Let’s start with a simple test to ping our endpoints to see if they are alive.
Return to WorkshopPipelineStack.java
and add the following:
package com.myorg;
import java.util.List;
import java.util.Map;
import software.constructs.Construct;
import software.amazon.awscdk.Stack;
import software.amazon.awscdk.StackProps;
import software.amazon.awscdk.pipelines.CodeBuildStep;
import software.amazon.awscdk.pipelines.CodePipeline;
import software.amazon.awscdk.pipelines.CodePipelineSource;
import software.amazon.awscdk.pipelines.StageDeployment;
import software.amazon.awscdk.services.codecommit.Repository;
import software.amazon.awscdk.services.codepipeline.actions.CodeCommitSourceAction;
public class WorkshopPipelineStack extends Stack {
public WorkshopPipelineStack(final Construct parent, final String id) {
this(parent, id, null);
}
public WorkshopPipelineStack(final Construct parent, final String id, final StackProps props) {
super(parent, id, props);
// PIPELINE CODE HERE
final WorkshopPipelineStage deploy = new WorkshopPipelineStage(this, "Deploy");
StageDeployment stageDeployment = pipeline.addStage(deploy);
stageDeployment.addPost(
CodeBuildStep.Builder.create("TestViewerEndpoint")
.projectName("TestViewerEndpoint")
.commands(List.of("curl -Ssf $ENDPOINT_URL"))
.envFromCfnOutputs(Map.of("ENDPOINT_URL", /* TBD */))
.build(),
CodeBuildStep.Builder.create("TestAPIGatewayEndpoint")
.projectName("TestAPIGatewayEndpoint")
.envFromCfnOutputs(Map.of("ENDPOINT_URL", /* TBD */))
.commands(List.of(
"curl -Ssf $ENDPOINT_URL",
"curl -Ssf $ENDPOINT_URL/hello",
"curl -Ssf $ENDPOINT_URL/test"
))
.build()
);
}
}
We add deployment post step via stageDeployment.addPost(...)
from CDK Pipelines. We add two actions to our deployment stage that test our TableViewer endpoint and our APIGateway endpoint respectively.
Note: We submit several
curl
requests to the APIGateway endpoint so that when we look at our tableviewer, there are several values already populated.
You may notice that we have not yet set the URLs of these endpoints. This is because they are not yet exposed to this stack!
With a slight modification to WorkshopPipelineStage.java
we can expose them:
package com.myorg;
import software.amazon.awscdk.Stage;
import software.constructs.Construct;
import software.amazon.awscdk.StageProps;
import software.amazon.awscdk.CfnOutput;
public class WorkshopPipelineStage extends Stage {
public final CfnOutput hcViewerUrl;
public final CfnOutput hcEndpoint;
public WorkshopPipelineStage(final Construct scope, final String id) {
this(scope, id, null);
}
public WorkshopPipelineStage(final Construct scope, final String id, final StageProps props) {
super(scope, id, props);
final CdkWorkshopStack service = new CdkWorkshopStack(this, "WebService");
hcViewerUrl = service.hcViewerUrl;
hcEndpoint = service.hcEndpoint;
}
}
Now we can add those values to our actions in WorkshopPipelineStack.java
by getting the StackOutput
of our pipeline stack:
// OTHER CODE HERE...
final WorkshopPipelineStage deploy = new WorkshopPipelineStage(this, "Deploy");
StageDeployment stageDeployment = pipeline.addStage(deploy);
stageDeployment.addPost(
CodeBuildStep.Builder.create("TestViewerEndpoint")
.projectName("TestViewerEndpoint")
.commands(List.of("curl -Ssf $ENDPOINT_URL"))
.envFromCfnOutputs(Map.of("ENDPOINT_URL", deploy.hcViewerUrl))
.build(),
CodeBuildStep.Builder.create("TestAPIGatewayEndpoint")
.projectName("TestAPIGatewayEndpoint")
.envFromCfnOutputs(Map.of("ENDPOINT_URL", deploy.hcEndpoint))
.commands(List.of(
"curl -Ssf $ENDPOINT_URL",
"curl -Ssf $ENDPOINT_URL/hello",
"curl -Ssf $ENDPOINT_URL/test"
))
.build()
Commit and View! #
Commit those changes, wait for the pipeline to re-deploy the app, and navigate back to the CodePipeline Console and you can now see that there are two test actions contained within the Deploy
stage!
Congratulations! You have successfully created a CD pipeline for your application complete with tests and all! Feel free to explore the console to see the details of the stack created, or check out the API Reference section on CDK Pipelines and build one for your application.