diff --git a/binding.go b/binding.go deleted file mode 100644 index 1ed3c3f..0000000 --- a/binding.go +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2018-2020 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 libpak - -import ( - "fmt" - "strings" - - "github.com/buildpacks/libcnb" -) - -// BindingResolver provides functionality for resolving a binding given a collection of constraints. -type BindingResolver struct { - - // Bindings are the bindings to resolve against. - Bindings libcnb.Bindings -} - -// Resolve returns the matching binding within the collection of Bindings. The candidate set is filtered by the -// constraints. -func (b *BindingResolver) Resolve(bindingType string) (libcnb.Binding, bool, error) { - m := make([]libcnb.Binding, 0) - for _, binding := range b.Bindings { - if strings.ToLower(binding.Type) == strings.ToLower(bindingType) { - m = append(m, binding) - } - } - - if len(m) < 1 { - return libcnb.Binding{}, false, nil - } else if len(m) > 1 { - return libcnb.Binding{}, false, fmt.Errorf("multiple bindings found for type %s in %+v", bindingType, b.Bindings) - } - - return m[0], true, nil -} diff --git a/binding_test.go b/binding_test.go deleted file mode 100644 index 9d06edc..0000000 --- a/binding_test.go +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2018-2020 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 libpak_test - -import ( - "fmt" - "testing" - - "github.com/buildpacks/libcnb" - . "github.com/onsi/gomega" - "github.com/sclevine/spec" - - "github.com/paketo-buildpacks/libpak" -) - -func testBinding(t *testing.T, context spec.G, it spec.S) { - var ( - Expect = NewWithT(t).Expect - - resolver libpak.BindingResolver - ) - - it.Before(func() { - resolver.Bindings = libcnb.Bindings{} - }) - - it("returns error if binding does not exist", func() { - _, ok, err := resolver.Resolve("test-type") - Expect(err).NotTo(HaveOccurred()) - Expect(ok).To(BeFalse()) - }) - - it("returns error if multiple bindings exist", func() { - resolver.Bindings = libcnb.Bindings{ - libcnb.NewBinding("test-binding-1", "test-path", map[string]string{libcnb.BindingType: "test-type"}), - libcnb.NewBinding("test-binding-2", "test-path", map[string]string{libcnb.BindingType: "test-type"}), - } - - _, _, err := resolver.Resolve("test-type") - Expect(err).To(MatchError(fmt.Sprintf("multiple bindings found for type test-type in %s", resolver.Bindings))) - }) - - it("filters on type", func() { - c := libcnb.NewBinding("test-binding-2", "test-path", map[string]string{libcnb.BindingType: "test-type-2"}) - - resolver.Bindings = libcnb.Bindings{ - libcnb.NewBinding("test-binding-1", "test-path", map[string]string{libcnb.BindingType: "test-type-1"}), - c, - } - - b, ok, err := resolver.Resolve("test-type-2") - Expect(err).NotTo(HaveOccurred()) - Expect(ok).To(BeTrue()) - Expect(b).To(Equal(c)) - }) - -} diff --git a/bindings/init_test.go b/bindings/init_test.go new file mode 100644 index 0000000..329c1a7 --- /dev/null +++ b/bindings/init_test.go @@ -0,0 +1,30 @@ +/* + * Copyright 2018-2020 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 bindings_test + +import ( + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" +) + +func TestUnit(t *testing.T) { + suite := spec.New("bindings", spec.Report(report.Terminal{})) + suite("Resolve", testResolve) + suite.Run(t) +} diff --git a/bindings/resolve.go b/bindings/resolve.go new file mode 100644 index 0000000..8166209 --- /dev/null +++ b/bindings/resolve.go @@ -0,0 +1,89 @@ +/* + * Copyright 2018-2020 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 bindings + +import ( + "fmt" + "strings" + + "github.com/buildpacks/libcnb" +) + +// Predicate should return true if it matches a given binding. +type Predicate func(bind libcnb.Binding) bool + +// OfType returns a Predicate that returns true if a given binding has Type that matches t. The comparison is +// case-insensitive. +func OfType(t string) Predicate { + return func(bind libcnb.Binding) bool { + return strings.ToLower(bind.Type) == strings.ToLower(t) + } +} + +// OfType returns a Predicate that returns true if a given binding has Provider that matches p. The comparison is +// case-insensitive. +func OfProvider(p string) Predicate { + return func(bind libcnb.Binding) bool { + return strings.ToLower(bind.Provider) == strings.ToLower(p) + } +} + +// Resolve returns all bindings from binds that match every Predicate in predicates. +func Resolve(binds libcnb.Bindings, predicates ...Predicate) libcnb.Bindings { + var result libcnb.Bindings + // deep copy + for _, bind := range binds { + result = append(result, bind) + } + // filter on predicates + for _, p := range predicates { + result = filter(result, p) + } + return result +} + +// ResolveOne returns a single binding from bindings that match every Predicate if present. If exactly one match is found +// ResolveOne returns the binding and true. If zero matches are found, ResolveOne returns an empty binding and false. +// An error is returned if multiple matches are found. +func ResolveOne(binds libcnb.Bindings, predicates ...Predicate) (libcnb.Binding, bool, error) { + resolved := Resolve(binds, predicates...) + if len(resolved) == 0 { + return libcnb.Binding{}, false, nil + } + if len(resolved) > 1 { + return libcnb.Binding{}, false, errTooManyBindings(resolved) + } + return resolved[0], true, nil +} + +func errTooManyBindings(binds libcnb.Bindings) error { + var names []string + for _, bind := range binds { + names = append(names, bind.Name) + } + return fmt.Errorf("multiple bindings matched the given predicates %+v", names) +} + +func filter(binds libcnb.Bindings, p Predicate) libcnb.Bindings { + var result []libcnb.Binding + for _, bind := range binds { + if p(bind) { + result = append(result, bind) + } + } + return result +} diff --git a/bindings/resolve_test.go b/bindings/resolve_test.go new file mode 100644 index 0000000..c200235 --- /dev/null +++ b/bindings/resolve_test.go @@ -0,0 +1,166 @@ +/* + * Copyright 2018-2020 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 bindings_test + +import ( + "testing" + + "github.com/buildpacks/libcnb" + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/libpak/bindings" +) + +func testResolve(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + + binds libcnb.Bindings + ) + + it.Before(func() { + binds = []libcnb.Binding{ + { + Name: "name1", + Type: "some-type", + Provider: "some-provider", + }, + { + Name: "name2", + Type: "some-type", + Provider: "other-provider", + }, + { + Name: "name3", + Type: "other-type", + Provider: "some-provider", + }, + } + }) + + context("Resolve", func() { + context("no predicate", func() { + it("returns all bindings", func() { + resolved := bindings.Resolve(binds) + Expect(resolved).To(Equal(binds)) + }) + }) + + context("ByType", func() { + it("returns all with matching type", func() { + resolved := bindings.Resolve(binds, + bindings.OfType("some-type"), + ) + Expect(resolved).To(Equal(libcnb.Bindings{ + { + Name: "name1", + Type: "some-type", + Provider: "some-provider", + }, + { + Name: "name2", + Type: "some-type", + Provider: "other-provider", + }, + })) + }) + }) + + context("ByProvider", func() { + it("returns all with matching type", func() { + resolved := bindings.Resolve(binds, + bindings.OfProvider("some-provider"), + ) + Expect(resolved).To(Equal(libcnb.Bindings{ + { + Name: "name1", + Type: "some-type", + Provider: "some-provider", + }, + { + Name: "name3", + Type: "other-type", + Provider: "some-provider", + }, + })) + }) + }) + + context("multiple predicates", func() { + it("returns the intersection", func() { + resolved := bindings.Resolve(binds, + bindings.OfType("some-type"), + bindings.OfProvider("some-provider"), + ) + Expect(resolved).To(Equal(libcnb.Bindings{ + { + Name: "name1", + Type: "some-type", + Provider: "some-provider", + }, + })) + }) + }) + + context("zero bindings match", func() { + it("returns nil", func() { + resolved := bindings.Resolve(binds, + bindings.OfType("missing-type"), + ) + Expect(resolved).To(BeNil()) + }) + }) + }) + + context("ResolveOne", func() { + context("one binding matches", func() { + it("returns the binding and true", func() { + bind, ok, err := bindings.ResolveOne(binds, + bindings.OfType("some-type"), + bindings.OfProvider("some-provider"), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(ok).To(BeTrue()) + Expect(bind).To(Equal(libcnb.Binding{ + Name: "name1", + Type: "some-type", + Provider: "some-provider", + })) + }) + }) + + context("multiples match", func() { + it("returns an error", func() { + _, _, err := bindings.ResolveOne(binds, + bindings.OfType("some-type"), + ) + Expect(err).To(MatchError(`multiple bindings matched the given predicates [name1 name2]`)) + }) + }) + + context("zero bindings match", func() { + it("returns the binding and false", func() { + _, ok, err := bindings.ResolveOne(binds, + bindings.OfType("missing-type"), + ) + Expect(err).NotTo(HaveOccurred()) + Expect(ok).To(BeFalse()) + }) + }) + }) +} diff --git a/init_test.go b/init_test.go index 302af54..1dc179a 100644 --- a/init_test.go +++ b/init_test.go @@ -25,7 +25,6 @@ import ( func TestUnit(t *testing.T) { suite := spec.New("libpak", spec.Report(report.Terminal{})) - suite("Binding", testBinding) suite("Build", testBuild) suite("Buildpack", testBuildpack) suite("BuildpackPlan", testBuildpackPlan) diff --git a/sherpa/sherpa.go b/sherpa/sherpa.go index 8ad4619..7d63946 100644 --- a/sherpa/sherpa.go +++ b/sherpa/sherpa.go @@ -54,7 +54,7 @@ type ExecD interface { // Helpers is called by the main function of a buildpack's helper application, for execution. func Helpers(helpers map[string]ExecD, options ...Option) error { config := Config{ - arguments: os.Args, + arguments: os.Args, execdWriter: os.NewFile(3, "/dev/fd/3"), }