Skip to content

Commit

Permalink
Fix lambda import (#605)
Browse files Browse the repository at this point in the history
* Fix lambda import

* more updates

* more changes
  • Loading branch information
seiflotfy authored and treeder committed Mar 24, 2017
1 parent cc4ff19 commit d8871d1
Show file tree
Hide file tree
Showing 6 changed files with 44 additions and 217 deletions.
6 changes: 3 additions & 3 deletions examples/lambda/node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

This is the exact same function that is in the AWS Lambda tutorial.

Other than a different runtime, this is no different than any other node example.
Other than a different runtime, this is no different than any other node example.

To use the lambda-node runtime, use this `fn init` command:
To use the lambda-nodejs4.3 runtime, use this `fn init` command:

```sh
fn init --runtime lambda-node <DOCKER_HUB_USERNAME>/lambda-node
fn init --runtime lambda-nodejs4.3 <DOCKER_HUB_USERNAME>/lambda-node
fn build
cat payload.json | fn run
```
32 changes: 16 additions & 16 deletions fn/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,22 +118,22 @@ func exists(name string) bool {
}

var acceptableFnRuntimes = map[string]string{
"elixir": "iron/elixir",
"erlang": "iron/erlang",
"gcc": "iron/gcc",
"go": "iron/go",
"java": "iron/java",
"leiningen": "iron/leiningen",
"mono": "iron/mono",
"node": "iron/node",
"perl": "iron/perl",
"php": "iron/php",
"python": "iron/python:2",
"ruby": "iron/ruby",
"scala": "iron/scala",
"rust": "corey/rust-alpine",
"dotnet": "microsoft/dotnet:runtime",
"lambda-node": "iron/functions-lambda:node",
"elixir": "iron/elixir",
"erlang": "iron/erlang",
"gcc": "iron/gcc",
"go": "iron/go",
"java": "iron/java",
"leiningen": "iron/leiningen",
"mono": "iron/mono",
"node": "iron/node",
"perl": "iron/perl",
"php": "iron/php",
"python": "iron/python:2",
"ruby": "iron/ruby",
"scala": "iron/scala",
"rust": "corey/rust-alpine",
"dotnet": "microsoft/dotnet:runtime",
"lambda-nodejs4.3": "iron/functions-lambda:nodejs4.3",
}

const tplDockerfile = `FROM {{ .BaseImage }}
Expand Down
217 changes: 22 additions & 195 deletions fn/lambda.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"archive/zip"
"bytes"
"errors"
"fmt"
"io"
Expand All @@ -17,16 +16,12 @@ import (
"github.com/aws/aws-sdk-go/aws/session"
aws_lambda "github.com/aws/aws-sdk-go/service/lambda"
"github.com/docker/docker/pkg/jsonmessage"
lambdaImpl "github.com/iron-io/lambda/lambda"
"github.com/urfave/cli"
yaml "gopkg.in/yaml.v2"
)

func init() {
if len(runtimeCreateHandlers) != len(runtimeImportHandlers) {
panic("incomplete implementation of runtime support")
}

var runtimes = map[string]string{
"nodejs4.3": "lambda-nodejs4.3",
}

func lambda() cli.Command {
Expand All @@ -38,20 +33,6 @@ func lambda() cli.Command {
Name: "lambda",
Usage: "create and publish lambda functions",
Subcommands: []cli.Command{
{
Name: "create-function",
Usage: `create Docker image that can run your Lambda function, where files are the contents of the zip file to be uploaded to AWS Lambda.`,
ArgsUsage: "<name> <runtime> <handler> </path> [/paths...]",
Action: create,
Flags: flags,
},
{
Name: "test-function",
Usage: `runs local dockerized Lambda function and writes output to stdout.`,
ArgsUsage: "<name>",
Action: test,
Flags: flags,
},
{
Name: "aws-import",
Usage: `converts an existing Lambda function to an image, where the function code is downloaded to a directory in the current working directory that has the same name as the Lambda function.`,
Expand Down Expand Up @@ -100,87 +81,6 @@ func transcribeEnvConfig(configs []string) map[string]string {
return c
}

// create creates the Docker image for the Lambda function
func create(c *cli.Context) error {
args := c.Args()
functionName := args[0]
runtime := args[1]
handler := args[2]
fileNames := args[3:]

files := make([]fileLike, 0, len(fileNames))
opts := createImageOptions{
Name: functionName,
Base: fmt.Sprintf("iron/lambda-%s", runtime),
Package: "",
Handler: handler,
OutputStream: newdockerJSONWriter(os.Stdout),
RawJSONStream: true,
Config: transcribeEnvConfig(c.StringSlice("config")),
}

if handler == "" {
return errors.New("No handler specified.")
}

rh, ok := runtimeCreateHandlers[runtime]
if !ok {
return fmt.Errorf("unsupported runtime %v", runtime)
}

if err := rh(fileNames, &opts); err != nil {
return err
}

for _, fileName := range fileNames {
file, err := os.Open(fileName)
if err != nil {
return err
}
defer file.Close()
files = append(files, file)
}

return createDockerfile(opts, files...)
}

var runtimeCreateHandlers = map[string]func(filenames []string, opts *createImageOptions) error{
"nodejs": func(filenames []string, opts *createImageOptions) error { return nil },
"python2.7": func(filenames []string, opts *createImageOptions) error { return nil },
"java8": func(filenames []string, opts *createImageOptions) error {
if len(filenames) != 1 {
return errors.New("Java Lambda functions can only include 1 file and it must be a JAR file.")
}

if filepath.Ext(filenames[0]) != ".jar" {
return errors.New("Java Lambda function package must be a JAR file.")
}

opts.Package = filepath.Base(filenames[0])
return nil
},
}

func test(c *cli.Context) error {
args := c.Args()
if len(args) < 1 {
return fmt.Errorf("Missing NAME argument")
}
functionName := args[0]

exists, err := lambdaImpl.ImageExists(functionName)
if err != nil {
return err
}
if !exists {
return fmt.Errorf("Function %s does not exist.", functionName)
}

payload := c.String("payload")
// Redirect output to stdout.
return lambdaImpl.RunImageWithPayload(functionName, payload)
}

func awsImport(c *cli.Context) error {
args := c.Args()

Expand Down Expand Up @@ -216,7 +116,7 @@ func awsImport(c *cli.Context) error {

opts := createImageOptions{
Name: functionName,
Base: fmt.Sprintf("iron/lambda-%s", *function.Configuration.Runtime),
Base: runtimes[(*function.Configuration.Runtime)],
Package: "",
Handler: *function.Configuration.Handler,
OutputStream: newdockerJSONWriter(os.Stdout),
Expand All @@ -230,7 +130,7 @@ func awsImport(c *cli.Context) error {
return fmt.Errorf("unsupported runtime %v", runtime)
}

files, err := rh(functionName, tmpFileName, &opts)
_, err = rh(functionName, tmpFileName, &opts)
if err != nil {
return nil
}
Expand All @@ -239,12 +139,18 @@ func awsImport(c *cli.Context) error {
opts.Name = image
}

return createDockerfile(opts, files...)
fmt.Print("Creating func.yaml ... ")
if err := createFunctionYaml(opts, functionName); err != nil {
return err
}
fmt.Println("OK")

return nil
}

var (
runtimeImportHandlers = map[string]func(functionName, tmpFileName string, opts *createImageOptions) ([]fileLike, error){
"nodejs": basicImportHandler,
"nodejs4.3": basicImportHandler,
"python2.7": basicImportHandler,
"java8": func(functionName, tmpFileName string, opts *createImageOptions) ([]fileLike, error) {
fmt.Println("Found Java Lambda function. Going to assume code is a single JAR file.")
Expand All @@ -268,20 +174,25 @@ func basicImportHandler(functionName, tmpFileName string, opts *createImageOptio
return unzipAndGetTopLevelFiles(functionName, tmpFileName)
}

func createFunctionYaml(opts createImageOptions) error {
func createFunctionYaml(opts createImageOptions, functionName string) error {
strs := strings.Split(opts.Name, "/")
path := fmt.Sprintf("/%s", strs[1])

funcDesc := &funcfile{
Name: opts.Name,
Path: &path,
Config: opts.Config,
Name: opts.Name,
Path: &path,
Config: opts.Config,
Version: "0.0.1",
Runtime: &opts.Base,
Cmd: opts.Handler,
}

out, err := yaml.Marshal(funcDesc)
if err != nil {
return err
}
return ioutil.WriteFile(filepath.Join(opts.Name, "func.yaml"), out, 0644)

return ioutil.WriteFile(filepath.Join(functionName, "func.yaml"), out, 0644)
}

type createImageOptions struct {
Expand All @@ -301,90 +212,6 @@ type fileLike interface {

var errNoFiles = errors.New("No files to add to image")

// Create a Dockerfile that adds each of the files to the base image. The
// expectation is that the base image sets up the current working directory
// inside the image correctly. `handler` is set to be passed to node-lambda
// for now, but we may have to change this to accomodate other stacks.
func makeDockerfile(base string, pkg string, handler string, files ...fileLike) ([]byte, error) {
var buf bytes.Buffer
fmt.Fprintf(&buf, "FROM %s\n", base)

for _, file := range files {
// FIXME(nikhil): Validate path, no parent paths etc.
info, err := file.Stat()
if err != nil {
return buf.Bytes(), err
}

fmt.Fprintf(&buf, "ADD [\"%s\", \"./%s\"]\n", info.Name(), info.Name())
}

fmt.Fprint(&buf, "CMD [")
if pkg != "" {
fmt.Fprintf(&buf, "\"%s\", ", pkg)
}
// FIXME(nikhil): Validate handler.
fmt.Fprintf(&buf, `"%s"`, handler)
fmt.Fprint(&buf, "]\n")

return buf.Bytes(), nil
}

// Creates a docker image called `name`, using `base` as the base image.
// `handler` is the runtime-specific name to use for a lambda invocation (i.e.
// <module>.<function> for nodejs). `files` should be a list of files+dirs
// *relative to the current directory* that are to be included in the image.
func createDockerfile(opts createImageOptions, files ...fileLike) error {
if len(files) == 0 {
return errNoFiles
}

df, err := makeDockerfile(opts.Base, opts.Package, opts.Handler, files...)
if err != nil {
return err
}

fmt.Printf("Creating directory: %s ... ", opts.Name)
if err := os.MkdirAll(opts.Name, os.ModePerm); err != nil {
return err
}
fmt.Println("OK")

fmt.Printf("Creating Dockerfile: %s ... ", filepath.Join(opts.Name, "Dockerfile"))
outputFile, err := os.Create(filepath.Join(opts.Name, "Dockerfile"))
if err != nil {
return err
}
fmt.Println("OK")

for _, f := range files {
fstat, err := f.Stat()
if err != nil {
return err
}
fmt.Printf("Copying file: %s ... ", filepath.Join(opts.Name, fstat.Name()))
src, err := os.Create(filepath.Join(opts.Name, fstat.Name()))
if err != nil {
return err
}
if _, err := io.Copy(src, f); err != nil {
return err
}
fmt.Println("OK")
}

if _, err = outputFile.Write(df); err != nil {
return err
}

fmt.Print("Creating func.yaml ... ")
if err := createFunctionYaml(opts); err != nil {
return err
}
fmt.Println("OK")
return nil
}

type dockerJSONWriter struct {
under io.Writer
w io.Writer
Expand Down
2 changes: 1 addition & 1 deletion fn/lambda/node/build.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
set -ex

docker build -t iron/functions-lambda:node4.3 .
docker build -t iron/functions-lambda:nodejs4.3 .
2 changes: 1 addition & 1 deletion fn/lambda/node/release.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
set -ex

./build.sh
docker push iron/functions-lambda:node4.3
docker push iron/functions-lambda:nodejs4.3
2 changes: 1 addition & 1 deletion fn/langs/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func GetLangHelper(lang string) LangHelper {
return &RustLangHelper{}
case "dotnet":
return &DotNetLangHelper{}
case "lambda-node":
case "lambda-nodejs4.3":
return &LambdaNodeHelper{}
}
return nil
Expand Down

0 comments on commit d8871d1

Please sign in to comment.