Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tidy Up #269

Merged
merged 8 commits into from
Sep 21, 2022
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/.bin
/build
.idea/
44 changes: 25 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,29 @@
## `gcr.io/paketo-buildpacks/node-start`

The Paketo Node Start CNB sets the start command for a given node application.
The buildpack expects that the app contains a valid `server.js` at the root.
The buildpack will detect when a valid javascript file is present within the
application source code directory (see [Application Detection](#application-detection).

## Integration

This CNB writes a start command, so there's currently no scenario we can
imagine that you would need to require it as dependency. If a user likes to
include some other functionality, it can be done independent of the Node Start
CNB without requiring a dependency of it.
imagine that you would need to require it as dependency. You may include additional
functionality in a separate buildpack without requiring anything from this buildpack.

## Usage

To package this buildpack for consumption:

```
```shell
$ ./scripts/package.sh --version <version-number>
```

This will create a `buildpackage.cnb` file under the `build` directory which you
can use to build your app as follows:
```
pack build <app-name> -p <path-to-app> -b <path/to/node-engine.cnb> -b build/buildpackage.cnb
This will create a `build/buildpackage.cnb` file which you can use to build your app as follows:
```shell
pack build <app-name> \
--path <path-to-app> \
--buildpack <path/to/node-engine.cnb> \
--buildpack build/buildpackage.cnb
```

## Graceful shutdown and signal handling
Expand All @@ -39,11 +41,11 @@ init system that will properly handle signals.
To specify a project subdirectory to be used as the root of the app, please use
the `BP_NODE_PROJECT_PATH` environment variable at build time either directly
(e.g. `pack build my-app --env BP_NODE_PROJECT_PATH=./src/my-app`) or through a
[`project.toml`
file](https://github.com/buildpacks/spec/blob/main/extensions/project-descriptor.md).
[`project.toml` file](https://github.com/buildpacks/spec/blob/main/extensions/project-descriptor.md).
This could be useful if your app is a part of a monorepo.

## Application Detection

This buildpack searches your application root for the following files:
1. `server.js`
1. `app.js`
Expand All @@ -55,30 +57,34 @@ chosen for the start command.

## BP_LAUNCHPOINT

The BP_LAUNCHPOINT environment variable may be used to specify a file for the
The `BP_LAUNCHPOINT` environment variable may be used to specify a file for the
start command that is not included in the above set.

e.g. If `BP_LAUNCHPOINT=./src/launchpoint.js`, the buildpack will verify that
the file exists and then set the start command using that file `node
src/launchpoint.js`
the file exists and then set the start command using that file `node src/launchpoint.js`

## Enabling reloadable process types

You can configure this buildpack to wrap the entrypoint process of your app
such that it kills and restarts the process whenever files in the app's working
directory in the container change. With this feature enabled, copying new
verisons of source code into the running container will trigger your app's
process to restart. Set the environment variable `BP_LIVE_RELOAD_ENABLED=true`
at build time to enable this feature.
versions of source code into the running container will restart the application process.

Set the environment variable `BP_LIVE_RELOAD_ENABLED=true` at build time to enable this feature.

```shell
pack build my-app \
--env BP_LIVE_RELOAD_ENABLED=true
````

## Run Tests

To run all unit tests, run:
```
```shell
./scripts/unit.sh
```

To run all integration tests, run:
```
```shell
/scripts/integration.sh
```
90 changes: 18 additions & 72 deletions build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"bytes"
"errors"
"fmt"
"os"
"testing"

nodestart "github.com/paketo-buildpacks/node-start"
Expand All @@ -27,36 +26,22 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {

applicationFinder *fakes.ApplicationFinder

build packit.BuildFunc
buildContext packit.BuildContext
build packit.BuildFunc
)

it.Before(func() {
var err error
layersDir, err = os.MkdirTemp("", "layers")
Expect(err).NotTo(HaveOccurred())

cnbDir, err = os.MkdirTemp("", "cnb")
Expect(err).NotTo(HaveOccurred())

workingDir, err = os.MkdirTemp("", "working-dir")
Expect(err).NotTo(HaveOccurred())
layersDir = t.TempDir()
cnbDir = t.TempDir()
workingDir = t.TempDir()

applicationFinder = &fakes.ApplicationFinder{}
applicationFinder.FindCall.Returns.String = "server.js"

buffer = bytes.NewBuffer(nil)
logger := scribe.NewEmitter(buffer)
build = nodestart.Build(applicationFinder, 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 provides a node start command", func() {
result, err := build(packit.BuildContext{
buildContext = packit.BuildContext{
WorkingDir: workingDir,
CNBPath: cnbDir,
Stack: "some-stack",
Expand All @@ -68,7 +53,12 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
Entries: []packit.BuildpackPlanEntry{},
},
Layers: packit.Layers{Path: layersDir},
})
}
build = nodestart.Build(applicationFinder, logger)
})

it("returns a result that provides a node start command", func() {
result, err := build(buildContext)
Expect(err).NotTo(HaveOccurred())

Expect(result).To(Equal(packit.BuildResult{
Expand Down Expand Up @@ -98,27 +88,11 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {

context("when BP_LIVE_RELOAD_ENABLED=true in the build environment", func() {
it.Before(func() {
os.Setenv("BP_LIVE_RELOAD_ENABLED", "true")
})

it.After(func() {
os.Unsetenv("BP_LIVE_RELOAD_ENABLED")
t.Setenv("BP_LIVE_RELOAD_ENABLED", "true")
})

it("adds a reloadable start command and makes it the default", 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},
})
result, err := build(buildContext)
Expect(err).NotTo(HaveOccurred())

Expect(result.Launch.Processes).To(Equal([]packit.Process{
Expand Down Expand Up @@ -154,48 +128,20 @@ func testBuild(t *testing.T, context spec.G, it spec.S) {
context("when the application finding fails", func() {
it.Before(func() {
applicationFinder.FindCall.Returns.Error = errors.New("failed to find application")

})

it("returns an error", func() {
_, 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},
})
_, err := build(buildContext)
Expect(err).To(MatchError("failed to find application"))
})
})
context("when BP_LIVE_RELOAD_ENABLED is set to an invalid value", func() {
it.Before(func() {
os.Setenv("BP_LIVE_RELOAD_ENABLED", "not-a-bool")
})

it.After(func() {
os.Unsetenv("BP_LIVE_RELOAD_ENABLED")
t.Setenv("BP_LIVE_RELOAD_ENABLED", "not-a-bool")
})

it("returns an error", func() {
_, 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},
})
_, err := build(buildContext)
Expect(err).To(MatchError(ContainSubstring("failed to parse BP_LIVE_RELOAD_ENABLED value not-a-bool")))
})
})
Expand Down
47 changes: 16 additions & 31 deletions detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,22 @@ func Detect(applicationFinder ApplicationFinder) packit.DetectFunc {
return packit.DetectResult{}, err
}

requirements := []packit.BuildPlanRequirement{
{
Name: "node",
Metadata: map[string]interface{}{
"launch": true,
},
},
}
requirements := []packit.BuildPlanRequirement{newLaunchRequirement("node")}

exists, err := fs.Exists(filepath.Join(context.WorkingDir, os.Getenv("BP_NODE_PROJECT_PATH"), "package.json"))
if err != nil {
if packageJsonExists, err := fs.Exists(filepath.Join(context.WorkingDir, os.Getenv("BP_NODE_PROJECT_PATH"), "package.json")); err != nil {
return packit.DetectResult{}, err
} else if packageJsonExists {
requirements = append(requirements, newLaunchRequirement("node_modules"))
}

if exists {
requirements = append(requirements, packit.BuildPlanRequirement{
Name: "node_modules",
Metadata: map[string]interface{}{
"launch": true,
},
})
}

shouldReload, err := checkLiveReloadEnabled()
if err != nil {
if shouldReload, err := checkLiveReloadEnabled(); err != nil {
return packit.DetectResult{}, err
}

if shouldReload {
requirements = append(requirements, packit.BuildPlanRequirement{
Name: "watchexec",
Metadata: map[string]interface{}{
"launch": true,
},
})
} else if shouldReload {
requirements = append(requirements, newLaunchRequirement("watchexec"))
}

return packit.DetectResult{
Plan: packit.BuildPlan{
Provides: []packit.BuildPlanProvision{},
Requires: requirements,
},
}, nil
Expand All @@ -78,3 +54,12 @@ func checkLiveReloadEnabled() (bool, error) {
}
return false, nil
}

func newLaunchRequirement(name string) packit.BuildPlanRequirement {
return packit.BuildPlanRequirement{
Name: name,
Metadata: map[string]interface{}{
"launch": true,
},
}
}
32 changes: 6 additions & 26 deletions detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,7 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
)

it.Before(func() {
var err error
workingDir, err = os.MkdirTemp("", "working-dir")
Expect(err).NotTo(HaveOccurred())
workingDir = t.TempDir()

applicationFinder = &fakes.ApplicationFinder{}
applicationFinder.FindCall.Returns.String = "./src/server.js"
Expand All @@ -37,24 +35,18 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {

context("when an application is detected in the working dir", func() {
it.Before(func() {
os.Setenv("BP_NODE_PROJECT_PATH", "./src")
os.Setenv("BP_LAUNCHPOINT", "./src/server.js")
t.Setenv("BP_NODE_PROJECT_PATH", "./src")
t.Setenv("BP_LAUNCHPOINT", "./src/server.js")
Expect(os.MkdirAll(filepath.Join(workingDir, "src"), os.ModePerm)).To(Succeed())
Expect(os.WriteFile(filepath.Join(workingDir, "src", "server.js"), nil, 0600)).To(Succeed())
})

it.After(func() {
os.Unsetenv("BP_NODE_PROJECT_PATH")
os.Unsetenv("BP_LAUNCHPOINT")
})

it("detects", func() {
result, err := detect(packit.DetectContext{
WorkingDir: workingDir,
})
Expect(err).NotTo(HaveOccurred())
Expect(result.Plan).To(Equal(packit.BuildPlan{
Provides: []packit.BuildPlanProvision{},
Requires: []packit.BuildPlanRequirement{
{
Name: "node",
Expand All @@ -72,11 +64,7 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {

context("when BP_LIVE_RELOAD_ENABLED=true", func() {
it.Before(func() {
os.Setenv("BP_LIVE_RELOAD_ENABLED", "true")
})

it.After(func() {
os.Unsetenv("BP_LIVE_RELOAD_ENABLED")
t.Setenv("BP_LIVE_RELOAD_ENABLED", "true")
})

it("requires watchexec at launch time", func() {
Expand Down Expand Up @@ -104,15 +92,11 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {

context("when a package.json is detected in the working dir", func() {
it.Before(func() {
os.Setenv("BP_NODE_PROJECT_PATH", "./src")
t.Setenv("BP_NODE_PROJECT_PATH", "./src")
Expect(os.MkdirAll(filepath.Join(workingDir, "src"), os.ModePerm)).To(Succeed())
Expect(os.WriteFile(filepath.Join(workingDir, "src", "package.json"), nil, 0600)).To(Succeed())
})

it.After(func() {
os.Unsetenv("BP_NODE_PROJECT_PATH")
})

it("requires node_modules", func() {
result, err := detect(packit.DetectContext{
WorkingDir: workingDir,
Expand Down Expand Up @@ -168,11 +152,7 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {

context("when BP_LIVE_RELOAD_ENABLED is set to an invalid value", func() {
it.Before(func() {
os.Setenv("BP_LIVE_RELOAD_ENABLED", "not-a-bool")
})

it.After(func() {
os.Unsetenv("BP_LIVE_RELOAD_ENABLED")
t.Setenv("BP_LIVE_RELOAD_ENABLED", "not-a-bool")
})

it("returns an error", func() {
Expand Down
Loading