Skip to content

Commit

Permalink
Add support for BP_PROCFILE_DEFAULT_PROCESS environment variable
Browse files Browse the repository at this point in the history
  • Loading branch information
jericop committed Apr 2, 2024
1 parent 6db5e7e commit 6d325c1
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 13 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ This buildpack will participate if one or all of the following conditions are me

* The application contains a `Procfile`
* A Binding exists with type `Procfile` and secret containing a `Procfile`
* The `BP_PROCFILE_DEFAULT_PROCESS` environment variable is set to a non-empty value

The buildpack will do the following:

* When `BP_PROCFILE_DEFAULT_PROCESS` is set, it will contribute the `web` process type to the image.
* Contribute the process types from one or both `Procfile` files to the image.
* If process types are identified from both Binding _and_ file, the contents are merged into a single `Procfile`. Commands from the Binding take precedence if there are duplicate types.
* If process types are identified from environment _and_ Binding _or_ file, the contents are merged into a single `Procfile`. Commands from Binding or file take precedence if there are duplicate types, with Binding taking precedence over file.
* If the application's stack is `io.paketo.stacks.tiny` the contents of the `Procfile` must be single command with zero or more space delimited arguments. Argument values containing whitespace should be quoted. The resulting process will be executed directly and will not be parsed by the shell.
* If the application's stack is not `io.paketo.stacks.tiny` the contents of `Procfile` will be executed as a shell script.

Expand Down
7 changes: 4 additions & 3 deletions procfile/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,24 @@
package procfile

import (
"os"

"github.com/buildpacks/libcnb"
"github.com/paketo-buildpacks/libpak/bard"
"os"
)

type Detect struct{}

func (d Detect) Detect(context libcnb.DetectContext) (libcnb.DetectResult, error) {
l := bard.NewLogger(os.Stdout)
// Create Procfile from source path or binding, if both exist, merge into one. The binding takes precedence on duplicate name/command pairs.
p, err := NewProcfileFromPathOrBinding(context.Application.Path, context.Platform.Bindings)
p, err := NewProcfileFromEnvironmentOrPathOrBinding(context.Application.Path, context.Platform.Bindings)
if err != nil {
return libcnb.DetectResult{}, err
}

if len(p) == 0 {
l.Logger.Info("SKIPPED: No procfile found from either source path or binding.")
l.Logger.Info("SKIPPED: No procfile found from environment, source path, or binding.")
return libcnb.DetectResult{Pass: false}, nil
}

Expand Down
29 changes: 28 additions & 1 deletion procfile/detect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
Expect(os.RemoveAll(ctx.Application.Path)).To(Succeed())
})

it("fails without Procfile", func() {
it("fails without Procfile or BP_PROCFILE_DEFAULT_PROCESS", func() {
Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{}))
})

Expand All @@ -58,6 +58,13 @@ func testDetect(t *testing.T, context spec.G, it spec.S) {
Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{}))
})

it("fails with empty Procfile and empty BP_PROCFILE_DEFAULT_PROCESS", func() {
t.Setenv("BP_PROCFILE_DEFAULT_PROCESS", "")
Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "Procfile"), []byte(""), 0644))

Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{}))
})

it("passes with Procfile", func() {
Expect(ioutil.WriteFile(filepath.Join(ctx.Application.Path, "Procfile"), []byte(`test-type-1: test-command-1
test-type-2: test-command-2`), 0644))
Expand All @@ -79,4 +86,24 @@ test-type-2: test-command-2`), 0644))
},
}))
})

it("passes with BP_PROCFILE_DEFAULT_PROCESS", func() {
t.Setenv("BP_PROCFILE_DEFAULT_PROCESS", "test-command-1")

Expect(detect.Detect(ctx)).To(Equal(libcnb.DetectResult{
Pass: true,
Plans: []libcnb.BuildPlan{
{
Provides: []libcnb.BuildPlanProvide{
{Name: "procfile"},
},
Requires: []libcnb.BuildPlanRequire{
{Name: "procfile", Metadata: procfile.Procfile{
"web": "test-command-1",
}},
},
},
},
}))
})
}
30 changes: 25 additions & 5 deletions procfile/procfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"path/filepath"
"regexp"

"github.com/paketo-buildpacks/libpak/bard"
"github.com/paketo-buildpacks/libpak/bindings"

"github.com/buildpacks/libcnb"
Expand All @@ -35,6 +36,17 @@ const (
BindingType = "Procfile" // BindingType is used to resolve a binding containing a Procfile
)

// NewProcfileFromEnvironment creates a Procfile by reading environment variable BP_PROCFILE_DEFAULT_PROCESS if it exists.
// If it does not exist, returns an empty Procfile.
func NewProcfileFromEnvironment() (Procfile, error) {
if process, isSet := os.LookupEnv("BP_PROCFILE_DEFAULT_PROCESS"); isSet {
if process != "" {
return Procfile{"web": process}, nil
}
}
return nil, nil
}

// NewProcfileFromPath creates a Procfile by reading Procfile from path if it exists. If it does not exist, returns an
// empty Procfile.
func NewProcfileFromPath(path string) (Procfile, error) {
Expand Down Expand Up @@ -87,10 +99,13 @@ func NewProcfileFromBinding(binds libcnb.Bindings) (Procfile, error) {
}
}

// NewProcfileFromPathOrBinding attempts to create a merged Procfile from given path and bindings. If neither can be created, returns an
// empty Procfile.
func NewProcfileFromPathOrBinding(path string, binds libcnb.Bindings) (Procfile, error) {

// NewProcfileFromEnvironmentOrPathOrBinding attempts to create a merged Procfile from environment and/or given path and bindings.
// If none can be created, returns an empty Procfile.
func NewProcfileFromEnvironmentOrPathOrBinding(path string, binds libcnb.Bindings) (Procfile, error) {
procEnv, err := NewProcfileFromEnvironment()
if err != nil {
return nil, err
}
procPath, err := NewProcfileFromPath(path)
if err != nil {
return nil, err
Expand All @@ -99,7 +114,12 @@ func NewProcfileFromPathOrBinding(path string, binds libcnb.Bindings) (Procfile,
if err != nil {
return nil, err
}
procBind = mergeProcfiles(procPath, procBind)
if len(procEnv) > 0 && len(procPath)+len(procBind) > 0 {
l := bard.NewLogger(os.Stdout)
l.Logger.Info("A Procfile exists and BP_PROCFILE_DEFAULT_PROCESS is set, entries in Procfile take precedence")
}

procBind = mergeProcfiles(procEnv, procPath, procBind)
return procBind, nil
}

Expand Down
33 changes: 29 additions & 4 deletions procfile/procfile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ func testProcfile(t *testing.T, context spec.G, it spec.S) {
Expect(os.RemoveAll(path)).To(Succeed())
})

it("returns an empty Procfile when BP_PROCFILE_DEFAULT_PROCESS is an empty string", func() {
t.Setenv("BP_PROCFILE_DEFAULT_PROCESS", "")
Expect(procfile.NewProcfileFromEnvironment()).To(HaveLen(0))
})

it("returns a parsed Profile when BP_PROCFILE_DEFAULT_PROCESS is a non-empty string", func() {
t.Setenv("BP_PROCFILE_DEFAULT_PROCESS", "test-command")
Expect(procfile.NewProcfileFromEnvironment()).To(HaveLen(1))
})

it("returns an empty Procfile when file does not exist", func() {
Expect(procfile.NewProcfileFromPath(path)).To(HaveLen(0))
})
Expand All @@ -74,7 +84,7 @@ func testProcfile(t *testing.T, context spec.G, it spec.S) {
bindings = libcnb.Bindings{}
Expect(ioutil.WriteFile(filepath.Join(path, "Procfile"), []byte("test-type-path: test-command"), 0644)).To(Succeed())

Expect(procfile.NewProcfileFromPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type-path": "test-command"}))
Expect(procfile.NewProcfileFromEnvironmentOrPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type-path": "test-command"}))

})

Expand All @@ -87,7 +97,7 @@ func testProcfile(t *testing.T, context spec.G, it spec.S) {
Secret: map[string]string{"Procfile": filepath.Join(bindPath, "Procfile")},
}}

Expect(procfile.NewProcfileFromPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type-bind": "test-command"}))
Expect(procfile.NewProcfileFromEnvironmentOrPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type-bind": "test-command"}))

})

Expand All @@ -101,7 +111,7 @@ func testProcfile(t *testing.T, context spec.G, it spec.S) {
}}
Expect(ioutil.WriteFile(filepath.Join(path, "Procfile"), []byte("test-type-path: test-command"), 0644)).To(Succeed())

Expect(procfile.NewProcfileFromPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type-path": "test-command", "test-type-bind": "test-command"}))
Expect(procfile.NewProcfileFromEnvironmentOrPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type-path": "test-command", "test-type-bind": "test-command"}))

})

Expand All @@ -115,7 +125,22 @@ func testProcfile(t *testing.T, context spec.G, it spec.S) {
}}
Expect(ioutil.WriteFile(filepath.Join(path, "Procfile"), []byte("test-type: path-test-command"), 0644)).To(Succeed())

Expect(procfile.NewProcfileFromPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type": "bind-test-command", "test-type-2": "another-test-command"}))
Expect(procfile.NewProcfileFromEnvironmentOrPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"test-type": "bind-test-command", "test-type-2": "another-test-command"}))

})

it("returns a merged Procfile from environment and given binding + file, binding takes precedence on duplicates", func() {
t.Setenv("BP_PROCFILE_DEFAULT_PROCESS", "env-test-command")
Expect(ioutil.WriteFile(filepath.Join(bindPath, "Procfile"), []byte("web: bind-test-command\ntest-type-2: another-test-command"), 0644)).To(Succeed())
bindings = libcnb.Bindings{libcnb.Binding{
Name: "name1",
Type: "Procfile",
Path: bindPath,
Secret: map[string]string{"Procfile": filepath.Join(bindPath, "Procfile")},
}}
Expect(ioutil.WriteFile(filepath.Join(path, "Procfile"), []byte("web: path-test-command"), 0644)).To(Succeed())

Expect(procfile.NewProcfileFromEnvironmentOrPathOrBinding(path, bindings)).To(Equal(procfile.Procfile{"web": "bind-test-command", "test-type-2": "another-test-command"}))

})

Expand Down

0 comments on commit 6d325c1

Please sign in to comment.