Skip to content

Commit 0b4291a

Browse files
authored
cmd/build-lambda-zip: Produce valid .zips for provided runtime functions (#307)
* cmd/build-lambda-zip: Produce valid .zips for provided runtime functions * Update main.go * Update main_test.go
1 parent 1d109eb commit 0b4291a

File tree

4 files changed

+214
-0
lines changed

4 files changed

+214
-0
lines changed

cmd/build-lambda-zip/main.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved
2+
13
package main
24

35
import (
46
"archive/zip"
57
"errors"
68
"fmt"
79
"io/ioutil"
10+
"log"
811
"os"
912
"path/filepath"
1013

@@ -37,6 +40,7 @@ func main() {
3740
if err := compressExeAndArgs(outputZip, inputExe, c.Args().Tail()); err != nil {
3841
return fmt.Errorf("failed to compress file: %v", err)
3942
}
43+
log.Print("wrote " + outputZip)
4044
return nil
4145
},
4246
}
@@ -48,6 +52,18 @@ func main() {
4852
}
4953

5054
func writeExe(writer *zip.Writer, pathInZip string, data []byte) error {
55+
if pathInZip != "bootstrap" {
56+
header := &zip.FileHeader{Name: "bootstrap", Method: zip.Deflate}
57+
header.SetMode(0755 | os.ModeSymlink)
58+
link, err := writer.CreateHeader(header)
59+
if err != nil {
60+
return err
61+
}
62+
if _, err := link.Write([]byte(pathInZip)); err != nil {
63+
return err
64+
}
65+
}
66+
5167
exe, err := writer.CreateHeader(&zip.FileHeader{
5268
CreatorVersion: 3 << 8, // indicates Unix
5369
ExternalAttrs: 0777 << 16, // -rwxrwxrwx file permissions

cmd/build-lambda-zip/main_test.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved
2+
3+
package main
4+
5+
import (
6+
"archive/zip"
7+
"fmt"
8+
"io/ioutil"
9+
"os"
10+
"os/exec"
11+
"path"
12+
"path/filepath"
13+
"testing"
14+
15+
"github.com/stretchr/testify/assert"
16+
"github.com/stretchr/testify/require"
17+
)
18+
19+
func TestSizes(t *testing.T) {
20+
if testing.Short() {
21+
t.Skip()
22+
return
23+
}
24+
t.Log("test how different arguments affect binary and archive sizes")
25+
cases := []struct {
26+
file string
27+
args []string
28+
}{
29+
{"testdata/apigw.go", nil},
30+
{"testdata/noop.go", nil},
31+
{"testdata/noop.go", []string{"-tags", "lambda.norpc"}},
32+
{"testdata/noop.go", []string{"-ldflags=-s -w"}},
33+
{"testdata/noop.go", []string{"-tags", "lambda.norpc", "-ldflags=-s -w"}},
34+
}
35+
testDir, err := os.Getwd()
36+
require.NoError(t, err)
37+
tempDir, err := ioutil.TempDir("/tmp", "build-lambda-zip")
38+
require.NoError(t, err)
39+
for _, test := range cases {
40+
require.NoError(t, os.Chdir(testDir))
41+
testName := fmt.Sprintf("%s, %v", test.file, test.args)
42+
t.Run(testName, func(t *testing.T) {
43+
binPath := path.Join(tempDir, test.file+".bin")
44+
zipPath := path.Join(tempDir, test.file+".zip")
45+
46+
buildArgs := []string{"build", "-o", binPath}
47+
buildArgs = append(buildArgs, test.args...)
48+
buildArgs = append(buildArgs, test.file)
49+
50+
gocmd := exec.Command("go", buildArgs...)
51+
gocmd.Env = append(os.Environ(), "GOOS=linux")
52+
gocmd.Stderr = os.Stderr
53+
require.NoError(t, gocmd.Run())
54+
require.NoError(t, os.Chdir(filepath.Dir(binPath)))
55+
require.NoError(t, compressExeAndArgs(zipPath, binPath, []string{}))
56+
57+
binInfo, err := os.Stat(binPath)
58+
require.NoError(t, err)
59+
zipInfo, err := os.Stat(zipPath)
60+
require.NoError(t, err)
61+
62+
t.Logf("zip size = %d Kb, bin size = %d Kb", zipInfo.Size()/1024, binInfo.Size()/1024)
63+
})
64+
}
65+
66+
}
67+
68+
func TestCompressExeAndArgs(t *testing.T) {
69+
tempDir, err := ioutil.TempDir("/tmp", "build-lambda-zip")
70+
require.NoError(t, err)
71+
defer os.RemoveAll(tempDir)
72+
73+
fileNames := []string{"the-exe", "the-config", "other-config"}
74+
filePaths := make([]string, len(fileNames))
75+
for i, fileName := range fileNames {
76+
filePaths[i] = filepath.Join(tempDir, fileName)
77+
f, err := os.Create(filePaths[i])
78+
require.NoError(t, err)
79+
_, _ = fmt.Fprintf(f, "Hello file %d!", i)
80+
err = f.Close()
81+
require.NoError(t, err)
82+
}
83+
outZipPath := filepath.Join(tempDir, "lambda.zip")
84+
85+
err = compressExeAndArgs(outZipPath, filePaths[0], filePaths[1:])
86+
require.NoError(t, err)
87+
88+
t.Run("handler exe configured in zip root", func(t *testing.T) {
89+
require.NotEqual(t, filePaths[0], filepath.Base(filePaths[0]), "test precondition")
90+
zipReader, err := zip.OpenReader(outZipPath)
91+
require.NoError(t, err)
92+
defer zipReader.Close()
93+
for _, zf := range zipReader.File {
94+
if zf.Name == filepath.Base(filePaths[0]) {
95+
assert.True(t, zf.FileInfo().Mode().IsRegular())
96+
permissions := int(zf.FileInfo().Mode() & 0777)
97+
assert.Equal(t, 0111, permissions&0111, "file permissions: %#o, aren't executable", permissions)
98+
assert.Equal(t, 0444, permissions&0444, "file permissions: %#o, aren't readable", permissions)
99+
return
100+
}
101+
}
102+
t.Fatalf("failed to find handler exe in zip")
103+
})
104+
105+
t.Run("boostrap is a symlink to handler exe", func(t *testing.T) {
106+
zipReader, err := zip.OpenReader(outZipPath)
107+
require.NoError(t, err)
108+
defer zipReader.Close()
109+
var bootstrap *zip.File
110+
for _, f := range zipReader.File {
111+
if f.Name == "bootstrap" {
112+
bootstrap = f
113+
}
114+
}
115+
require.NotNil(t, bootstrap)
116+
assert.Equal(t, 0755|os.ModeSymlink, bootstrap.FileInfo().Mode())
117+
link, err := bootstrap.Open()
118+
require.NoError(t, err)
119+
defer link.Close()
120+
linkTarget, err := ioutil.ReadAll(link)
121+
require.NoError(t, err)
122+
assert.Equal(t, filepath.Base(filePaths[0]), string(linkTarget))
123+
})
124+
125+
t.Run("resource file paths", func(t *testing.T) {
126+
zipReader, err := zip.OpenReader(outZipPath)
127+
require.NoError(t, err)
128+
defer zipReader.Close()
129+
eachFile:
130+
for _, path := range filePaths[1:] {
131+
for _, zipFileEntry := range zipReader.File {
132+
if zipFileEntry.Name == path {
133+
continue eachFile
134+
}
135+
}
136+
t.Logf("failed to find resource file %s in zip", path)
137+
t.Fail()
138+
}
139+
})
140+
141+
t.Run("file contents match", func(t *testing.T) {
142+
zipReader, err := zip.OpenReader(outZipPath)
143+
require.NoError(t, err)
144+
defer zipReader.Close()
145+
expectedIndex := 0
146+
for _, zf := range zipReader.File {
147+
if zf.FileInfo().Mode().IsRegular() {
148+
f, err := zf.Open()
149+
require.NoError(t, err)
150+
defer f.Close()
151+
content, err := ioutil.ReadAll(f)
152+
require.NoError(t, err)
153+
assert.Equal(t, fmt.Sprintf("Hello file %d!", expectedIndex), string(content), "in file: %s", zf.Name)
154+
expectedIndex++
155+
}
156+
}
157+
})
158+
159+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved
2+
3+
package main
4+
5+
import (
6+
"context"
7+
"fmt"
8+
"github.com/aws/aws-lambda-go/events"
9+
"github.com/aws/aws-lambda-go/lambda"
10+
)
11+
12+
func main () {
13+
lambda.Start(func (ctx context.Context, event events.APIGatewayV2HTTPRequest) (*events.APIGatewayProxyResponse, error) {
14+
return &events.APIGatewayProxyResponse{
15+
Body: fmt.Sprintf("Hello %v", event),
16+
}, nil
17+
})
18+
}

cmd/build-lambda-zip/testdata/noop.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved
2+
3+
package main
4+
5+
import (
6+
"context"
7+
"github.com/aws/aws-lambda-go/lambda"
8+
)
9+
10+
type BinaryHandler func(context.Context, []byte) ([]byte, error)
11+
func (bh BinaryHandler) Invoke(ctx context.Context, req []byte) ([]byte, error) {
12+
return bh(ctx, req)
13+
}
14+
15+
func noop (ctx context.Context, req []byte) ([]byte, error) {
16+
return req, nil
17+
}
18+
19+
func main() {
20+
lambda.StartHandler(BinaryHandler(noop))
21+
}

0 commit comments

Comments
 (0)