Skip to content

Commit

Permalink
Merge branch 'main' into update/pipeline
Browse files Browse the repository at this point in the history
  • Loading branch information
sambhav authored Oct 9, 2021
2 parents cac3da1 + e90023c commit 9173ab1
Show file tree
Hide file tree
Showing 18 changed files with 413 additions and 17 deletions.
2 changes: 1 addition & 1 deletion build.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (b BuildResult) String() string {
)
}

//go:generate mockery -name Builder -case=underscore
//go:generate mockery --name=Builder --case=underscore

// Builder describes an interface for types that can be used by the Build function.
type Builder interface {
Expand Down
25 changes: 22 additions & 3 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package libcnb

//go:generate mockery -name EnvironmentWriter -case=underscore
//go:generate mockery --name EnvironmentWriter --case=underscore

// EnvironmentWriter is the interface implemented by a type that wants to serialize a map of environment variables to
// the file system.
Expand All @@ -27,7 +27,7 @@ type EnvironmentWriter interface {
Write(dir string, environment map[string]string) error
}

//go:generate mockery -name ExitHandler -case=underscore
//go:generate mockery --name ExitHandler --case=underscore

// ExitHandler is the interface implemented by a type that wants to handle exit behavior when a buildpack encounters an
// error.
Expand All @@ -43,7 +43,7 @@ type ExitHandler interface {
Pass()
}

//go:generate mockery -name TOMLWriter -case=underscore
//go:generate mockery --name TOMLWriter --case=underscore

// TOMLWriter is the interface implemented by a type that wants to serialize an object to a TOML file.
type TOMLWriter interface {
Expand All @@ -52,12 +52,23 @@ type TOMLWriter interface {
Write(path string, value interface{}) error
}

//go:generate mockery --name ExecDWriter --case=underscore

// ExecDWriter is the interface implemented by a type that wants to write exec.d output to file descriptor 3.
type ExecDWriter interface {

// Write is called with the map of environment value key value
// pairs that will be written out
Write(value map[string]string) error
}

// Config is an object that contains configurable properties for execution.
type Config struct {
arguments []string
environmentWriter EnvironmentWriter
exitHandler ExitHandler
tomlWriter TOMLWriter
execdWriter ExecDWriter
}

// Option is a function for configuring a Config instance.
Expand Down Expand Up @@ -94,3 +105,11 @@ func WithTOMLWriter(tomlWriter TOMLWriter) Option {
return config
}
}

// WithExecDWriter creates an Option that sets a ExecDWriter implementation.
func WithExecDWriter(execdWriter ExecDWriter) Option {
return func(config Config) Config {
config.execdWriter = execdWriter
return config
}
}
2 changes: 1 addition & 1 deletion detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ type DetectResult struct {
Plans []BuildPlan
}

//go:generate mockery -name Detector -case=underscore
//go:generate mockery --name Detector --case=underscore

// Detector describes an interface for types that can be used by the Detect function.
type Detector interface {
Expand Down
71 changes: 71 additions & 0 deletions exec_d.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package libcnb

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

"github.com/buildpacks/libcnb/internal"
)

//go:generate mockery --name ExecD --case=underscore

// ExecD describes an interface for types that follow the Exec.d specification.
// It should return a map of environment variables and their values as output.
type ExecD interface {
Execute() (map[string]string, error)
}

// RunExecD is called by the main function of a buildpack's execd binary, encompassing multiple execd
// executors in one binary.
func RunExecD(execDMap map[string]ExecD, options ...Option) {
config := Config{
arguments: os.Args,
execdWriter: internal.NewExecDWriter(),
exitHandler: internal.NewExitHandler(),
}

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

if len(config.arguments) == 0 {
config.exitHandler.Error(fmt.Errorf("expected command name"))

return
}

c := filepath.Base(config.arguments[0])
e, ok := execDMap[c]
if !ok {
config.exitHandler.Error(fmt.Errorf("unsupported command %s", c))
return
}

r, err := e.Execute()
if err != nil {
config.exitHandler.Error(err)
return
}

if err := config.execdWriter.Write(r); err != nil {
config.exitHandler.Error(err)
return
}
}
130 changes: 130 additions & 0 deletions exec_d_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package libcnb_test

import (
"fmt"
"testing"

. "github.com/onsi/gomega"
"github.com/sclevine/spec"
"github.com/stretchr/testify/mock"

"github.com/buildpacks/libcnb"
"github.com/buildpacks/libcnb/mocks"
)

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

exitHandler *mocks.ExitHandler
execdWriter *mocks.ExecDWriter
)

it.Before(func() {
execdWriter = &mocks.ExecDWriter{}
execdWriter.On("Write", mock.Anything).Return(nil)
exitHandler = &mocks.ExitHandler{}
exitHandler.On("Error", mock.Anything)
exitHandler.On("Pass", mock.Anything)
exitHandler.On("Fail", mock.Anything)
})

it("encounters the wrong number of arguments", func() {
libcnb.RunExecD(map[string]libcnb.ExecD{},
libcnb.WithArguments([]string{}),
libcnb.WithExitHandler(exitHandler),
)

Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("expected command name"))
})

it("encounters an unsupported execd binary name", func() {
libcnb.RunExecD(map[string]libcnb.ExecD{},
libcnb.WithArguments([]string{"/dne"}),
libcnb.WithExitHandler(exitHandler),
)

Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError("unsupported command dne"))
})

it("calls the appropriate execd for a given execd invoker binary", func() {
execd1 := &mocks.ExecD{}
execd2 := &mocks.ExecD{}
execd1.On("Execute", mock.Anything).Return(map[string]string{}, nil)

libcnb.RunExecD(map[string]libcnb.ExecD{"execd1": execd1, "execd2": execd2},
libcnb.WithArguments([]string{"execd1"}),
libcnb.WithExitHandler(exitHandler),
libcnb.WithExecDWriter(execdWriter),
)

Expect(execd1.Calls).To(HaveLen(1))
Expect(execd2.Calls).To(BeEmpty())
})

it("calls exitHandler with the error from the execd", func() {
e := &mocks.ExecD{}
err := fmt.Errorf("example error")
e.On("Execute", mock.Anything).Return(nil, err)

libcnb.RunExecD(map[string]libcnb.ExecD{"e": e},
libcnb.WithArguments([]string{"/bin/e"}),
libcnb.WithExitHandler(exitHandler),
libcnb.WithExecDWriter(execdWriter),
)

Expect(e.Calls).To(HaveLen(1))
Expect(execdWriter.Calls).To(HaveLen(0))
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(err))
})

it("calls execdWriter.write with the appropriate input", func() {
e := &mocks.ExecD{}
o := map[string]string{"test": "test"}
e.On("Execute", mock.Anything).Return(o, nil)

libcnb.RunExecD(map[string]libcnb.ExecD{"e": e},
libcnb.WithArguments([]string{"/bin/e"}),
libcnb.WithExitHandler(exitHandler),
libcnb.WithExecDWriter(execdWriter),
)

Expect(e.Calls).To(HaveLen(1))
Expect(execdWriter.Calls).To(HaveLen(1))
Expect(execdWriter.Calls[0].Method).To(BeIdenticalTo("Write"))
Expect(execdWriter.Calls[0].Arguments).To(HaveLen(1))
Expect(execdWriter.Calls[0].Arguments[0]).To(Equal(o))
})

it("calls exitHandler with the error from the execd", func() {
e := &mocks.ExecD{}
err := fmt.Errorf("example error")
e.On("Execute", mock.Anything).Return(nil, err)

libcnb.RunExecD(map[string]libcnb.ExecD{"e": e},
libcnb.WithArguments([]string{"/bin/e"}),
libcnb.WithExitHandler(exitHandler),
libcnb.WithExecDWriter(execdWriter),
)

Expect(e.Calls).To(HaveLen(1))
Expect(execdWriter.Calls).To(HaveLen(0))
Expect(exitHandler.Calls[0].Arguments.Get(0)).To(MatchError(err))
})
}
1 change: 1 addition & 0 deletions init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ func TestUnit(t *testing.T) {
suite("Layer", testLayer)
suite("Main", testMain)
suite("Platform", testPlatform)
suite("ExecD", testExecD)
suite.Run(t)
}
62 changes: 62 additions & 0 deletions internal/execd_writer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2018-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package internal

import (
"io"
"os"

"github.com/BurntSushi/toml"
)

// ExecDWriter is a type used to write TOML files to fd3.
type ExecDWriter struct {
outputWriter io.Writer
}

// Option is a function for configuring an ExitHandler instance.
type ExecDOption func(handler ExecDWriter) ExecDWriter

// WithExecDOutputWriter creates an Option that configures the writer.
func WithExecDOutputWriter(writer io.Writer) ExecDOption {
return func(execdWriter ExecDWriter) ExecDWriter {
execdWriter.outputWriter = writer
return execdWriter
}
}

// NewExitHandler creates a new instance that calls os.Exit and writes to os.stderr.
func NewExecDWriter(options ...ExecDOption) ExecDWriter {
h := ExecDWriter{
outputWriter: os.NewFile(3, "/dev/fd/3"),
}

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

return h
}

// Write outputs the value serialized in TOML format to the appropriate writer.
func (e ExecDWriter) Write(value map[string]string) error {
if value == nil {
return nil
}

return toml.NewEncoder(e.outputWriter).Encode(value)
}
Loading

0 comments on commit 9173ab1

Please sign in to comment.