diff --git a/.gitignore b/.gitignore
index cfa6b239db..9e9a7e00ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,4 @@ dist/
.vscode/
.cache/
./aws-sam-local
+debug
diff --git a/README.md b/README.md
index 0d041945f9..7683a1e200 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@
- [Package and Deploy to Lambda](#package-and-deploy-to-lambda)
- [Getting started](#getting-started)
- [Advanced](#advanced)
+ - [Compiled Languages (Java)](#compiled-languages-java)
- [IAM Credentials](#iam-credentials)
- [Lambda Environment Variables](#lambda-environment-variables)
- [Environment Variable file](#environment-variable-file)
@@ -285,6 +286,40 @@ $ sam deploy --template-file ./packaged.yaml --stack-name mystack --capabilities
## Advanced
+### Compiled Languages (Java)
+
+To use SAM Local with compiled languages, such as Java that require a packaged artifact (e.g. a JAR, or ZIP), you can specify the location of the artifact with the `AWS::Serverless::Function` `CodeUri` property in your SAM template.
+
+For example:
+
+```
+AWSTemplateFormatVersion: 2010-09-09
+Transform: AWS::Serverless-2016-10-31
+
+Resources:
+ ExampleJavaFunction:
+ Type: AWS::Serverless::Function
+ Properties:
+ Handler: com.example.HelloWorldHandler
+ CodeUri: ./target/HelloWorld-1.0.jar
+ Runtime: java8
+```
+
+You should then build your JAR file using your normal build process. Please note that JAR files used with AWS Lambda should be a shaded JAR file (or uber jar) containing all of the function dependencies.
+
+```
+// Build the JAR file
+$ mvn package shade:shade
+
+// Invoke with SAM Local
+$ echo '{ "some": "input" }' | sam local invoke
+
+// Or start local API Gateway simulator
+$ sam local start-api
+```
+
+You can find a full Java example in the [samples/java](samples/java) folder
+
### IAM Credentials
SAM Local will invoke functions with your locally configured IAM credentials.
diff --git a/runtime.go b/runtime.go
index 65449a262a..6995b1bade 100644
--- a/runtime.go
+++ b/runtime.go
@@ -1,6 +1,7 @@
package main
import (
+ "archive/zip"
"io"
"log"
"os"
@@ -47,6 +48,7 @@ type Runtime struct {
Name string
Image string
Cwd string
+ DecompressedCwd string
Function resources.AWSServerlessFunction
EnvVarOverrides map[string]string
DebugPort string
@@ -199,15 +201,25 @@ func overrideHostConfig(cfg *container.HostConfig) error {
}
func (r *Runtime) getHostConfig() (*container.HostConfig, error) {
+
+ // Check if there is a decompressed archive directory we should
+ // be using instead of the normal working directory (e.g. if a
+ // ZIP/JAR archive was specified as the CodeUri)
+ mount := r.Cwd
+ if r.DecompressedCwd != "" {
+ mount = r.DecompressedCwd
+ }
+
host := &container.HostConfig{
Resources: container.Resources{
Memory: int64(r.Function.MemorySize() * 1024 * 1024),
},
Binds: []string{
- fmt.Sprintf("%s:/var/task:ro", r.Cwd),
+ fmt.Sprintf("%s:/var/task:ro", mount),
},
PortBindings: r.getDebugPortBindings(),
}
+
if err := overrideHostConfig(host); err != nil {
log.Print(err)
}
@@ -222,6 +234,18 @@ func (r *Runtime) Invoke(event string) (io.Reader, io.Reader, error) {
log.Printf("Invoking %s (%s)\n", r.Function.Handler(), r.Name)
+ // If the CodeUri has been specified as a .jar or .zip file, unzip it on the fly
+ if strings.HasSuffix(r.Cwd, ".jar") || strings.HasSuffix(r.Cwd, ".zip") {
+ log.Printf("Decompressing %s into runtime container...\n", filepath.Base(r.Cwd))
+ decompressedDir, err := decompressArchive(r.Cwd)
+ if err != nil {
+ log.Printf("ERROR: Failed to decompress archive: %s\n", err)
+ return nil, nil, fmt.Errorf("failed to decompress archive: %s", err)
+ }
+ r.DecompressedCwd = decompressedDir
+
+ }
+
env := getEnvironmentVariables(r.Function, r.EnvVarOverrides)
// Define the container options
@@ -517,11 +541,21 @@ func toStringMaybe(value interface{}) (string, bool) {
// CleanUp removes the Docker container used by this runtime
func (r *Runtime) CleanUp() {
+
+ // Stop the Lambda timeout timer
if r.TimeoutTimer != nil {
r.TimeoutTimer.Stop()
}
+
+ // Remove the container
r.Client.ContainerKill(r.Context, r.ID, "SIGKILL")
r.Client.ContainerRemove(r.Context, r.ID, types.ContainerRemoveOptions{})
+
+ // Remove any decompressed archive if there was one (e.g. ZIP/JAR)
+ if r.DecompressedCwd != "" {
+ os.RemoveAll(r.DecompressedCwd)
+ }
+
}
// demuxDockerStream takes a Docker attach stream, and parses out stdout/stderr
@@ -591,7 +625,79 @@ func getWorkingDir(basedir string, codeuri string, checkWorkingDirExist bool) (s
// Windows uses \ as the path delimiter, but Docker requires / as the path delimiter.
dir = filepath.ToSlash(dir)
-
return dir, nil
}
+
+// decompressArchive unzips a ZIP archive to a temporary directory and returns
+// the temporary directory name, or an error
+func decompressArchive(src string) (string, error) {
+
+ // Create a temporary directory just for this decompression (dirname: OS tmp directory + unix timestamp))
+ tmpdir := os.TempDir()
+
+ // By default on OSX, os.TempDir() returns a directory in /var/folders/.
+ // This sits outside the default Docker Shared Files directories, however
+ // /var/folders is just a symlink to /private/var/folders/, so use that instead
+ if strings.HasPrefix(tmpdir, "/var/folders") {
+ tmpdir = "/private" + tmpdir
+ }
+
+ dest := filepath.Join(tmpdir, "aws-sam-local-"+strconv.FormatInt(time.Now().UnixNano(), 10))
+
+ var filenames []string
+
+ r, err := zip.OpenReader(src)
+ if err != nil {
+ return dest, err
+ }
+ defer r.Close()
+
+ for _, f := range r.File {
+
+ rc, err := f.Open()
+ if err != nil {
+ return dest, err
+ }
+ defer rc.Close()
+
+ // Store filename/path for returning and using later on
+ fpath := filepath.Join(dest, f.Name)
+ filenames = append(filenames, fpath)
+
+ if f.FileInfo().IsDir() {
+
+ // Make Folder
+ os.MkdirAll(fpath, os.ModePerm)
+
+ } else {
+
+ // Make File
+ var fdir string
+ if lastIndex := strings.LastIndex(fpath, string(os.PathSeparator)); lastIndex > -1 {
+ fdir = fpath[:lastIndex]
+ }
+
+ err = os.MkdirAll(fdir, os.ModePerm)
+ if err != nil {
+ log.Fatal(err)
+ return dest, err
+ }
+ f, err := os.OpenFile(
+ fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
+ if err != nil {
+ return dest, err
+ }
+ defer f.Close()
+
+ _, err = io.Copy(f, rc)
+ if err != nil {
+ return dest, err
+ }
+
+ }
+ }
+
+ return dest, nil
+
+}
diff --git a/samples/java/.gitignore b/samples/java/.gitignore
new file mode 100644
index 0000000000..e420ee4ba0
--- /dev/null
+++ b/samples/java/.gitignore
@@ -0,0 +1 @@
+target/*
diff --git a/samples/java/README.md b/samples/java/README.md
new file mode 100644
index 0000000000..e2cfb82be8
--- /dev/null
+++ b/samples/java/README.md
@@ -0,0 +1,45 @@
+Welcome to the AWS CodeStar sample web service
+==============================================
+
+This sample code helps get you started with a simple Java web service using
+AWS Lambda and Amazon API Gateway.
+
+What's Here
+-----------
+
+This sample includes:
+
+* README.md - this file
+* buildspec.yml - this file is used by AWS CodeBuild to build the web
+ service
+* pom.xml - this file is the Maven Project Object Model for the web service
+* src/ - this directory contains your Java service source files
+* template.yml - this file contains the Serverless Application Model (SAM) used
+ by AWS Cloudformation to deploy your application to AWS Lambda and Amazon API
+ Gateway.
+
+
+What Do I Do Next?
+------------------
+
+If you have checked out a local copy of your AWS CodeCommit repository you can
+start making changes to the sample code. We suggest making a small change to
+index.py first, so you can see how changes pushed to your project's repository
+in AWS CodeCommit are automatically picked up by your project pipeline and
+deployed to AWS Lambda and Amazon API Gateway. (You can watch the pipeline
+progress on your AWS CodeStar project dashboard.) Once you've seen how that
+works, start developing your own code, and have fun!
+
+Learn more about Serverless Application Model (SAM) and how it works here:
+https://github.com/awslabs/serverless-application-model/blob/master/HOWTO.md
+
+AWS Lambda Developer Guide:
+http://docs.aws.amazon.com/lambda/latest/dg/deploying-lambda-apps.html
+
+Learn more about AWS CodeStar by reading the user guide, and post questions and
+comments about AWS CodeStar on our forum.
+
+AWS CodeStar User Guide:
+http://docs.aws.amazon.com/codestar/latest/userguide/welcome.html
+
+AWS CodeStar Forum: https://forums.aws.amazon.com/forum.jspa?forumID=248
diff --git a/samples/java/buildspec.yml b/samples/java/buildspec.yml
new file mode 100644
index 0000000000..1b8a006adb
--- /dev/null
+++ b/samples/java/buildspec.yml
@@ -0,0 +1,15 @@
+version: 0.2
+
+phases:
+ build:
+ commands:
+ - echo Entering build phase...
+ - echo Build started on `date`
+ - mvn package shade:shade
+ - mv target/HelloWorld-1.0.jar .
+ - unzip HelloWorld-1.0.jar
+ - rm -rf target src buildspec.yml pom.xml HelloWorld-1.0.jar
+ - aws cloudformation package --template template.yml --s3-bucket $S3_BUCKET --output-template template-export.json
+artifacts:
+ files:
+ - template-export.json
\ No newline at end of file
diff --git a/samples/java/pom.xml b/samples/java/pom.xml
new file mode 100644
index 0000000000..c78dfa7bfa
--- /dev/null
+++ b/samples/java/pom.xml
@@ -0,0 +1,62 @@
+
+ 4.0.0
+ com.aws.codestar.projecttemplates
+ HelloWorld
+ 1.0
+ jar
+ A sample Java Spring web service created with AWS CodeStar.
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.4.3.RELEASE
+
+
+ 1.8
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-tomcat
+ provided
+
+
+ com.amazonaws
+ aws-lambda-java-core
+ 1.1.0
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.3
+
+ false
+
+
+
+ package
+
+ shade
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/samples/java/src/main/java/com/aws/codestar/projecttemplates/Application.java b/samples/java/src/main/java/com/aws/codestar/projecttemplates/Application.java
new file mode 100644
index 0000000000..b6702649bd
--- /dev/null
+++ b/samples/java/src/main/java/com/aws/codestar/projecttemplates/Application.java
@@ -0,0 +1,19 @@
+package com.aws.codestar.projecttemplates;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/** Simple class to start up the application.
+ *
+ * @SpringBootApplication adds:
+ * @Configuration
+ * @EnableAutoConfiguration
+ * @ComponentScan
+ */
+@SpringBootApplication
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/samples/java/src/main/java/com/aws/codestar/projecttemplates/GatewayResponse.java b/samples/java/src/main/java/com/aws/codestar/projecttemplates/GatewayResponse.java
new file mode 100644
index 0000000000..0d3bb005a0
--- /dev/null
+++ b/samples/java/src/main/java/com/aws/codestar/projecttemplates/GatewayResponse.java
@@ -0,0 +1,33 @@
+package com.aws.codestar.projecttemplates;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * POJO containing response object for API Gateway.
+ */
+public class GatewayResponse {
+
+ private final String body;
+ private final Map headers;
+ private final int statusCode;
+
+ public GatewayResponse(final String body, final Map headers, final int statusCode) {
+ this.statusCode = statusCode;
+ this.body = body;
+ this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
+ }
+
+ public String getBody() {
+ return body;
+ }
+
+ public Map getHeaders() {
+ return headers;
+ }
+
+ public int getStatusCode() {
+ return statusCode;
+ }
+}
diff --git a/samples/java/src/main/java/com/aws/codestar/projecttemplates/handler/HelloWorldHandler.java b/samples/java/src/main/java/com/aws/codestar/projecttemplates/handler/HelloWorldHandler.java
new file mode 100644
index 0000000000..d437d09328
--- /dev/null
+++ b/samples/java/src/main/java/com/aws/codestar/projecttemplates/handler/HelloWorldHandler.java
@@ -0,0 +1,24 @@
+package com.aws.codestar.projecttemplates.handler;
+
+import com.amazonaws.services.lambda.runtime.Context;
+import com.amazonaws.services.lambda.runtime.RequestHandler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import com.aws.codestar.projecttemplates.GatewayResponse;
+
+/**
+ * Handler for requests to Lambda function.
+ */
+public class HelloWorldHandler implements RequestHandler