Skip to content

Commit

Permalink
cmd/build-lambda-zip: Produce valid .zips for provided runtime functi…
Browse files Browse the repository at this point in the history
…ons (#307)

* cmd/build-lambda-zip: Produce valid .zips for provided runtime functions

* Update main.go

* Update main_test.go
  • Loading branch information
bmoffatt committed Jul 15, 2020
1 parent 1d109eb commit 0b4291a
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 0 deletions.
16 changes: 16 additions & 0 deletions cmd/build-lambda-zip/main.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved

package main

import (
"archive/zip"
"errors"
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"

Expand Down Expand Up @@ -37,6 +40,7 @@ func main() {
if err := compressExeAndArgs(outputZip, inputExe, c.Args().Tail()); err != nil {
return fmt.Errorf("failed to compress file: %v", err)
}
log.Print("wrote " + outputZip)
return nil
},
}
Expand All @@ -48,6 +52,18 @@ func main() {
}

func writeExe(writer *zip.Writer, pathInZip string, data []byte) error {
if pathInZip != "bootstrap" {
header := &zip.FileHeader{Name: "bootstrap", Method: zip.Deflate}
header.SetMode(0755 | os.ModeSymlink)
link, err := writer.CreateHeader(header)
if err != nil {
return err
}
if _, err := link.Write([]byte(pathInZip)); err != nil {
return err
}
}

exe, err := writer.CreateHeader(&zip.FileHeader{
CreatorVersion: 3 << 8, // indicates Unix
ExternalAttrs: 0777 << 16, // -rwxrwxrwx file permissions
Expand Down
159 changes: 159 additions & 0 deletions cmd/build-lambda-zip/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved

package main

import (
"archive/zip"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestSizes(t *testing.T) {
if testing.Short() {
t.Skip()
return
}
t.Log("test how different arguments affect binary and archive sizes")
cases := []struct {
file string
args []string
}{
{"testdata/apigw.go", nil},
{"testdata/noop.go", nil},
{"testdata/noop.go", []string{"-tags", "lambda.norpc"}},
{"testdata/noop.go", []string{"-ldflags=-s -w"}},
{"testdata/noop.go", []string{"-tags", "lambda.norpc", "-ldflags=-s -w"}},
}
testDir, err := os.Getwd()
require.NoError(t, err)
tempDir, err := ioutil.TempDir("/tmp", "build-lambda-zip")
require.NoError(t, err)
for _, test := range cases {
require.NoError(t, os.Chdir(testDir))
testName := fmt.Sprintf("%s, %v", test.file, test.args)
t.Run(testName, func(t *testing.T) {
binPath := path.Join(tempDir, test.file+".bin")
zipPath := path.Join(tempDir, test.file+".zip")

buildArgs := []string{"build", "-o", binPath}
buildArgs = append(buildArgs, test.args...)
buildArgs = append(buildArgs, test.file)

gocmd := exec.Command("go", buildArgs...)
gocmd.Env = append(os.Environ(), "GOOS=linux")
gocmd.Stderr = os.Stderr
require.NoError(t, gocmd.Run())
require.NoError(t, os.Chdir(filepath.Dir(binPath)))
require.NoError(t, compressExeAndArgs(zipPath, binPath, []string{}))

binInfo, err := os.Stat(binPath)
require.NoError(t, err)
zipInfo, err := os.Stat(zipPath)
require.NoError(t, err)

t.Logf("zip size = %d Kb, bin size = %d Kb", zipInfo.Size()/1024, binInfo.Size()/1024)
})
}

}

func TestCompressExeAndArgs(t *testing.T) {
tempDir, err := ioutil.TempDir("/tmp", "build-lambda-zip")
require.NoError(t, err)
defer os.RemoveAll(tempDir)

fileNames := []string{"the-exe", "the-config", "other-config"}
filePaths := make([]string, len(fileNames))
for i, fileName := range fileNames {
filePaths[i] = filepath.Join(tempDir, fileName)
f, err := os.Create(filePaths[i])
require.NoError(t, err)
_, _ = fmt.Fprintf(f, "Hello file %d!", i)
err = f.Close()
require.NoError(t, err)
}
outZipPath := filepath.Join(tempDir, "lambda.zip")

err = compressExeAndArgs(outZipPath, filePaths[0], filePaths[1:])
require.NoError(t, err)

t.Run("handler exe configured in zip root", func(t *testing.T) {
require.NotEqual(t, filePaths[0], filepath.Base(filePaths[0]), "test precondition")
zipReader, err := zip.OpenReader(outZipPath)
require.NoError(t, err)
defer zipReader.Close()
for _, zf := range zipReader.File {
if zf.Name == filepath.Base(filePaths[0]) {
assert.True(t, zf.FileInfo().Mode().IsRegular())
permissions := int(zf.FileInfo().Mode() & 0777)
assert.Equal(t, 0111, permissions&0111, "file permissions: %#o, aren't executable", permissions)
assert.Equal(t, 0444, permissions&0444, "file permissions: %#o, aren't readable", permissions)
return
}
}
t.Fatalf("failed to find handler exe in zip")
})

t.Run("boostrap is a symlink to handler exe", func(t *testing.T) {
zipReader, err := zip.OpenReader(outZipPath)
require.NoError(t, err)
defer zipReader.Close()
var bootstrap *zip.File
for _, f := range zipReader.File {
if f.Name == "bootstrap" {
bootstrap = f
}
}
require.NotNil(t, bootstrap)
assert.Equal(t, 0755|os.ModeSymlink, bootstrap.FileInfo().Mode())
link, err := bootstrap.Open()
require.NoError(t, err)
defer link.Close()
linkTarget, err := ioutil.ReadAll(link)
require.NoError(t, err)
assert.Equal(t, filepath.Base(filePaths[0]), string(linkTarget))
})

t.Run("resource file paths", func(t *testing.T) {
zipReader, err := zip.OpenReader(outZipPath)
require.NoError(t, err)
defer zipReader.Close()
eachFile:
for _, path := range filePaths[1:] {
for _, zipFileEntry := range zipReader.File {
if zipFileEntry.Name == path {
continue eachFile
}
}
t.Logf("failed to find resource file %s in zip", path)
t.Fail()
}
})

t.Run("file contents match", func(t *testing.T) {
zipReader, err := zip.OpenReader(outZipPath)
require.NoError(t, err)
defer zipReader.Close()
expectedIndex := 0
for _, zf := range zipReader.File {
if zf.FileInfo().Mode().IsRegular() {
f, err := zf.Open()
require.NoError(t, err)
defer f.Close()
content, err := ioutil.ReadAll(f)
require.NoError(t, err)
assert.Equal(t, fmt.Sprintf("Hello file %d!", expectedIndex), string(content), "in file: %s", zf.Name)
expectedIndex++
}
}
})

}
18 changes: 18 additions & 0 deletions cmd/build-lambda-zip/testdata/apigw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved

package main

import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)

func main () {
lambda.Start(func (ctx context.Context, event events.APIGatewayV2HTTPRequest) (*events.APIGatewayProxyResponse, error) {
return &events.APIGatewayProxyResponse{
Body: fmt.Sprintf("Hello %v", event),
}, nil
})
}
21 changes: 21 additions & 0 deletions cmd/build-lambda-zip/testdata/noop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved

package main

import (
"context"
"github.com/aws/aws-lambda-go/lambda"
)

type BinaryHandler func(context.Context, []byte) ([]byte, error)
func (bh BinaryHandler) Invoke(ctx context.Context, req []byte) ([]byte, error) {
return bh(ctx, req)
}

func noop (ctx context.Context, req []byte) ([]byte, error) {
return req, nil
}

func main() {
lambda.StartHandler(BinaryHandler(noop))
}

0 comments on commit 0b4291a

Please sign in to comment.