Navigation Menu

Skip to content
This repository has been archived by the owner on Dec 1, 2022. It is now read-only.

Commit

Permalink
Add the skeleton of an AWS API Gateway + Lambda frontend service
Browse files Browse the repository at this point in the history
This adds a Spring MVC/REST web service, running as a Lambda function,
fronted by API Gateway. See `docs/frontend.md` for an overview of how
this works

The main moving parts are:
- Spring controller from which Swagger model is derived: `com.amazonaws.blox.frontend.controllers.EnvironmentController`
- Serverless Application Model template: `frontend-infrastructure/api/template.yml`
- Generated Swagger model: `frontend-service/api/swagger.yml`

There is some custom build logic in `buildSrc` that takes care of
generating a Swagger model from the Spring controller, and to add all
the necessary extensions to make it work with API Gateway.

This commit does not include the client that gets generated from the
API, for ease of reviewing.
  • Loading branch information
Jacob Buys authored and wjbuys committed Aug 9, 2017
1 parent d81dc48 commit b3d8545
Show file tree
Hide file tree
Showing 35 changed files with 1,578 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .travis.yml
@@ -1,6 +1,8 @@
# Travis auto-detects build.gradle, and configures an appropriate build step (`gradle check`):
# (see https://docs.travis-ci.com/user/languages/java/#Projects-Using-Gradle)
language: java
jdk:
- oraclejdk8

# Avoid updating the cache after every build:
# (see https://docs.travis-ci.com/user/languages/java/#Caching)
Expand Down
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Expand Up @@ -7,6 +7,9 @@ If you would like to make a significant change, it's a good idea to first open a
### Making the request
Development takes place against the dev branch of this repository and pull requests should be opened against that branch.

### Code Style
This project follows the [Google Java Styleguide](https://google.github.io/styleguide/javaguide.html), and this style is enforced as part of the `check` task. We recommend you install [the `google-java-format` plugin for your IDE](https://github.com/google/google-java-format), or use the `gradle spotlessApply` task to format code before checking in.

### Testing
Any contributions should pass all tests. You can run all tests by running `gradle check` from the project root.

Expand Down
60 changes: 60 additions & 0 deletions README.md
Expand Up @@ -9,6 +9,66 @@ Blox is being delivered as a managed service via the Amazon ECS Console, API and
Blox schedulers are built using AWS primitives, and the Blox designs and code are open source. If you are interested in learning more or collaborating on the designs, please read the [design](docs/daemon_design.md).
If you are currently using Blox v0.3, please read the [FAQ](docs/faq.md).

### Project structure
For an overview of the components of Blox, run:

```
./gradlew projects
```

### Testing
To run the full unit test suite, run:

```
./gradlew check
```

This will run the same tests that we run in the Travis CI build.

### Deploying
To deploy a personal stack:
- install the [official AWS CLI](https://aws.amazon.com/cli/)
- create an IAM user with the following permissions:

```json
{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Action":[
"s3:*",
"lambda:*",
"apigateway:*",
"cloudformation:*",
"iam:*"
],
"Resource":"*"
}]
}

```

These permissions are pretty broad, so we recommend you use a separate, test account.
- configure the `default` profile with the AWS credentials for the user you created above
- create an S3 bucket where all resources (code, cloudformation templates, etc) to be deployed will be stored:

```
./gradlew createBucket
```

- deploy the Blox stack:

```
./gradlew deploy
```

### End to end testing
Once you have a stack deployed, you can test it with:

```
./gradlew testEndToEnd
```

### Contact

* [Gitter](https://gitter.im/blox)
Expand Down
100 changes: 100 additions & 0 deletions build.gradle
@@ -1,3 +1,103 @@
import groovy.json.JsonSlurper

group 'com.amazonaws.blox'
version '0.1-SNAPSHOT'
description "Blox: Open Source schedulers for Amazon ECS"

buildscript {
repositories {
maven { url 'https://plugins.gradle.org/m2/' }
}

dependencies {
classpath 'com.diffplug.gradle.spotless:spotless:2.4.1'
}
}

def unformattedProjects = [
'frontend-infrastructure',
'frontend-service-client'
]

allprojects {
apply plugin: 'com.diffplug.gradle.spotless'
}

configure(subprojects.findAll { !unformattedProjects.contains(it.name) }) {
spotless {
java {
googleJavaFormat()
licenseHeaderFile rootProject.file('licenses/license-header.java')
}
}
}

ext {
// This can be overridden by specifying -PawsProfile=my-other-profile
// on the Gradle command line:
awsProfile = project.hasProperty("awsProfile") ? project.awsProfile : "default"
println("Profile: ${awsProfile}")

awsCli = "/usr/local/bin/aws"
awsRegion = "us-west-2"
awsPrefix = System.getenv("USER")

stackName = "${awsPrefix}-blox-frontend-${awsRegion}"
s3BucketName = "${stackName}-${awsProfile}"
stageName = "Beta"

sdkZip = file("${buildDir}/java-sdk-${version}.zip")
}

def aws(... args) {
return [awsCli, "--profile", awsProfile, "--region", awsRegion, *args]
}

task downloadClient() {
group "codegen"
description "Download a new version of the SDK for the currently deployed stack."

def deployTask = tasks.getByPath(":frontend-infrastructure:deploy")

inputs.file deployTask
outputs.file sdkZip

doLast {
sdkZip.parentFile.mkdirs()

def stackOutputs = new JsonSlurper().parse(deployTask.outputs.files.singleFile)

def parameters = [
"service.name=Blox",
"java.package-name=com.amazonaws.blox",
"java.build-system=gradle",
"java.group-id=${project.group}",
"java.artifact-id=frontend-service-client",
"java.artifact-version=${project.version}",
].join(",")

exec {
commandLine aws("apigateway", "get-sdk",
"--rest-api-id", stackOutputs.ApiId,
"--stage-name", stageName,
"--sdk-type", "java",
"--parameters", parameters,
sdkZip)
}
}
}

task updateClient(type: Copy, dependsOn: downloadClient) {
group "codegen"
description "Unpack the client for the currently deployed stack into the blox-client subproject."

ext.tmpDir = file("${buildDir}/tmp/sdk")

from zipTree(sdkZip)
into tmpDir

doLast {
file("frontend-service-client").deleteDir()
file("${tmpDir}/generated-code").renameTo(file("frontend-service-client"))
}
}
17 changes: 17 additions & 0 deletions buildSrc/build.gradle
@@ -1,3 +1,20 @@
description "Custom build logic for building Blox"

apply plugin: 'java'

sourceCompatibility = 1.8

repositories {
mavenCentral()
}

dependencies {
compile gradleApi()
compile localGroovy()

compile 'com.github.kongchen:swagger-maven-plugin:+'

compileOnly 'org.projectlombok:lombok:1.16.16'

testCompile group: 'junit', name: 'junit', version: '4.12'
}
@@ -0,0 +1,80 @@
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may
* not use this file except in compliance with the License. A copy of the
* License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "LICENSE" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.blox.swagger;

import io.swagger.models.Operation;
import io.swagger.models.Path;
import io.swagger.models.Swagger;
import java.util.HashMap;
import java.util.Map;
import lombok.AllArgsConstructor;
import org.gradle.api.tasks.Input;

/**
* Add API Gateway extensions to all operations of a swagger spec.
*
* <p>This will add the necessary x-amazon-apigateway-integration section to all operations in a
* Swagger spec to correctly proxy all requests to a single lambda function.
*
* <p>TODO Move everything in com.amazonaws.blox.swagger to a separate project
*/
@AllArgsConstructor
public class ApiGatewayExtensionsFilter implements SwaggerFilter {
/**
* The template for the Lambda function name to proxy to, in Cloudformation's Fn::Sub syntax.
*
* <p>A typical format for this is:
*
* <p>arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${MyLambdaFunction.Arn}/invocations
*/
@Input private final String lambdaFunctionArnTemplate;

public Map<String, Object> defaultExtensions() {
Map<String, Object> extensions = new HashMap<>();

extensions.put("passthroughBehavior", "when_no_match");
extensions.put("httpMethod", "POST");
extensions.put("type", "aws_proxy");

extensions.put("uri", sub(lambdaFunctionArnTemplate));

return extensions;
}

@Override
public void apply(Swagger swagger) {
Map<String, Object> extensions = defaultExtensions();

for (Path path : swagger.getPaths().values()) {
for (Operation operation : path.getOperations()) {
operation.setVendorExtension("x-amazon-apigateway-integration", extensions);
}
}
}

/**
* Create a Fn::Sub node with the given template as contents.
*
* <p>This is to support using the CloudFormation Fn::Sub intrinsic function in the swagger
* definition. Using sub("foo${AWS::Region}") for example, would emit {"Fn::Sub":
* "foo${AWS::Region}"} in the template.
*/
Map<String, String> sub(String template) {
Map<String, String> map = new HashMap<>();
map.put("Fn::Sub", template);

return map;
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may
* not use this file except in compliance with the License. A copy of the
* License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "LICENSE" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.blox.swagger;

import io.swagger.models.Swagger;
import java.time.Instant;

/**
* Add a timestamp to a Swagger spec as an 'x-generated-at' extended property.
*
* <p>TODO Move everything in com.amazonaws.blox.swagger to a separate project
*/
public class GenerationTimestampFilter implements SwaggerFilter {
@Override
public void apply(Swagger swagger) {
swagger.setVendorExtension("x-generated-at", Instant.now().toString());
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may
* not use this file except in compliance with the License. A copy of the
* License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "LICENSE" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package com.amazonaws.blox.swagger;

import io.swagger.models.Swagger;

/**
* A filter that can apply arbitrary changes to a Swagger model
*
* <p>Implementations of this interface are wired up into {@link
* com.amazonaws.blox.tasks.GenerateSwaggerModel} to post-process the Swagger model it generates
* from source code.
*
* <p>TODO Move everything in com.amazonaws.blox.swagger to a separate project
*/
public interface SwaggerFilter {
void apply(Swagger swagger);
}

0 comments on commit b3d8545

Please sign in to comment.