Skip to content

Commit

Permalink
Adds Run
Browse files Browse the repository at this point in the history
Run takes a DetectFunc and a BuildFunc as arguments. Then when compiled
into executables called "detect" or "build", it uses the name of the
executable to determine which phase to run. This helps simplify
buildpacks wishing to combine their detect and build executables into a
single executable with symlinking.
  • Loading branch information
Ryan Moran authored and joshzarrabi committed May 15, 2020
1 parent 89bf78c commit d96d141
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 3 deletions.
3 changes: 3 additions & 0 deletions cargo/jam/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"os"
"sync"
"testing"
"time"

"github.com/onsi/gomega/gexec"
"github.com/sclevine/spec"
Expand All @@ -21,6 +22,8 @@ import (
var path string

func TestUnitJam(t *testing.T) {
SetDefaultEventuallyTimeout(10 * time.Second)

suite := spec.New("cargo/jam", spec.Report(report.Terminal{}))
suite("pack", testPack)
suite("summarize", testSummarize)
Expand Down
35 changes: 32 additions & 3 deletions doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,7 @@
//
// package main
//
// import (
// "github.com/paketo-buildpacks/packit"
// )
// import "github.com/paketo-buildpacks/packit"
//
// func main() {
// // The build phase includes the yarn cli in a new layer that is made
Expand Down Expand Up @@ -152,6 +150,37 @@
// return nil
// }
//
// Run
//
// Buildpacks can be created with a single entrypoint executable using the
// packit.Run function. Here, you can combine both the Detect and Build phases
// and run will ensure that the correct phase is called when the matching
// executable is called by the Cloud Native Buildpack Lifecycle. Below is an
// example that combines a simple detect and build into a single main program.
//
// package main
//
// import "github.com/paketo-buildpacks/packit"
//
// func main() {
// detect := func(context packit.DetectContext) (packit.DetectResult, error) {
// return packit.DetectResult{}, nil
// }

// build := func(context packit.BuildContext) (packit.BuildResult, error) {
// return packit.BuildResult{
// Processes: []packit.Process{
// {
// Type: "web",
// Command: `while true; do nc -l -p $PORT -c 'echo -e "HTTP/1.1 200 OK\n\n Hello, world!\n"'; done`,
// },
// },
// }, nil
// }
//
// packit.Run(detect, build)
// }
//
// Summary
//
// These examples show the very basics of what a buildpack implementation using
Expand Down
1 change: 1 addition & 0 deletions init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ func TestUnitPackit(t *testing.T) {
suite("Environment", testEnvironment)
suite("Layer", testLayer)
suite("Layers", testLayers)
suite("Run", testRun)
suite.Run(t)
}
36 changes: 36 additions & 0 deletions run.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package packit

import (
"fmt"
"os"
"path/filepath"

"github.com/paketo-buildpacks/packit/internal"
)

// Run combines the invocation of both build and detect into a single entry
// point. Calling Run from an executable with a name matching "build" or
// "detect" will result in the matching DetectFunc or BuildFunc being called.
func Run(detect DetectFunc, build BuildFunc, options ...Option) {
config := OptionConfig{
exitHandler: internal.NewExitHandler(),
args: os.Args,
}

for _, option := range options {
config = option(config)
}

phase := filepath.Base(config.args[0])

switch phase {
case "detect":
Detect(detect, options...)

case "build":
Build(build, options...)

default:
config.exitHandler.Error(fmt.Errorf("failed to run buildpack: unknown lifecycle phase %q", phase))
}
}
153 changes: 153 additions & 0 deletions run_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package packit_test

import (
"io/ioutil"
"os"
"path/filepath"
"testing"

"github.com/paketo-buildpacks/packit"
"github.com/paketo-buildpacks/packit/fakes"
"github.com/sclevine/spec"

. "github.com/onsi/gomega"
)

func testRun(t *testing.T, context spec.G, it spec.S) {
var (
Expect = NewWithT(t).Expect

workingDir string
tmpDir string
cnbDir string
exitHandler *fakes.ExitHandler
)

it.Before(func() {
var err error
workingDir, err = os.Getwd()
Expect(err).NotTo(HaveOccurred())

tmpDir, err = ioutil.TempDir("", "")
Expect(err).NotTo(HaveOccurred())

tmpDir, err = filepath.EvalSymlinks(tmpDir)
Expect(err).NotTo(HaveOccurred())

Expect(os.Chdir(tmpDir)).To(Succeed())

cnbDir, err = ioutil.TempDir("", "cnb")
Expect(err).NotTo(HaveOccurred())

Expect(ioutil.WriteFile(filepath.Join(cnbDir, "buildpack.toml"), []byte(`
[buildpack]
id = "some-id"
name = "some-name"
version = "some-version"
clear-env = false
`), 0644)).To(Succeed())

exitHandler = &fakes.ExitHandler{}
})

it.After(func() {
Expect(os.Chdir(workingDir)).To(Succeed())
Expect(os.RemoveAll(tmpDir)).To(Succeed())
Expect(os.RemoveAll(cnbDir)).To(Succeed())
})

context("when running the detect executable", func() {
var (
args []string
buildPlanPath string
)

it.Before(func() {
buildPlanPath = filepath.Join(tmpDir, "buildplan.toml")

args = []string{filepath.Join(cnbDir, "bin", "detect"), "", buildPlanPath}
})

it.After(func() {
Expect(os.Remove(buildPlanPath)).To(Succeed())
})

it("calls the DetectFunc", func() {
var detectCalled bool

detect := func(packit.DetectContext) (packit.DetectResult, error) {
detectCalled = true
return packit.DetectResult{}, nil
}

packit.Run(detect, nil, packit.WithArgs(args), packit.WithExitHandler(exitHandler))

Expect(detectCalled).To(BeTrue())
Expect(exitHandler.ErrorCall.CallCount).To(Equal(0))
})
})

context("when running the build executable", func() {
var (
args []string
layersDir string
planPath string
)

it.Before(func() {
file, err := ioutil.TempFile("", "plan.toml")
Expect(err).NotTo(HaveOccurred())
defer file.Close()

_, err = file.WriteString(`
[[entries]]
name = "some-entry"
version = "some-version"
[entries.metadata]
some-key = "some-value"
`)
Expect(err).NotTo(HaveOccurred())

planPath = file.Name()

layersDir, err = ioutil.TempDir("", "layers")
Expect(err).NotTo(HaveOccurred())

args = []string{filepath.Join(cnbDir, "bin", "build"), layersDir, "", planPath}
})

it.After(func() {
Expect(os.RemoveAll(layersDir)).To(Succeed())
Expect(os.Remove(planPath)).To(Succeed())
})

it("calls the BuildFunc", func() {
var buildCalled bool

build := func(packit.BuildContext) (packit.BuildResult, error) {
buildCalled = true
return packit.BuildResult{}, nil
}

packit.Run(nil, build, packit.WithArgs(args), packit.WithExitHandler(exitHandler))

Expect(buildCalled).To(BeTrue())
Expect(exitHandler.ErrorCall.CallCount).To(Equal(0))
})
})

context("when running any other executable", func() {
var args []string

it.Before(func() {
args = []string{filepath.Join(cnbDir, "bin", "something-else"), "some", "args"}
})

it("returns an error", func() {
packit.Run(nil, nil, packit.WithArgs(args), packit.WithExitHandler(exitHandler))

Expect(exitHandler.ErrorCall.Receives.Error).To(MatchError("failed to run buildpack: unknown lifecycle phase \"something-else\""))
})
})
}

0 comments on commit d96d141

Please sign in to comment.