Skip to content

Commit 0bfd1ed

Browse files
feat: deploy functions from manifest file (#362)
* feat: deploy functions from manifest file * chore: use `ioutil.WriteFile` * chore: update test * refactor: move functionsManifest to separate file * chore: fix paths in test * chore: fix Windows paths in tests * chore: fix test message * chore: update test messages * refactor: throw error on malformed manifest file * refactor: add error message Co-authored-by: Marcus Weiner <[email protected]> * refactor: add error handling * refactor: wrap error message Co-authored-by: Marcus Weiner <[email protected]>
1 parent 0e796cc commit 0bfd1ed

File tree

5 files changed

+147
-0
lines changed

5 files changed

+147
-0
lines changed
1.91 KB
Binary file not shown.
432 Bytes
Binary file not shown.

go/porcelain/deploy.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,16 @@ func bundle(functionDir string, observer DeployObserver) (*deployFiles, error) {
583583
return nil, nil
584584
}
585585

586+
manifestFile, err := os.Open(filepath.Join(functionDir, "manifest.json"))
587+
588+
// If a `manifest.json` file is found, we extract the functions and their
589+
// metadata from it.
590+
if err == nil {
591+
defer manifestFile.Close()
592+
593+
return bundleFromManifest(manifestFile, observer)
594+
}
595+
586596
functions := newDeployFiles()
587597

588598
info, err := ioutil.ReadDir(functionDir)
@@ -626,6 +636,42 @@ func bundle(functionDir string, observer DeployObserver) (*deployFiles, error) {
626636
return functions, nil
627637
}
628638

639+
func bundleFromManifest(manifestFile *os.File, observer DeployObserver) (*deployFiles, error) {
640+
manifestBytes, err := ioutil.ReadAll(manifestFile)
641+
642+
if err != nil {
643+
return nil, err
644+
}
645+
646+
var manifest functionsManifest
647+
648+
err = json.Unmarshal(manifestBytes, &manifest)
649+
650+
if err != nil {
651+
return nil, fmt.Errorf("malformed functions manifest file: %w", err)
652+
}
653+
654+
functions := newDeployFiles()
655+
656+
for _, function := range manifest.Functions {
657+
fileInfo, err := os.Stat(function.Path)
658+
659+
if err != nil {
660+
return nil, fmt.Errorf("manifest file specifies a function path that cannot be found: %s", function.Path)
661+
}
662+
663+
file, err := newFunctionFile(function.Path, fileInfo, function.Runtime, observer)
664+
665+
if err != nil {
666+
return nil, err
667+
}
668+
669+
functions.Add(file.Name, file)
670+
}
671+
672+
return functions, nil
673+
}
674+
629675
func readZipRuntime(filePath string) (string, error) {
630676
zf, err := zip.OpenReader(filePath)
631677
if err != nil {

go/porcelain/deploy_test.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ package porcelain
33
import (
44
"bytes"
55
gocontext "context"
6+
"fmt"
67
"io/ioutil"
78
"net/http"
89
"net/http/httptest"
910
"net/url"
1011
"os"
12+
"path"
1113
"path/filepath"
1214
"strings"
1315
"testing"
@@ -200,6 +202,91 @@ func TestUploadFiles_Cancelation(t *testing.T) {
200202
require.ErrorIs(t, err, gocontext.Canceled)
201203
}
202204

205+
func TestBundle(t *testing.T) {
206+
functions, err := bundle("../internal/data", mockObserver{})
207+
208+
if err != nil {
209+
t.Fatalf("unexpected error bundling functions: %v", err)
210+
}
211+
212+
if len(functions.Files) != 3 {
213+
t.Fatalf("unexpected number of functions, expected=3, got=%d", len(functions.Files))
214+
}
215+
216+
jsFunction := functions.Files["hello-js-function-test"]
217+
pyFunction := functions.Files["hello-py-function-test"]
218+
rsFunction := functions.Files["hello-rs-function-test"]
219+
220+
if jsFunction.Runtime != "js" {
221+
t.Fatalf("unexpected runtime, expected='js', got='%v'", jsFunction.Runtime)
222+
}
223+
224+
if pyFunction.Runtime != "py" {
225+
t.Fatalf("unexpected runtime, expected='py', got='%v'", pyFunction.Runtime)
226+
}
227+
228+
if rsFunction.Runtime != "rs" {
229+
t.Fatalf("unexpected runtime, expected='rs', got='%v'", rsFunction.Runtime)
230+
}
231+
}
232+
233+
func TestBundleWithManifest(t *testing.T) {
234+
cwd, _ := os.Getwd()
235+
basePath := path.Join(filepath.Dir(cwd), "internal", "data")
236+
jsFunctionPath := strings.Replace(filepath.Join(basePath, "hello-js-function-test.zip"), "\\", "/", -1)
237+
pyFunctionPath := strings.Replace(filepath.Join(basePath, "hello-py-function-test.zip"), "\\", "/", -1)
238+
manifestPath := path.Join(basePath, "manifest.json")
239+
manifestFile := fmt.Sprintf(`{
240+
"functions": [
241+
{
242+
"path": "%s",
243+
"runtime": "a-runtime",
244+
"mainFile": "/some/path/hello-js-function-test.js",
245+
"name": "hello-js-function-test"
246+
},
247+
{
248+
"path": "%s",
249+
"runtime": "some-other-runtime",
250+
"mainFile": "/some/path/hello-py-function-test",
251+
"name": "hello-py-function-test"
252+
}
253+
],
254+
"version": 1
255+
}`, jsFunctionPath, pyFunctionPath)
256+
257+
err := ioutil.WriteFile(manifestPath, []byte(manifestFile), 0644)
258+
defer os.Remove(manifestPath)
259+
260+
if err != nil {
261+
t.Fatal("could not create manifest file")
262+
}
263+
264+
functions, err := bundle("../internal/data", mockObserver{})
265+
266+
t.Log(functions)
267+
268+
if err != nil {
269+
t.Fatalf("unexpected error bundling functions: %v", err)
270+
}
271+
272+
if len(functions.Files) != 2 {
273+
t.Fatalf("unexpected number of functions, expected=2, got=%d", len(functions.Files))
274+
}
275+
276+
jsFunction := functions.Files["hello-js-function-test"]
277+
expectedJsFunctionRuntime := "a-runtime"
278+
pyFunction := functions.Files["hello-py-function-test"]
279+
expectedPyFunctionRuntime := "some-other-runtime"
280+
281+
if jsFunction.Runtime != expectedJsFunctionRuntime {
282+
t.Fatalf("unexpected runtime, expected='%s', got='%v'", expectedJsFunctionRuntime, jsFunction.Runtime)
283+
}
284+
285+
if pyFunction.Runtime != expectedPyFunctionRuntime {
286+
t.Fatalf("unexpected runtime, expected='%s', got='%v'", expectedPyFunctionRuntime, pyFunction.Runtime)
287+
}
288+
}
289+
203290
func TestReadZipRuntime(t *testing.T) {
204291
runtime, err := readZipRuntime("../internal/data/hello-rs-function-test.zip")
205292
if err != nil {

go/porcelain/functions_manifest.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package porcelain
2+
3+
// https://github.com/netlify/zip-it-and-ship-it/blob/main/src/manifest.ts
4+
type functionsManifest struct {
5+
Functions []functionsManifestEntry `json:"functions"`
6+
Version int `json:"version"`
7+
}
8+
9+
type functionsManifestEntry struct {
10+
MainFile string `json:"mainFile"`
11+
Name string `json:"name"`
12+
Path string `json:"path"`
13+
Runtime string `json:"runtime"`
14+
}

0 commit comments

Comments
 (0)