Skip to content
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
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))
}