From a3c8d946fc7d963104f35ba3e8e6a0da69880cc7 Mon Sep 17 00:00:00 2001 From: Clare Liguori Date: Wed, 2 Oct 2019 17:59:21 -0700 Subject: [PATCH] Extract function deployment package from container image Bump version to 1.0, yay! --- README.md | 43 +++---- VERSION | 2 +- example/{function => deploy}/serverless.yml | 6 +- example/{function => deploy}/template.json | 4 +- example/{function => deploy}/template.yaml | 4 +- img2lambda/cli/main.go | 15 ++- img2lambda/extract/repack_image.go | 124 +++++++++++++++----- img2lambda/extract/repack_image_test.go | 64 +++++++++- img2lambda/types/types.go | 5 + scripts/build_example.sh | 13 +- 10 files changed, 211 insertions(+), 69 deletions(-) rename example/{function => deploy}/serverless.yml (77%) rename example/{function => deploy}/template.json (87%) rename example/{function => deploy}/template.yaml (86%) diff --git a/README.md b/README.md index 1264c52d..c94540e9 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,18 @@ [![Build Status](https://travis-ci.org/awslabs/aws-lambda-container-image-converter.svg?branch=master)](https://travis-ci.org/awslabs/aws-lambda-container-image-converter) -This container image converter tool (img2lambda) repackages container images (such as Docker images) into AWS Lambda layers, and publishes them as new layer versions to Lambda. +This container image converter tool (img2lambda) extracts an AWS Lambda function deployment package from a container image (such as a Docker image). +It also extracts AWS Lambda layers from a container image, and publishes them as new layer versions to Lambda. -The tool copies all files under '/opt' in the Docker image, maintaining the individual Docker image layers as individual Lambda layers. The published layer ARNs will be stored in a file 'output/layers.json', which can be used as input when creating Lambda functions. Each layer is named using a "namespace" prefix (like img2lambda or my-docker-image) and the SHA256 digest of the Docker image layer, in order to provide a way of tracking the provenance of the Lambda layer back to the Docker image that created it. +![img2lambda Demo](assets/demo.gif) -If a layer is already published to Lambda (same layer name, SHA256 digest, and size), it will not be published again. Instead the existing layer version ARN will be written to the output file. +To extract a Lambda function deployment package, the tool copies all files under '/var/task' in the container image into a deployment package zip file. -![img2lambda Demo](assets/demo.gif) +To extract Lambda layers, the tool copies all files under '/opt' in the container image, repackaging the individual container image layers as individual Lambda layer zip files. +The published layer ARNs are stored in a file 'output/layers.json', which can be used as input when creating Lambda functions. +Each layer is named using a "namespace" prefix (like 'img2lambda' or 'my-docker-image') and the SHA256 digest of the container image layer, in order to provide a way of tracking the provenance of the Lambda layer back to the container image that created it. +If a layer is already published to Lambda (same layer name, SHA256 digest, and size), it will not be published again. +Instead the existing layer version ARN will be written to the output file. **Table of Contents** @@ -41,7 +46,7 @@ GLOBAL OPTIONS: --image-type value, -t value Type of the source container image. Valid values: 'docker' (Docker image from the local Docker daemon), 'oci' (OCI image archive at the given path and optional tag) (default: "docker") --region value, -r value AWS region (default: "us-east-1") --profile value, -p value AWS credentials profile. Credentials will default to the same chain as the AWS CLI: environment variables, default profile, container credentials, EC2 instance credentials - --output-directory value, -o value Destination directory for command output (default: "./output") + --output-directory value, -o value Destination directory for output: function deployment package (function.zip) and list of published layers (layers.json, layers.yaml) (default: "./output") --layer-namespace value, -n value Prefix for the layers published to Lambda (default: "img2lambda") --dry-run, -d Conduct a dry-run: Repackage the image, but only write the Lambda layers to local disk (do not publish to Lambda) --description value, --desc value The description of this layer version (default: "created by img2lambda from image ") @@ -124,21 +129,21 @@ For example: ### Docker Example -Build the example Docker image to create a PHP Lambda custom runtime: +Build the example Docker image to create a PHP Lambda custom runtime and Hello World PHP function: ``` cd example docker build -t lambda-php . ``` -The example PHP functions are also built into the example image, so they can be run with Docker: +The Hello World function can be invoked locally by running the Docker image: ``` docker run lambda-php hello '{"name": "World"}' docker run lambda-php goodbye '{"name": "World"}' ``` -Run the tool to create and publish Lambda layers that contain the PHP custom runtime: +Run the tool to both create a Lambda deployment package that contains the Hello World PHP function, and to create and publish Lambda layers that contain the PHP custom runtime: ``` ../bin/local/img2lambda -i lambda-php:latest -r us-east-1 -o ./output ``` @@ -154,26 +159,22 @@ podman build --format oci -t lambda-php . podman push lambda-php oci-archive:./lambda-php-oci ``` -Run the tool to create and publish Lambda layers that contain the PHP custom runtime: +Run the tool to both create a Lambda deployment package that contains the Hello World PHP function, and to create and publish Lambda layers that contain the PHP custom runtime: ``` ../bin/local/img2lambda -i ./lambda-php-oci -t oci -r us-east-1 -o ./output ``` ### Deploy Manually -Create a PHP function that uses the layers: +Create a PHP function that uses the layers and deployment package extracted from the container image: ``` -cd function - -zip hello.zip src/hello.php - aws lambda create-function \ --function-name php-example-hello \ --handler hello \ - --zip-file fileb://./hello.zip \ + --zip-file fileb://./output/function.zip \ --runtime provided \ --role "arn:aws:iam::XXXXXXXXXXXX:role/service-role/LambdaPhpExample" \ --region us-east-1 \ - --layers file://../output/layers.json + --layers file://./output/layers.json ``` Finally, invoke the function: @@ -191,18 +192,18 @@ cat hello-output.txt ### Deploy with AWS Serverless Application Model (SAM) -See [the sample template.yaml](example/function/template.yaml) and [the sample template.json](example/function/template.json). +See [the sample template.yaml](example/deploy/template.yaml) and [the sample template.json](example/deploy/template.json). Insert the layers ARNs into the function definition: ``` -cd function +cd example/deploy sed -i 's/^- / - /' ../output/layers.yaml && \ sed -e "/LAYERS_PLACEHOLDER/r ../output/layers.yaml" -e "s///" template.yaml > template-with-layers.yaml OR -cd function +cd example/deploy sed -e "/\"LAYERS_PLACEHOLDER\"/r ../output/layers.json" -e "s///" template.json | jq . > template-with-layers.json ``` @@ -247,11 +248,11 @@ cat hello-output.txt ### Deploy with Serverless Framework -See [the sample serverless.yml](example/function/serverless.yml) for how to use the img2lambda-generated layers in your Serverless function. +See [the sample serverless.yml](example/deploy/serverless.yml) for how to use the img2lambda-generated layers in your Serverless function. Deploy the function: ``` -cd function +cd example/deploy serverless deploy -v ``` diff --git a/VERSION b/VERSION index 79a2734b..afaf360d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.0 \ No newline at end of file +1.0.0 \ No newline at end of file diff --git a/example/function/serverless.yml b/example/deploy/serverless.yml similarity index 77% rename from example/function/serverless.yml rename to example/deploy/serverless.yml index 72ec5ebf..403bfd94 100644 --- a/example/function/serverless.yml +++ b/example/deploy/serverless.yml @@ -8,14 +8,12 @@ functions: hello: handler: hello.hello package: - include: - - src/hello.php + artifact: ../output/function.zip layers: ${file(../output/layers.json)} goodbye: handler: goodbye.goodbye package: - include: - - src/goodbye.php + artifact: ../output/function.zip layers: ${file(../output/layers.json)} diff --git a/example/function/template.json b/example/deploy/template.json similarity index 87% rename from example/function/template.json rename to example/deploy/template.json index fa0a1276..ccc01737 100644 --- a/example/function/template.json +++ b/example/deploy/template.json @@ -8,7 +8,7 @@ "FunctionName": "sam-php-example-hello", "Handler": "hello.hello", "Runtime": "provided", - "CodeUri": "./", + "CodeUri": "../output/function.zip", "Layers": "LAYERS_PLACEHOLDER" } }, @@ -18,7 +18,7 @@ "FunctionName": "sam-php-example-goodbye", "Handler": "goodbye.goodbye", "Runtime": "provided", - "CodeUri": "./", + "CodeUri": "../output/function.zip", "Layers": "LAYERS_PLACEHOLDER" } } diff --git a/example/function/template.yaml b/example/deploy/template.yaml similarity index 86% rename from example/function/template.yaml rename to example/deploy/template.yaml index 234932ac..9a3ae383 100644 --- a/example/function/template.yaml +++ b/example/deploy/template.yaml @@ -8,7 +8,7 @@ Resources: FunctionName: sam-php-example-hello Handler: hello.hello Runtime: provided - CodeUri: ./ + CodeUri: ../output/function.zip Layers: LAYERS_PLACEHOLDER Goodbye: @@ -17,5 +17,5 @@ Resources: FunctionName: sam-php-example-goodbye Handler: goodbye.goodbye Runtime: provided - CodeUri: ./ + CodeUri: ../output/function.zip Layers: LAYERS_PLACEHOLDER diff --git a/img2lambda/cli/main.go b/img2lambda/cli/main.go index 1dc238b9..6fae382a 100644 --- a/img2lambda/cli/main.go +++ b/img2lambda/cli/main.go @@ -23,7 +23,7 @@ func createApp() (*cli.App, *types.CmdOptions) { app.EnableBashCompletion = true app.Name = "img2lambda" app.Version = version.VersionString() - app.Usage = "Repackages a container image into AWS Lambda layers and publishes them to Lambda" + app.Usage = "Repackages a container image into an AWS Lambda function deployment package. Extracts AWS Lambda layers from the image and publishes them to Lambda" app.Action = func(c *cli.Context) error { // parse and store the passed runtime list into the options object opts.CompatibleRuntimes = c.StringSlice("cr") @@ -56,7 +56,7 @@ func createApp() (*cli.App, *types.CmdOptions) { }, cli.StringFlag{ Name: "output-directory, o", - Usage: "Destination directory for command output", + Usage: "Destination directory for output: function deployment package (function.zip) and list of published layers (layers.json, layers.yaml)", Value: "./output", Destination: &opts.OutputDir, }, @@ -125,13 +125,18 @@ func repackImageAction(opts *types.CmdOptions, context *cli.Context) error { imageLocation += ":latest" } - layers, err := extract.RepackImage(imageLocation, opts.OutputDir) + layers, function, err := extract.RepackImage(imageLocation, opts.OutputDir) if err != nil { return err } - if len(layers) == 0 { - return errors.New("No compatible layers found in the image (likely nothing found in /opt)") + if function.FileCount == 0 { + // remove empty zip file + os.Remove(function.File) + } + + if len(layers) == 0 && function.FileCount == 0 { + return errors.New("No compatible layers or function files found in the image (likely nothing found in /opt and /var/task)") } if !opts.DryRun { diff --git a/img2lambda/extract/repack_image.go b/img2lambda/extract/repack_image.go index 45abbbf4..414574ba 100644 --- a/img2lambda/extract/repack_image.go +++ b/img2lambda/extract/repack_image.go @@ -23,14 +23,14 @@ import ( "github.com/pkg/errors" ) -// Converts container image to Lambda layer archive files -func RepackImage(imageName string, layerOutputDir string) (layers []types.LambdaLayer, retErr error) { +// Converts container image to Lambda layer and function deployment package archive files +func RepackImage(imageName string, layerOutputDir string) (layers []types.LambdaLayer, function *types.LambdaDeploymentPackage, retErr error) { log.Printf("Parsing the image %s", imageName) // Get image's layer data from image name ref, err := alltransports.ParseImageName(imageName) if err != nil { - return nil, err + return nil, nil, err } sys := &imgtypes.SystemContext{} @@ -53,16 +53,16 @@ func RepackImage(imageName string, layerOutputDir string) (layers []types.Lambda rawSource, err := ref.NewImageSource(ctx, sys) if err != nil { - return nil, err + return nil, nil, err } src, err := image.FromSource(ctx, sys, rawSource) if err != nil { if closeErr := rawSource.Close(); closeErr != nil { - return nil, errors.Wrapf(err, " (close error: %v)", closeErr) + return nil, nil, errors.Wrapf(err, " (close error: %v)", closeErr) } - return nil, err + return nil, nil, err } defer func() { if err := src.Close(); err != nil { @@ -89,17 +89,31 @@ type repackOptions struct { layerOutputDir string } -func repackImage(opts *repackOptions) (layers []types.LambdaLayer, retErr error) { +func repackImage(opts *repackOptions) (layers []types.LambdaLayer, function *types.LambdaDeploymentPackage, retErr error) { layerInfos := opts.imageSource.LayerInfos() log.Printf("Image %s has %d layers", opts.imageName, len(layerInfos)) - // Unpack and inspect each image layer, copy relevant files to new Lambda layer + // Unpack and inspect each image layer, copy relevant files to new Lambda layer or to a Lambda deployment package if err := os.MkdirAll(opts.layerOutputDir, 0777); err != nil { - return nil, err + return nil, nil, err } + function = &types.LambdaDeploymentPackage{FileCount: 0, File: filepath.Join(opts.layerOutputDir, "function.zip")} + functionZip, functionFile, err := startZipFile(function.File) + if err != nil { + return nil, nil, fmt.Errorf("starting zip file: %v", err) + } + defer func() { + if err := functionZip.Close(); err != nil { + retErr = errors.Wrapf(err, " (zip close error: %v)", err) + } + if err := functionFile.Close(); err != nil { + retErr = errors.Wrapf(err, " (file close error: %v)", err) + } + }() + lambdaLayerNum := 1 for _, layerInfo := range layerInfos { @@ -107,28 +121,34 @@ func repackImage(opts *repackOptions) (layers []types.LambdaLayer, retErr error) layerStream, _, err := opts.rawImageSource.GetBlob(opts.ctx, layerInfo, opts.cache) if err != nil { - return nil, err + return nil, function, err } defer layerStream.Close() - fileCreated, err := repackLayer(lambdaLayerFilename, layerStream, false) + layerFileCreated, layerFunctionFileCount, err := repackLayer(lambdaLayerFilename, functionZip, layerStream, false) if err != nil { tarErr := err // tar extraction failed, try tar.gz layerStream, _, err = opts.rawImageSource.GetBlob(opts.ctx, layerInfo, opts.cache) if err != nil { - return nil, err + return nil, function, err } defer layerStream.Close() - fileCreated, err = repackLayer(lambdaLayerFilename, layerStream, true) + layerFileCreated, layerFunctionFileCount, err = repackLayer(lambdaLayerFilename, functionZip, layerStream, true) if err != nil { - return nil, fmt.Errorf("could not read layer with tar nor tar.gz: %v, %v", err, tarErr) + return nil, function, fmt.Errorf("could not read layer with tar nor tar.gz: %v, %v", err, tarErr) } } - if fileCreated { + function.FileCount += layerFunctionFileCount + + if layerFunctionFileCount == 0 { + log.Printf("Did not extract any Lambda function files from image layer %s (no relevant files found)", string(layerInfo.Digest)) + } + + if layerFileCreated { log.Printf("Created Lambda layer file %s from image layer %s", lambdaLayerFilename, string(layerInfo.Digest)) lambdaLayerNum++ layers = append(layers, types.LambdaLayer{Digest: string(layerInfo.Digest), File: lambdaLayerFilename}) @@ -137,15 +157,19 @@ func repackImage(opts *repackOptions) (layers []types.LambdaLayer, retErr error) } } + log.Printf("Extracted %d Lambda function files for image %s", function.FileCount, opts.imageName) + if function.FileCount > 0 { + log.Printf("Created Lambda function deployment package %s", function.File) + } log.Printf("Created %d Lambda layer files for image %s", len(layers), opts.imageName) - return layers, nil + return layers, function, retErr } // Converts container image layer archive (tar) to Lambda layer archive (zip). // Filters files from the source and only writes a new archive if at least // one file in the source matches the filter (i.e. does not create empty archives). -func repackLayer(outputFilename string, layerContents io.Reader, isGzip bool) (created bool, retError error) { +func repackLayer(outputFilename string, functionZip *archiver.Zip, layerContents io.Reader, isGzip bool) (lambdaLayerCreated bool, functionFileCount int, retError error) { t := archiver.NewTar() contentsReader := layerContents var err error @@ -153,7 +177,7 @@ func repackLayer(outputFilename string, layerContents io.Reader, isGzip bool) (c if isGzip { gzr, err := gzip.NewReader(layerContents) if err != nil { - return false, fmt.Errorf("could not create gzip reader for layer: %v", err) + return false, 0, fmt.Errorf("could not create gzip reader for layer: %v", err) } defer gzr.Close() contentsReader = gzr @@ -161,7 +185,7 @@ func repackLayer(outputFilename string, layerContents io.Reader, isGzip bool) (c err = t.Open(contentsReader, 0) if err != nil { - return false, fmt.Errorf("opening layer tar: %v", err) + return false, 0, fmt.Errorf("opening layer tar: %v", err) } defer t.Close() @@ -189,19 +213,19 @@ func repackLayer(outputFilename string, layerContents io.Reader, isGzip bool) (c } if err != nil { - return false, fmt.Errorf("opening next file in layer tar: %v", err) + return false, 0, fmt.Errorf("opening next file in layer tar: %v", err) } - // Determine if this file should be repacked - repack, err := shouldRepackLayerFile(f) + // Determine if this file should be repacked into a Lambda layer + repack, err := shouldRepackLayerFileToLambdaLayer(f) if err != nil { - return false, fmt.Errorf("filtering file in layer tar: %v", err) + return false, 0, fmt.Errorf("filtering file in layer tar: %v", err) } if repack { if z == nil { z, out, err = startZipFile(outputFilename) if err != nil { - return false, fmt.Errorf("starting zip file: %v", err) + return false, 0, fmt.Errorf("starting zip file: %v", err) } } @@ -209,11 +233,25 @@ func repackLayer(outputFilename string, layerContents io.Reader, isGzip bool) (c } if err != nil { - return false, fmt.Errorf("walking %s in layer tar: %v", f.Name(), err) + return false, 0, fmt.Errorf("walking %s in layer tar: %v", f.Name(), err) + } + + // Determine if this file should be repacked into a Lambda function package + repack, err = shouldRepackLayerFileToLambdaFunction(f) + if err != nil { + return false, 0, fmt.Errorf("filtering file in layer tar: %v", err) + } + if repack { + err = repackLayerFile(f, functionZip) + functionFileCount++ + } + + if err != nil { + return false, 0, fmt.Errorf("walking %s in layer tar: %v", f.Name(), err) } } - return (z != nil), nil + return (z != nil), functionFileCount, nil } func startZipFile(destination string) (zip *archiver.Zip, zipFile *os.File, err error) { @@ -232,23 +270,48 @@ func startZipFile(destination string) (zip *archiver.Zip, zipFile *os.File, err return z, out, nil } -func shouldRepackLayerFile(f archiver.File) (should bool, err error) { +func getLayerFileName(f archiver.File) (name string, err error) { header, ok := f.Header.(*tar.Header) if !ok { - return false, fmt.Errorf("expected header to be *tar.Header but was %T", f.Header) + return "", fmt.Errorf("expected header to be *tar.Header but was %T", f.Header) } if f.IsDir() || header.Typeflag == tar.TypeDir { - return false, nil + return "", nil } // Ignore whiteout files if strings.HasPrefix(f.Name(), ".wh.") { + return "", nil + } + + return header.Name, nil +} + +func shouldRepackLayerFileToLambdaLayer(f archiver.File) (should bool, err error) { + filename, err := getLayerFileName(f) + if err != nil { + return false, err + } + if filename == "" { return false, nil } // Only extract files that can be used for Lambda custom runtimes - return zglob.Match("opt/**/**", header.Name) + return zglob.Match("opt/**/**", filename) +} + +func shouldRepackLayerFileToLambdaFunction(f archiver.File) (should bool, err error) { + filename, err := getLayerFileName(f) + if err != nil { + return false, err + } + if filename == "" { + return false, nil + } + + // Only extract files that can be used for Lambda deployment packages + return zglob.Match("var/task/**/**", filename) } func repackLayerFile(f archiver.File, z *archiver.Zip) error { @@ -258,6 +321,7 @@ func repackLayerFile(f archiver.File, z *archiver.Zip) error { } filename := strings.TrimPrefix(filepath.ToSlash(hdr.Name), "opt/") + filename = strings.TrimPrefix(filename, "var/task/") switch hdr.Typeflag { case tar.TypeReg, tar.TypeRegA, tar.TypeChar, tar.TypeBlock, tar.TypeFifo, tar.TypeSymlink, tar.TypeLink: diff --git a/img2lambda/extract/repack_image_test.go b/img2lambda/extract/repack_image_test.go index d453635e..219ecb1f 100644 --- a/img2lambda/extract/repack_image_test.go +++ b/img2lambda/extract/repack_image_test.go @@ -155,6 +155,47 @@ func validateLambdaLayer(t *testing.T, assert.Nil(t, err) } +func validateLambdaDeploymentPackage(t *testing.T, + function *types.LambdaDeploymentPackage, + expectedFilenames []string, + expectedFilesContents []string) { + + z := archiver.NewZip() + + zipFile, err := os.Open(function.File) + assert.Nil(t, err) + zipFileInfo, err := os.Stat(zipFile.Name()) + assert.Nil(t, err) + + err = z.Open(zipFile, zipFileInfo.Size()) + assert.Nil(t, err) + + for i := range expectedFilenames { + contentsFile, err := z.Read() + assert.Nil(t, err) + zfh, ok := contentsFile.Header.(zip.FileHeader) + assert.True(t, ok) + assert.Equal(t, expectedFilenames[i], zfh.Name) + + buf := new(bytes.Buffer) + _, err = buf.ReadFrom(contentsFile.ReadCloser) + assert.Nil(t, err) + contents := buf.String() + assert.Equal(t, expectedFilesContents[i], contents) + } + + _, err = z.Read() + assert.NotNil(t, err) + assert.Equal(t, io.EOF, err) + + err = z.Close() + assert.Nil(t, err) + err = zipFile.Close() + assert.Nil(t, err) + err = os.Remove(zipFile.Name()) + assert.Nil(t, err) +} + func TestRepack(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -181,12 +222,22 @@ func TestRepack(t *testing.T) { blobInfo4 := createImageLayer(t, rawSource, "opt/file1", "hello world 4", "digest4") blobInfos = append(blobInfos, *blobInfo4) + // Function file layers + blobInfo5 := createImageLayer(t, rawSource, "var/task/file1", "hello world 5", "digest5") + blobInfos = append(blobInfos, *blobInfo5) + + blobInfo6 := createImageLayer(t, rawSource, "var/task/file2", "hello world 6", "digest6") + blobInfos = append(blobInfos, *blobInfo6) + + blobInfo7 := createImageLayer(t, rawSource, "var/task/file1", "hello world 7", "digest7") + blobInfos = append(blobInfos, *blobInfo7) + source.EXPECT().LayerInfos().Return(blobInfos) dir, err := ioutil.TempDir("", "") assert.Nil(t, err) - layers, err := repackImage(&repackOptions{ + layers, function, err := repackImage(&repackOptions{ ctx: nil, cache: nil, imageSource: source, @@ -197,11 +248,16 @@ func TestRepack(t *testing.T) { assert.Nil(t, err) assert.Len(t, layers, 3) + assert.Equal(t, 3, function.FileCount) validateLambdaLayer(t, &layers[0], "file1", "hello world 1", "digest1") validateLambdaLayer(t, &layers[1], "hello/file2", "hello world 2", "digest2") validateLambdaLayer(t, &layers[2], "file1", "hello world 4", "digest4") + validateLambdaDeploymentPackage(t, function, + []string{"file1", "file2", "file1"}, + []string{"hello world 5", "hello world 6", "hello world 7"}) + err = os.Remove(dir) assert.Nil(t, err) } @@ -230,7 +286,7 @@ func TestRepackFailure(t *testing.T) { dir, err := ioutil.TempDir("", "") assert.Nil(t, err) - layers, err := repackImage(&repackOptions{ + layers, function, err := repackImage(&repackOptions{ ctx: nil, cache: nil, imageSource: source, @@ -240,9 +296,13 @@ func TestRepackFailure(t *testing.T) { }) assert.Nil(t, layers) + assert.Equal(t, 0, function.FileCount) assert.NotNil(t, err) assert.Equal(t, err.Error(), "could not read layer with tar nor tar.gz: could not create gzip reader for layer: EOF, opening next file in layer tar: unexpected EOF") + err = os.Remove(function.File) + assert.Nil(t, err) + err = os.Remove(dir) assert.Nil(t, err) } diff --git a/img2lambda/types/types.go b/img2lambda/types/types.go index ca72123f..07b839db 100644 --- a/img2lambda/types/types.go +++ b/img2lambda/types/types.go @@ -7,6 +7,11 @@ import ( "github.com/awslabs/aws-lambda-container-image-converter/img2lambda/clients" ) +type LambdaDeploymentPackage struct { + FileCount int + File string +} + type LambdaLayer struct { Digest string File string diff --git a/scripts/build_example.sh b/scripts/build_example.sh index 8971d8d7..756fd7ee 100755 --- a/scripts/build_example.sh +++ b/scripts/build_example.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -e +set -ex # Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 @@ -20,6 +20,15 @@ docker run lambda-php goodbye '{"name": "World"}' ../bin/local/img2lambda -i lambda-php:latest --dry-run -# Look for the 2 layers that contain files in opt/ +# Look for the layers that contain files in opt/ and deployment package that contains files in var/task/ ls output/layer-1.zip ls output/layer-2.zip +ls output/function.zip + +unzip -l output/layer-1.zip | grep '2 files' +unzip -l output/layer-1.zip | grep 'bin/php' +unzip -l output/layer-1.zip | grep 'bootstrap' + +unzip -l output/function.zip | grep '2 files' +unzip -l output/function.zip | grep 'src/hello.php' +unzip -l output/function.zip | grep 'src/goodbye.php'