From 0b0a6abac64dad11e509425e352f67512b85bdea Mon Sep 17 00:00:00 2001 From: Sophie Wigmore Date: Tue, 1 Sep 2020 10:48:38 -0400 Subject: [PATCH] Implementation of RFC 0001 (#6) * Bootsrapped files and failing integration tests * working detect Signed-off-by: Arjun Sreedharan * build executable Signed-off-by: Arjun Sreedharan * Add tests for graceful shutdown Update README and CODEOWNERS Signed-off-by: Sophie Wigmore Co-authored-by: Arjun Sreedharan --- .github/.syncignore | 1 + .github/CODEOWNERS | 1 + .gitignore | 2 + .packit | 0 README.md | 19 ++ build.go | 28 +++ build_test.go | 80 +++++++++ buildpack.toml | 15 ++ constants.go | 8 + detect.go | 52 ++++++ detect_test.go | 103 +++++++++++ go.mod | 12 ++ go.sum | 162 ++++++++++++++++++ init_test.go | 15 ++ integration.json | 6 + integration/custom_start_cmd_test.go | 96 +++++++++++ integration/default_test.go | 92 ++++++++++ integration/graceful_shutdown_test.go | 114 ++++++++++++ integration/init_test.go | 91 ++++++++++ .../testdata/custom_start_cmd_app/.gitignore | 1 + .../testdata/custom_start_cmd_app/README.md | 1 + .../custom_start_cmd_app/package.json | 19 ++ .../testdata/custom_start_cmd_app/server.js | 17 ++ .../testdata/custom_start_cmd_app/yarn.lock | 8 + integration/testdata/default_app/.gitignore | 1 + integration/testdata/default_app/README.md | 1 + integration/testdata/default_app/package.json | 18 ++ integration/testdata/default_app/server.js | 17 ++ integration/testdata/default_app/yarn.lock | 8 + .../graceful_shutdown_app/package.json | 21 +++ .../testdata/graceful_shutdown_app/server.js | 21 +++ .../testdata/graceful_shutdown_app/yarn.lock | 8 + package.toml | 2 + run/main.go | 18 ++ 34 files changed, 1058 insertions(+) create mode 100644 .github/.syncignore create mode 100644 .github/CODEOWNERS create mode 100644 .gitignore create mode 100644 .packit create mode 100644 build.go create mode 100644 build_test.go create mode 100644 buildpack.toml create mode 100644 constants.go create mode 100644 detect.go create mode 100644 detect_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 init_test.go create mode 100644 integration.json create mode 100644 integration/custom_start_cmd_test.go create mode 100644 integration/default_test.go create mode 100644 integration/graceful_shutdown_test.go create mode 100644 integration/init_test.go create mode 100644 integration/testdata/custom_start_cmd_app/.gitignore create mode 100644 integration/testdata/custom_start_cmd_app/README.md create mode 100644 integration/testdata/custom_start_cmd_app/package.json create mode 100644 integration/testdata/custom_start_cmd_app/server.js create mode 100644 integration/testdata/custom_start_cmd_app/yarn.lock create mode 100644 integration/testdata/default_app/.gitignore create mode 100644 integration/testdata/default_app/README.md create mode 100644 integration/testdata/default_app/package.json create mode 100644 integration/testdata/default_app/server.js create mode 100644 integration/testdata/default_app/yarn.lock create mode 100644 integration/testdata/graceful_shutdown_app/package.json create mode 100644 integration/testdata/graceful_shutdown_app/server.js create mode 100644 integration/testdata/graceful_shutdown_app/yarn.lock create mode 100644 package.toml create mode 100644 run/main.go diff --git a/.github/.syncignore b/.github/.syncignore new file mode 100644 index 0000000..7c0127a --- /dev/null +++ b/.github/.syncignore @@ -0,0 +1 @@ +CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..01749b1 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @paketo-buildpacks/nodejs-maintainers diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a249166 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/.bin +/.build diff --git a/.packit b/.packit new file mode 100644 index 0000000..e69de29 diff --git a/README.md b/README.md index 7882773..a343131 100644 --- a/README.md +++ b/README.md @@ -1 +1,20 @@ # Yarn Start Cloud Native Buildpack + +## `gcr.io/paketo-community/yarn-install` + +The Yarn Start CNB sets the start command for the given application. + +## Integration + +This CNB writes a command, so there's currently no scenario we can +imagine that you would need to require it as dependency. + +To package this buildpack for consumption: +``` +$ ./scripts/package.sh +``` +This builds the buildpack's source using GOOS=linux by default. You can supply another value as the first argument to package.sh. + +## `buildpack.yml` Configurations + +There are no extra configurations for this buildpack based on `buildpack.yml`. diff --git a/build.go b/build.go new file mode 100644 index 0000000..d2145f6 --- /dev/null +++ b/build.go @@ -0,0 +1,28 @@ +package yarnstart + +import ( + "github.com/paketo-buildpacks/packit" + "github.com/paketo-buildpacks/packit/scribe" +) + +func Build(logger scribe.Logger) packit.BuildFunc { + return func(context packit.BuildContext) (packit.BuildResult, error) { + logger.Title("%s %s", context.BuildpackInfo.Name, context.BuildpackInfo.Version) + + logger.Process("Writing start command") + command := "tini -g -- yarn start" + logger.Subprocess(command) + + return packit.BuildResult{ + Plan: packit.BuildpackPlan{ + Entries: []packit.BuildpackPlanEntry{}, + }, + Processes: []packit.Process{ + { + Type: "web", + Command: command, + }, + }, + }, nil + } +} diff --git a/build_test.go b/build_test.go new file mode 100644 index 0000000..c6c55ae --- /dev/null +++ b/build_test.go @@ -0,0 +1,80 @@ +package yarnstart_test + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + + "github.com/paketo-buildpacks/packit" + "github.com/paketo-buildpacks/packit/scribe" + yarnstart "github.com/paketo-buildpacks/yarn-start" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" +) + +func testBuild(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + layersDir string + workingDir string + cnbDir string + buffer *bytes.Buffer + + build packit.BuildFunc + ) + + it.Before(func() { + var err error + layersDir, err = ioutil.TempDir("", "layers") + Expect(err).NotTo(HaveOccurred()) + + cnbDir, err = ioutil.TempDir("", "cnb") + Expect(err).NotTo(HaveOccurred()) + + workingDir, err = ioutil.TempDir("", "working-dir") + Expect(err).NotTo(HaveOccurred()) + + buffer = bytes.NewBuffer(nil) + logger := scribe.NewLogger(buffer) + + build = yarnstart.Build(logger) + }) + + it.After(func() { + Expect(os.RemoveAll(layersDir)).To(Succeed()) + Expect(os.RemoveAll(cnbDir)).To(Succeed()) + Expect(os.RemoveAll(workingDir)).To(Succeed()) + }) + + it("returns a result that builds correctly", func() { + result, err := build(packit.BuildContext{ + WorkingDir: workingDir, + CNBPath: cnbDir, + Stack: "some-stack", + BuildpackInfo: packit.BuildpackInfo{ + Name: "Some Buildpack", + Version: "some-version", + }, + Plan: packit.BuildpackPlan{ + Entries: []packit.BuildpackPlanEntry{}, + }, + Layers: packit.Layers{Path: layersDir}, + }) + Expect(err).NotTo(HaveOccurred()) + + Expect(result).To(Equal(packit.BuildResult{ + Plan: packit.BuildpackPlan{ + Entries: []packit.BuildpackPlanEntry{}, + }, + Processes: []packit.Process{ + { + Type: "web", + Command: "tini -g -- yarn start", + }, + }, + })) + }) +} diff --git a/buildpack.toml b/buildpack.toml new file mode 100644 index 0000000..9cc8288 --- /dev/null +++ b/buildpack.toml @@ -0,0 +1,15 @@ +api = "0.2" + +[buildpack] + id = "paketo-buildpacks/yarn-start" + name = "Yarn-Start Buildpack" + +[[stacks]] + id = "io.buildpacks.stacks.bionic" + +[[stacks]] + id = "org.cloudfoundry.stacks.cflinuxfs3" + +[metadata] + include_files = ["bin/run","bin/build","bin/detect","buildpack.toml"] + pre_package = "./scripts/build.sh" diff --git a/constants.go b/constants.go new file mode 100644 index 0000000..9be323d --- /dev/null +++ b/constants.go @@ -0,0 +1,8 @@ +package yarnstart + +const ( + Node = "node" + NodeModules = "node_modules" + Tini = "tini" + Yarn = "yarn" +) diff --git a/detect.go b/detect.go new file mode 100644 index 0000000..d6abb27 --- /dev/null +++ b/detect.go @@ -0,0 +1,52 @@ +package yarnstart + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/paketo-buildpacks/packit" +) + +func Detect() packit.DetectFunc { + return func(context packit.DetectContext) (packit.DetectResult, error) { + _, err := os.Stat(filepath.Join(context.WorkingDir, "yarn.lock")) + if err != nil { + if os.IsNotExist(err) { + return packit.DetectResult{}, packit.Fail + } + return packit.DetectResult{}, fmt.Errorf("failed to stat yarn.lock: %w", err) + } + + return packit.DetectResult{ + Plan: packit.BuildPlan{ + Requires: []packit.BuildPlanRequirement{ + { + Name: Node, + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + { + Name: NodeModules, + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + { + Name: Yarn, + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + { + Name: Tini, + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + }, + }, + }, nil + } +} diff --git a/detect_test.go b/detect_test.go new file mode 100644 index 0000000..bcd1360 --- /dev/null +++ b/detect_test.go @@ -0,0 +1,103 @@ +package yarnstart_test + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "github.com/paketo-buildpacks/packit" + yarnstart "github.com/paketo-buildpacks/yarn-start" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" +) + +func testDetect(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + workingDir string + detect packit.DetectFunc + ) + + it.Before(func() { + var err error + workingDir, err = ioutil.TempDir("", "working-dir") + Expect(err).NotTo(HaveOccurred()) + + detect = yarnstart.Detect() + }) + + it.After(func() { + Expect(os.RemoveAll(workingDir)).To(Succeed()) + }) + + context("when there is a yarn.lock", func() { + it.Before(func() { + Expect(ioutil.WriteFile(filepath.Join(workingDir, "yarn.lock"), nil, 0644)).To(Succeed()) + }) + it("detects", func() { + result, err := detect(packit.DetectContext{ + WorkingDir: workingDir, + }) + Expect(err).NotTo(HaveOccurred()) + Expect(result.Plan).To(Equal(packit.BuildPlan{ + Requires: []packit.BuildPlanRequirement{ + { + Name: "node", + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + { + Name: "node_modules", + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + { + Name: "yarn", + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + { + Name: "tini", + Metadata: map[string]interface{}{ + "launch": true, + }, + }, + }, + })) + }) + }) + + context("when there is no yarn.lock", func() { + it("fails detection", func() { + _, err := detect(packit.DetectContext{ + WorkingDir: workingDir, + }) + Expect(err).To(MatchError(packit.Fail)) + }) + }) + + context("failure cases", func() { + context("the workspace directory cannot be accessed", func() { + it.Before(func() { + Expect(os.Chmod(workingDir, 0000)).To(Succeed()) + }) + + it.After(func() { + Expect(os.Chmod(workingDir, os.ModePerm)).To(Succeed()) + }) + + it("returns an error", func() { + _, err := detect(packit.DetectContext{ + WorkingDir: workingDir, + }) + Expect(err).To(MatchError(ContainSubstring("failed to stat yarn.lock:"))) + }) + }) + }) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6dad004 --- /dev/null +++ b/go.mod @@ -0,0 +1,12 @@ +module github.com/paketo-buildpacks/yarn-start + +go 1.14 + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/onsi/gomega v1.10.1 + github.com/paketo-buildpacks/occam v0.0.17 + github.com/paketo-buildpacks/packit v0.2.6 + github.com/sclevine/spec v1.4.0 + golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dcf69e8 --- /dev/null +++ b/go.sum @@ -0,0 +1,162 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/ForestEckhardt/freezer v0.0.1 h1:SH2R9RiIh/0HdCcIymdF/sG2YJuOtf94THPF7L52HBA= +github.com/ForestEckhardt/freezer v0.0.1/go.mod h1:Tp5MaiHIfM9TOqTFYlqHsittmOnFGsUcUVDX+AtbNrI= +github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= +github.com/VividCortex/ewma v1.1.1 h1:MnEK4VOv6n0RSY4vtRe3h11qjxL3+t0B8yOL8iMXdcM= +github.com/VividCortex/ewma v1.1.1/go.mod h1:2Tkkvm3sRDVXaiyucHiACn4cqf7DpdyLvmxzcbUokwA= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cheggaaa/pb/v3 v3.0.4/go.mod h1:7rgWxLrAUcFMkvJuv09+DYi7mMUYi8nO9iOWcvGJPfw= +github.com/cheggaaa/pb/v3 v3.0.5 h1:lmZOti7CraK9RSjzExsY53+WWfub9Qv13B5m4ptEoPE= +github.com/cheggaaa/pb/v3 v3.0.5/go.mod h1:X1L61/+36nz9bjIsrDU52qHKOQukUQe2Ge+YvGuquCw= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw= +github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.13.0 h1:M76yO2HkZASFjXL0HSoZJ1AYEmQxNJmY41Jx1zNUq1Y= +github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/paketo-buildpacks/occam v0.0.17 h1:djTYtqfREwbTy+KlwscOUjN9ZIPld1uP3sTw4Fdr0L0= +github.com/paketo-buildpacks/occam v0.0.17/go.mod h1:9TFuRmC3OB8DlaBdd0hGPZQidnsmGmUlnLQTrltZB5Q= +github.com/paketo-buildpacks/packit v0.1.0/go.mod h1:b40wtWWAcgB47+vYGDD9KKhzOtBjI8KPqGxwGvV+XNs= +github.com/paketo-buildpacks/packit v0.2.2/go.mod h1:b40wtWWAcgB47+vYGDD9KKhzOtBjI8KPqGxwGvV+XNs= +github.com/paketo-buildpacks/packit v0.2.6 h1:+GZSjkt+qmTyux98dr5yYM4DFdThug2FojGvGx1y4Go= +github.com/paketo-buildpacks/packit v0.2.6/go.mod h1:75NgEq9XQGt+6n/VF8hR0wB8rTGeLmRmbTbhhL/zUow= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= +github.com/sclevine/spec v1.4.0 h1:z/Q9idDcay5m5irkZ28M7PtQM4aOISzOpj4bUPkDee8= +github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM= +github.com/ulikunitz/xz v0.5.7 h1:YvTNdFzX6+W5m9msiYg/zpkSURPPtOlzbqYjrFn7Yt4= +github.com/ulikunitz/xz v0.5.7/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191128015809-6d18c012aee9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae h1:Ih9Yo4hSPImZOpfGuA4bR/ORKTAbhZo2AbWNRCnevdo= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/init_test.go b/init_test.go new file mode 100644 index 0000000..c7ed0ce --- /dev/null +++ b/init_test.go @@ -0,0 +1,15 @@ +package yarnstart_test + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestUnitGoBuild(t *testing.T) { + suite := spec.New("yarn-start", spec.Report(report.Terminal{})) + suite("Build", testBuild) + suite("Detect", testDetect) + suite.Run(t) +} diff --git a/integration.json b/integration.json new file mode 100644 index 0000000..6c77a77 --- /dev/null +++ b/integration.json @@ -0,0 +1,6 @@ +{ + "node-engine": "github.com/paketo-buildpacks/node-engine", + "yarn": "github.com/paketo-buildpacks/yarn", + "yarn-install": "github.com/paketo-buildpacks/yarn-install", + "tini": "github.com/paketo-buildpacks/tini" +} diff --git a/integration/custom_start_cmd_test.go b/integration/custom_start_cmd_test.go new file mode 100644 index 0000000..cd3bc8a --- /dev/null +++ b/integration/custom_start_cmd_test.go @@ -0,0 +1,96 @@ +package integration_test + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/paketo-buildpacks/occam" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" + . "github.com/paketo-buildpacks/occam/matchers" +) + +func testCustomStartCmd(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + Eventually = NewWithT(t).Eventually + + pack occam.Pack + docker occam.Docker + ) + + it.Before(func() { + pack = occam.NewPack() + docker = occam.NewDocker() + }) + + context("when building a container image with pack", func() { + var ( + image occam.Image + container occam.Container + + name string + source string + ) + + it.Before(func() { + var err error + name, err = occam.RandomName() + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(docker.Container.Remove.Execute(container.ID)).To(Succeed()) + Expect(docker.Image.Remove.Execute(image.ID)).To(Succeed()) + Expect(docker.Volume.Remove.Execute(occam.CacheVolumeNames(name))).To(Succeed()) + Expect(os.RemoveAll(source)).To(Succeed()) + }) + + it("builds a working OCI image and runs given start cmd", func() { + var err error + source, err = occam.Source(filepath.Join("testdata", "custom_start_cmd_app")) + Expect(err).NotTo(HaveOccurred()) + + var logs fmt.Stringer + image, logs, err = pack.WithNoColor().Build. + WithBuildpacks( + nodeBuildpack, + yarnBuildpack, + yarnInstallBuildpack, + tiniBuildpack, + buildpack, + ). + WithNoPull(). + Execute(name, source) + Expect(err).NotTo(HaveOccurred(), logs.String()) + + container, err = docker.Container.Run.Execute(image.ID) + Expect(err).NotTo(HaveOccurred()) + + Eventually(container).Should(BeAvailable()) + + response, err := http.Get(fmt.Sprintf("http://localhost:%s", container.HostPort())) + Expect(err).NotTo(HaveOccurred()) + defer response.Body.Close() + + Expect(response.StatusCode).To(Equal(http.StatusOK)) + + content, err := ioutil.ReadAll(response.Body) + Expect(err).NotTo(HaveOccurred()) + Expect(string(content)).To(ContainSubstring("Hello, World!")) + + cLogs := func() fmt.Stringer { + containerLogs, err := docker.Container.Logs.Execute(container.ID) + Expect(err).NotTo(HaveOccurred()) + return containerLogs + } + + Eventually(cLogs).Should(ContainSubstring("This is the start command")) + }) + }) +} diff --git a/integration/default_test.go b/integration/default_test.go new file mode 100644 index 0000000..0f5a36e --- /dev/null +++ b/integration/default_test.go @@ -0,0 +1,92 @@ +package integration_test + +import ( + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "testing" + + "github.com/paketo-buildpacks/occam" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" + . "github.com/paketo-buildpacks/occam/matchers" +) + +func testDefault(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + Eventually = NewWithT(t).Eventually + pack occam.Pack + docker occam.Docker + ) + + it.Before(func() { + pack = occam.NewPack() + docker = occam.NewDocker() + }) + + context("when building a container image with pack", func() { + var ( + image occam.Image + container occam.Container + name string + source string + ) + + it.Before(func() { + var err error + name, err = occam.RandomName() + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(docker.Container.Remove.Execute(container.ID)).To(Succeed()) + Expect(docker.Image.Remove.Execute(image.ID)).To(Succeed()) + Expect(docker.Volume.Remove.Execute(occam.CacheVolumeNames(name))).To(Succeed()) + Expect(os.RemoveAll(source)).To(Succeed()) + }) + + it("builds a working OCI image for the default app", func() { + var err error + source, err = occam.Source(filepath.Join("testdata", "default_app")) + Expect(err).NotTo(HaveOccurred()) + + var logs fmt.Stringer + image, logs, err = pack.WithNoColor().Build. + WithNoPull(). + WithBuildpacks( + nodeBuildpack, + yarnBuildpack, + yarnInstallBuildpack, + tiniBuildpack, + buildpack, + ). + Execute(name, source) + Expect(err).NotTo(HaveOccurred(), logs.String()) + + container, err = docker.Container.Run.Execute(image.ID) + Expect(err).NotTo(HaveOccurred()) + + Eventually(container).Should(BeAvailable()) + + Expect(logs).To(ContainLines( + MatchRegexp(fmt.Sprintf(`%s \d+\.\d+\.\d+`, buildpackInfo.Buildpack.Name)), + " Writing start command", + ` tini -g -- yarn start`, + )) + + response, err := http.Get(fmt.Sprintf("http://localhost:%s", container.HostPort())) + Expect(err).NotTo(HaveOccurred()) + defer response.Body.Close() + + Expect(response.StatusCode).To(Equal(http.StatusOK)) + + content, err := ioutil.ReadAll(response.Body) + Expect(err).NotTo(HaveOccurred()) + Expect(string(content)).To(ContainSubstring("Hello, World!")) + }) + }) +} diff --git a/integration/graceful_shutdown_test.go b/integration/graceful_shutdown_test.go new file mode 100644 index 0000000..1dca187 --- /dev/null +++ b/integration/graceful_shutdown_test.go @@ -0,0 +1,114 @@ +package integration_test + +import ( + "bytes" + "fmt" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/paketo-buildpacks/occam" + "github.com/sclevine/spec" + + . "github.com/onsi/gomega" + . "github.com/paketo-buildpacks/occam/matchers" + "github.com/paketo-buildpacks/packit/pexec" +) + +func testGracefulShutdown(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + Eventually = NewWithT(t).Eventually + pack occam.Pack + docker occam.Docker + ) + + it.Before(func() { + pack = occam.NewPack() + docker = occam.NewDocker() + }) + + context("when building an image from an app that has a SIGTERM handler", func() { + var ( + image occam.Image + container occam.Container + name string + source string + ) + + it.Before(func() { + var err error + name, err = occam.RandomName() + Expect(err).NotTo(HaveOccurred()) + }) + + it.After(func() { + Expect(docker.Container.Remove.Execute(container.ID)).To(Succeed()) + Expect(docker.Image.Remove.Execute(image.ID)).To(Succeed()) + Expect(docker.Volume.Remove.Execute(occam.CacheVolumeNames(name))).To(Succeed()) + Expect(os.RemoveAll(source)).To(Succeed()) + }) + + it("builds a working OCI image and gracefully shuts down", func() { + var err error + source, err = occam.Source(filepath.Join("testdata", "graceful_shutdown_app")) + Expect(err).NotTo(HaveOccurred()) + + var logs fmt.Stringer + image, logs, err = pack.WithNoColor().Build. + WithBuildpacks( + nodeBuildpack, + yarnBuildpack, + yarnInstallBuildpack, + tiniBuildpack, + buildpack, + ). + WithNoPull(). + Execute(name, source) + Expect(err).NotTo(HaveOccurred(), logs.String()) + + container, err = docker.Container.Run.Execute(image.ID) + Expect(err).NotTo(HaveOccurred()) + + Eventually(container).Should(BeAvailable()) + + response, err := http.Get(fmt.Sprintf("http://localhost:%s", container.HostPort())) + Expect(err).NotTo(HaveOccurred()) + defer response.Body.Close() + + Expect(response.StatusCode).To(Equal(http.StatusOK)) + + content, err := ioutil.ReadAll(response.Body) + Expect(err).NotTo(HaveOccurred()) + Expect(string(content)).To(ContainSubstring("Hello, World")) + + Expect(dockerStop(container.ID)).NotTo(HaveOccurred()) + + cLogs := func() fmt.Stringer { + containerLogs, err := docker.Container.Logs.Execute(container.ID) + Expect(err).NotTo(HaveOccurred()) + return containerLogs + } + + // this works due to tini + Eventually(cLogs).Should(ContainSubstring("echo from SIGTERM handler")) + }) + }) +} + +func dockerStop(containerID string) error { + stderr := bytes.NewBuffer(nil) + exec := pexec.NewExecutable("docker") + err := exec.Execute(pexec.Execution{ + Args: []string{"container", "stop", containerID}, + Stderr: stderr, + }) + if err != nil { + return fmt.Errorf("failed to stop docker container: %w: %s", err, strings.TrimSpace(stderr.String())) + } + + return nil +} diff --git a/integration/init_test.go b/integration/init_test.go new file mode 100644 index 0000000..5d02ad9 --- /dev/null +++ b/integration/init_test.go @@ -0,0 +1,91 @@ +package integration_test + +import ( + "encoding/json" + "os" + "path/filepath" + "testing" + "time" + + "github.com/BurntSushi/toml" + . "github.com/onsi/gomega" + "github.com/paketo-buildpacks/occam" + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +var ( + buildpack string + nodeBuildpack string + tiniBuildpack string + yarnBuildpack string + yarnInstallBuildpack string + + buildpackInfo struct { + Buildpack struct { + ID string + Name string + } + } + + config struct { + NodeEngine string `json:"node-engine"` + Yarn string `json:"yarn"` + YarnInstall string `json:"yarn-install"` + Tini string `json:"tini"` + } +) + +func TestIntegration(t *testing.T) { + var ( + Expect = NewWithT(t).Expect + err error + ) + + root, err := filepath.Abs("./..") + Expect(err).ToNot(HaveOccurred()) + + file, err := os.Open("../buildpack.toml") + Expect(err).NotTo(HaveOccurred()) + + _, err = toml.DecodeReader(file, &buildpackInfo) + Expect(err).NotTo(HaveOccurred()) + Expect(file.Close()).To(Succeed()) + + file, err = os.Open("../integration.json") + Expect(err).NotTo(HaveOccurred()) + + Expect(json.NewDecoder(file).Decode(&config)).To(Succeed()) + Expect(file.Close()).To(Succeed()) + + buildpackStore := occam.NewBuildpackStore() + + buildpack, err = buildpackStore.Get. + WithVersion("1.2.3"). + Execute(root) + Expect(err).NotTo(HaveOccurred()) + + nodeBuildpack, err = buildpackStore.Get. + Execute(config.NodeEngine) + Expect(err).NotTo(HaveOccurred()) + + yarnBuildpack, err = buildpackStore.Get. + Execute(config.Yarn) + Expect(err).NotTo(HaveOccurred()) + + yarnInstallBuildpack, err = buildpackStore.Get. + Execute(config.YarnInstall) + Expect(err).NotTo(HaveOccurred()) + + tiniBuildpack, err = buildpackStore.Get. + Execute(config.Tini) + Expect(err).NotTo(HaveOccurred()) + + SetDefaultEventuallyTimeout(5 * time.Second) + + suite := spec.New("Integration", spec.Report(report.Terminal{}), spec.Parallel()) + suite("Default", testDefault) + suite("CustomStartCmd", testCustomStartCmd) + suite("GracefulShutdown", testGracefulShutdown) + suite.Run(t) +} diff --git a/integration/testdata/custom_start_cmd_app/.gitignore b/integration/testdata/custom_start_cmd_app/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/integration/testdata/custom_start_cmd_app/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/integration/testdata/custom_start_cmd_app/README.md b/integration/testdata/custom_start_cmd_app/README.md new file mode 100644 index 0000000..e42c264 --- /dev/null +++ b/integration/testdata/custom_start_cmd_app/README.md @@ -0,0 +1 @@ +This file here to suppress "npm WARN package.json node_web_app@0.0.0 No README data" diff --git a/integration/testdata/custom_start_cmd_app/package.json b/integration/testdata/custom_start_cmd_app/package.json new file mode 100644 index 0000000..4b14aea --- /dev/null +++ b/integration/testdata/custom_start_cmd_app/package.json @@ -0,0 +1,19 @@ +{ + "name": "simple_app", + "version": "0.0.0", + "description": "some app", + "main": "server.js", + "scripts": { + "start": "echo \"This is the start command\" && node server.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "leftpad": "~0.0.1" + }, + "repository": { + "type": "git", + "url": "" + } +} diff --git a/integration/testdata/custom_start_cmd_app/server.js b/integration/testdata/custom_start_cmd_app/server.js new file mode 100644 index 0000000..a454c14 --- /dev/null +++ b/integration/testdata/custom_start_cmd_app/server.js @@ -0,0 +1,17 @@ +const http = require('http') +const leftpad = require('leftpad') +const port = process.env.PORT || 8080 + +const requestHandler = (request, response) => { + response.end("Hello, World!") +} + +const server = http.createServer(requestHandler) + +server.listen(port, (err) => { + if (err) { + return console.log('something bad happened', err) + } + + console.log(`server is listening on ${port}`) +}) diff --git a/integration/testdata/custom_start_cmd_app/yarn.lock b/integration/testdata/custom_start_cmd_app/yarn.lock new file mode 100644 index 0000000..77a177c --- /dev/null +++ b/integration/testdata/custom_start_cmd_app/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +leftpad@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/leftpad/-/leftpad-0.0.1.tgz#86b1a4de4face180ac545a83f1503523d8fed115" + integrity sha1-hrGk3k+s4YCsVFqD8VA1I9j+0RU= diff --git a/integration/testdata/default_app/.gitignore b/integration/testdata/default_app/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/integration/testdata/default_app/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/integration/testdata/default_app/README.md b/integration/testdata/default_app/README.md new file mode 100644 index 0000000..e42c264 --- /dev/null +++ b/integration/testdata/default_app/README.md @@ -0,0 +1 @@ +This file here to suppress "npm WARN package.json node_web_app@0.0.0 No README data" diff --git a/integration/testdata/default_app/package.json b/integration/testdata/default_app/package.json new file mode 100644 index 0000000..62c9e27 --- /dev/null +++ b/integration/testdata/default_app/package.json @@ -0,0 +1,18 @@ +{ + "name": "simple_app", + "version": "0.0.0", + "description": "some app", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "leftpad": "~0.0.1" + }, + "repository": { + "type": "git", + "url": "" + } +} diff --git a/integration/testdata/default_app/server.js b/integration/testdata/default_app/server.js new file mode 100644 index 0000000..a454c14 --- /dev/null +++ b/integration/testdata/default_app/server.js @@ -0,0 +1,17 @@ +const http = require('http') +const leftpad = require('leftpad') +const port = process.env.PORT || 8080 + +const requestHandler = (request, response) => { + response.end("Hello, World!") +} + +const server = http.createServer(requestHandler) + +server.listen(port, (err) => { + if (err) { + return console.log('something bad happened', err) + } + + console.log(`server is listening on ${port}`) +}) diff --git a/integration/testdata/default_app/yarn.lock b/integration/testdata/default_app/yarn.lock new file mode 100644 index 0000000..77a177c --- /dev/null +++ b/integration/testdata/default_app/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +leftpad@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/leftpad/-/leftpad-0.0.1.tgz#86b1a4de4face180ac545a83f1503523d8fed115" + integrity sha1-hrGk3k+s4YCsVFqD8VA1I9j+0RU= diff --git a/integration/testdata/graceful_shutdown_app/package.json b/integration/testdata/graceful_shutdown_app/package.json new file mode 100644 index 0000000..db4461a --- /dev/null +++ b/integration/testdata/graceful_shutdown_app/package.json @@ -0,0 +1,21 @@ +{ + "name": "graceful_shutdown_app", + "version": "0.0.0", + "description": "some graceful app", + "main": "server.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "", + "dependencies": { + "leftpad": "~0.0.1" + }, + "repository": { + "type": "git", + "url": "" + }, + "engines": { + "node": "~10" + } +} diff --git a/integration/testdata/graceful_shutdown_app/server.js b/integration/testdata/graceful_shutdown_app/server.js new file mode 100644 index 0000000..bd2978b --- /dev/null +++ b/integration/testdata/graceful_shutdown_app/server.js @@ -0,0 +1,21 @@ +const http = require('http'); +const leftpad = require('leftpad'); + +const port = process.env.PORT || 8080; + +const server = http.createServer((request, response) => { + response.end("Hello, World") +}); + +process.once('SIGTERM', function (code) { + console.log('echo from SIGTERM handler'); + server.close(); +}); + +server.listen(port, (err) => { + if (err) { + return console.log('something bad happened', err); + } + + console.log(`NOT vendored server is listening on ${port}`); +}); diff --git a/integration/testdata/graceful_shutdown_app/yarn.lock b/integration/testdata/graceful_shutdown_app/yarn.lock new file mode 100644 index 0000000..77a177c --- /dev/null +++ b/integration/testdata/graceful_shutdown_app/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +leftpad@~0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/leftpad/-/leftpad-0.0.1.tgz#86b1a4de4face180ac545a83f1503523d8fed115" + integrity sha1-hrGk3k+s4YCsVFqD8VA1I9j+0RU= diff --git a/package.toml b/package.toml new file mode 100644 index 0000000..0d9c4a6 --- /dev/null +++ b/package.toml @@ -0,0 +1,2 @@ +[buildpack] +uri = "build/buildpack.tgz" diff --git a/run/main.go b/run/main.go new file mode 100644 index 0000000..ec33645 --- /dev/null +++ b/run/main.go @@ -0,0 +1,18 @@ +package main + +import ( + "os" + + "github.com/paketo-buildpacks/packit" + "github.com/paketo-buildpacks/packit/scribe" + yarnstart "github.com/paketo-buildpacks/yarn-start" +) + +func main() { + logger := scribe.NewLogger(os.Stdout) + + packit.Run( + yarnstart.Detect(), + yarnstart.Build(logger), + ) +}