Skip to content

Fix lambda import #605

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 24, 2017
Merged
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
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