From f6770ebcaca911b89ec391b824535af014c8f075 Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Fri, 13 Nov 2020 11:03:09 -0800 Subject: [PATCH 1/3] Helper Helpers This change adds some more helper functions to sherpa that are particularly useful in the environment that exec.d's run in. Signed-off-by: Ben Hale --- bindings/resolve.go | 30 +++++++++++ bindings/resolve_test.go | 56 +++++++++++++++++++++ sherpa/env_var.go | 52 +++++++++++++++++++ sherpa/env_var_test.go | 106 +++++++++++++++++++++++++++++++++++++++ sherpa/init_test.go | 1 + 5 files changed, 245 insertions(+) create mode 100644 sherpa/env_var.go create mode 100644 sherpa/env_var_test.go diff --git a/bindings/resolve.go b/bindings/resolve.go index 61b1a5a..99e4077 100644 --- a/bindings/resolve.go +++ b/bindings/resolve.go @@ -26,6 +26,36 @@ import ( // Predicate should return true if it matches a given binding. type Predicate func(bind libcnb.Binding) bool +// And combines multiple predicates logical and-ing their results together. +func And(predicates ...Predicate) Predicate { + return func(bind libcnb.Binding) bool { + if len(predicates) < 1 { + return false + } + + v := predicates[0](bind) + for _, p := range predicates[1:] { + v = v && p(bind) + } + return v + } +} + +// Or combines multiple predicates logical or-ing their results together. +func Or(predicates ...Predicate) Predicate { + return func(bind libcnb.Binding) bool { + if len(predicates) < 1 { + return false + } + + v := predicates[0](bind) + for _, p := range predicates[1:] { + v = v || p(bind) + } + return v + } +} + // 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 { diff --git a/bindings/resolve_test.go b/bindings/resolve_test.go index c200235..ab8943e 100644 --- a/bindings/resolve_test.go +++ b/bindings/resolve_test.go @@ -53,6 +53,62 @@ func testResolve(t *testing.T, context spec.G, it spec.S) { } }) + context("And", func() { + var ( + tr = func(bind libcnb.Binding) bool { + return true + } + + fa = func(bind libcnb.Binding) bool { + return false + } + ) + + it("returns false with no predicates", func() { + Expect(bindings.And()(libcnb.Binding{})).To(BeFalse()) + }) + + it("returns value of single predicate", func() { + Expect(bindings.And(tr)(libcnb.Binding{})).To(BeTrue()) + Expect(bindings.And(fa)(libcnb.Binding{})).To(BeFalse()) + }) + + it("returns and-ed value of multiple predicates", func() { + Expect(bindings.And(tr, tr)(libcnb.Binding{})).To(BeTrue()) + Expect(bindings.And(tr, fa)(libcnb.Binding{})).To(BeFalse()) + Expect(bindings.And(fa, tr)(libcnb.Binding{})).To(BeFalse()) + Expect(bindings.And(fa, fa)(libcnb.Binding{})).To(BeFalse()) + }) + }) + + context("Or", func() { + var ( + tr = func(bind libcnb.Binding) bool { + return true + } + + fa = func(bind libcnb.Binding) bool { + return false + } + ) + + it("returns false with no predicates", func() { + Expect(bindings.Or()(libcnb.Binding{})).To(BeFalse()) + }) + + it("returns value of single predicate", func() { + Expect(bindings.Or(tr)(libcnb.Binding{})).To(BeTrue()) + Expect(bindings.Or(fa)(libcnb.Binding{})).To(BeFalse()) + }) + + it("returns or-ed value of multiple predicates", func() { + Expect(bindings.Or(tr, tr)(libcnb.Binding{})).To(BeTrue()) + Expect(bindings.Or(tr, fa)(libcnb.Binding{})).To(BeTrue()) + Expect(bindings.Or(fa, tr)(libcnb.Binding{})).To(BeTrue()) + Expect(bindings.Or(fa, fa)(libcnb.Binding{})).To(BeFalse()) + }) + }) + context("Resolve", func() { context("no predicate", func() { it("returns all bindings", func() { diff --git a/sherpa/env_var.go b/sherpa/env_var.go new file mode 100644 index 0000000..a39bbc5 --- /dev/null +++ b/sherpa/env_var.go @@ -0,0 +1,52 @@ +/* + * 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 sherpa + +import ( + "fmt" + "os" + "strings" +) + +// AppendToEnvVar appends a collection of values to an env var separated by a delimiter. If the env var does not already +// exist, joins the values with the delimiter and returns the result. +func AppendToEnvVar(name string, delimiter string, values ...string) string { + var e []string + if s, ok := os.LookupEnv(name); ok { + e = append(e, s) + } + e = append(e, values...) + return strings.Join(e, delimiter) +} + +// GetEnvRequired returns the value of an environment varible if it exists, otherwire returns an error with a +// predictable message. +func GetEnvRequired(name string) (string, error) { + if s, ok := os.LookupEnv(name); ok { + return s, nil + } + + return "", fmt.Errorf("$%s must be set", name) +} + +// GetEnvWithWithDefault returns the value of an environment variable if it exists, otherwise returns the default. +func GetEnvWithDefault(name string, def string) string { + if s, ok := os.LookupEnv(name); ok { + return s + } + return def +} diff --git a/sherpa/env_var_test.go b/sherpa/env_var_test.go new file mode 100644 index 0000000..62c6db3 --- /dev/null +++ b/sherpa/env_var_test.go @@ -0,0 +1,106 @@ +/* + * 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 sherpa_test + +import ( + "os" + "testing" + + . "github.com/onsi/gomega" + "github.com/sclevine/spec" + + "github.com/paketo-buildpacks/libpak/sherpa" +) + +func testEnvVar(t *testing.T, context spec.G, it spec.S) { + var ( + Expect = NewWithT(t).Expect + ) + + context("AppendToEnvVar", func() { + + context("No Existing", func() { + + it("append one", func() { + Expect(sherpa.AppendToEnvVar("TEST_KEY", "|", "test-value-2")). + To(Equal("test-value-2")) + }) + + it("appends multiple", func() { + Expect(sherpa.AppendToEnvVar("TEST_KEY", "|", "test-value-2", "test-value-3")). + To(Equal("test-value-2|test-value-3")) + }) + }) + + context("With Existing", func() { + it.Before(func() { + Expect(os.Setenv("TEST_KEY", "test-value-1")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("TEST_KEY")).To(Succeed()) + }) + + it("append one", func() { + Expect(sherpa.AppendToEnvVar("TEST_KEY", "|", "test-value-2")). + To(Equal("test-value-1|test-value-2")) + }) + + it("appends multiple", func() { + Expect(sherpa.AppendToEnvVar("TEST_KEY", "|", "test-value-2", "test-value-3")). + To(Equal("test-value-1|test-value-2|test-value-3")) + }) + }) + }) + + context("GetEnvRequired", func() { + it.Before(func() { + Expect(os.Setenv("TEST_KEY", "test-value")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("TEST_KEY")).To(Succeed()) + }) + + it("returns value if set", func() { + Expect(sherpa.GetEnvRequired("TEST_KEY")).To(Equal("test-value")) + }) + + it("returns error if not set", func() { + _, err := sherpa.GetEnvRequired("ANOTHER_KEY") + Expect(err).To(MatchError("$ANOTHER_KEY must be set")) + }) + }) + + context("GetEnvWithDefault", func() { + it.Before(func() { + Expect(os.Setenv("TEST_KEY", "test-value")).To(Succeed()) + }) + + it.After(func() { + Expect(os.Unsetenv("TEST_KEY")).To(Succeed()) + }) + + it("returns value if set", func() { + Expect(sherpa.GetEnvWithDefault("TEST_KEY", "default-value")).To(Equal("test-value")) + }) + + it("returns default value if not set", func() { + Expect(sherpa.GetEnvWithDefault("ANOTHER_KEY", "default-value")).To(Equal("default-value")) + }) + }) +} diff --git a/sherpa/init_test.go b/sherpa/init_test.go index c90dccd..f524e5d 100644 --- a/sherpa/init_test.go +++ b/sherpa/init_test.go @@ -26,6 +26,7 @@ import ( func TestUnit(t *testing.T) { suite := spec.New("libpak/sherpa", spec.Report(report.Terminal{})) suite("CopyFile", testCopyFile) + suite("EnvVar", testEnvVar) suite("FileListing", testFileListing) suite("NodeJS", testNodeJS) suite("Sherpa", testSherpa) From 3a7e6f4f55e5820183735ecc89d48a6905718226 Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Fri, 13 Nov 2020 13:32:34 -0800 Subject: [PATCH 2/3] Update sherpa/env_var.go Signed-off-by: Ben Hale Co-authored-by: Emily Casey --- sherpa/env_var.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sherpa/env_var.go b/sherpa/env_var.go index a39bbc5..ff2b7c2 100644 --- a/sherpa/env_var.go +++ b/sherpa/env_var.go @@ -33,7 +33,7 @@ func AppendToEnvVar(name string, delimiter string, values ...string) string { return strings.Join(e, delimiter) } -// GetEnvRequired returns the value of an environment varible if it exists, otherwire returns an error with a +// GetEnvRequired returns the value of an environment variable if it exists, otherwise returns an error with a // predictable message. func GetEnvRequired(name string) (string, error) { if s, ok := os.LookupEnv(name); ok { From 41d5305ca03f323efb8dea5189749282a8176f8c Mon Sep 17 00:00:00 2001 From: Ben Hale Date: Fri, 13 Nov 2020 13:35:22 -0800 Subject: [PATCH 3/3] Remove Unnecessary Function Since and is the default behavior when multiple predicates are passed, this function while descriptive is redundant. Signed-off-by: Ben Hale --- bindings/resolve.go | 30 --------------------- bindings/resolve_test.go | 56 ---------------------------------------- buildpack.go | 13 ++++++++++ buildpack_test.go | 25 ++++++++++++++++++ 4 files changed, 38 insertions(+), 86 deletions(-) diff --git a/bindings/resolve.go b/bindings/resolve.go index 99e4077..61b1a5a 100644 --- a/bindings/resolve.go +++ b/bindings/resolve.go @@ -26,36 +26,6 @@ import ( // Predicate should return true if it matches a given binding. type Predicate func(bind libcnb.Binding) bool -// And combines multiple predicates logical and-ing their results together. -func And(predicates ...Predicate) Predicate { - return func(bind libcnb.Binding) bool { - if len(predicates) < 1 { - return false - } - - v := predicates[0](bind) - for _, p := range predicates[1:] { - v = v && p(bind) - } - return v - } -} - -// Or combines multiple predicates logical or-ing their results together. -func Or(predicates ...Predicate) Predicate { - return func(bind libcnb.Binding) bool { - if len(predicates) < 1 { - return false - } - - v := predicates[0](bind) - for _, p := range predicates[1:] { - v = v || p(bind) - } - return v - } -} - // 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 { diff --git a/bindings/resolve_test.go b/bindings/resolve_test.go index ab8943e..c200235 100644 --- a/bindings/resolve_test.go +++ b/bindings/resolve_test.go @@ -53,62 +53,6 @@ func testResolve(t *testing.T, context spec.G, it spec.S) { } }) - context("And", func() { - var ( - tr = func(bind libcnb.Binding) bool { - return true - } - - fa = func(bind libcnb.Binding) bool { - return false - } - ) - - it("returns false with no predicates", func() { - Expect(bindings.And()(libcnb.Binding{})).To(BeFalse()) - }) - - it("returns value of single predicate", func() { - Expect(bindings.And(tr)(libcnb.Binding{})).To(BeTrue()) - Expect(bindings.And(fa)(libcnb.Binding{})).To(BeFalse()) - }) - - it("returns and-ed value of multiple predicates", func() { - Expect(bindings.And(tr, tr)(libcnb.Binding{})).To(BeTrue()) - Expect(bindings.And(tr, fa)(libcnb.Binding{})).To(BeFalse()) - Expect(bindings.And(fa, tr)(libcnb.Binding{})).To(BeFalse()) - Expect(bindings.And(fa, fa)(libcnb.Binding{})).To(BeFalse()) - }) - }) - - context("Or", func() { - var ( - tr = func(bind libcnb.Binding) bool { - return true - } - - fa = func(bind libcnb.Binding) bool { - return false - } - ) - - it("returns false with no predicates", func() { - Expect(bindings.Or()(libcnb.Binding{})).To(BeFalse()) - }) - - it("returns value of single predicate", func() { - Expect(bindings.Or(tr)(libcnb.Binding{})).To(BeTrue()) - Expect(bindings.Or(fa)(libcnb.Binding{})).To(BeFalse()) - }) - - it("returns or-ed value of multiple predicates", func() { - Expect(bindings.Or(tr, tr)(libcnb.Binding{})).To(BeTrue()) - Expect(bindings.Or(tr, fa)(libcnb.Binding{})).To(BeTrue()) - Expect(bindings.Or(fa, tr)(libcnb.Binding{})).To(BeTrue()) - Expect(bindings.Or(fa, fa)(libcnb.Binding{})).To(BeFalse()) - }) - }) - context("Resolve", func() { context("no predicate", func() { it("returns all bindings", func() { diff --git a/buildpack.go b/buildpack.go index 4391438..44f756f 100644 --- a/buildpack.go +++ b/buildpack.go @@ -20,6 +20,7 @@ import ( "fmt" "os" "sort" + "strconv" "strings" "github.com/Masterminds/semver/v3" @@ -350,6 +351,18 @@ func (c *ConfigurationResolver) Resolve(name string) (string, bool) { return "", false } +// ResolveBool resolves a boolean value for a configuration option. Returns true for 1, t, T, TRUE, true, True. Returns +// false for all other values or unset. +func (c *ConfigurationResolver) ResolveBool(name string) bool { + s, _ := c.Resolve(name) + t, err := strconv.ParseBool(s) + if err != nil { + return false + } + + return t +} + // DependencyResolver provides functionality for resolving a dependency given a collection of constraints. type DependencyResolver struct { diff --git a/buildpack_test.go b/buildpack_test.go index 4b68105..7acd6df 100644 --- a/buildpack_test.go +++ b/buildpack_test.go @@ -130,16 +130,23 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { Configurations: []libpak.BuildpackConfiguration{ {Name: "TEST_KEY_1", Default: "test-default-value-1"}, {Name: "TEST_KEY_2", Default: "test-default-value-2"}, + {Name: "TEST_BOOL_3", Default: "true"}, + {Name: "TEST_BOOL_4", Default: "false"}, + {Name: "TEST_BOOL_6", Default: "test-value"}, }, } ) it.Before(func() { Expect(os.Setenv("TEST_KEY_1", "test-value-1")).To(Succeed()) + Expect(os.Setenv("TEST_BOOL_1", "true")).To(Succeed()) + Expect(os.Setenv("TEST_BOOL_2", "false")).To(Succeed()) }) it.After(func() { Expect(os.Unsetenv("TEST_KEY_1")).To(Succeed()) + Expect(os.Unsetenv("TEST_BOOL_1")).To(Succeed()) + Expect(os.Unsetenv("TEST_BOOL_2")).To(Succeed()) }) it("returns configured value", func() { @@ -159,6 +166,24 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { Expect(v).To(Equal("")) Expect(ok).To(BeFalse()) }) + + it("returns configured bool", func() { + Expect(resolver.ResolveBool("TEST_BOOL_1")).To(BeTrue()) + Expect(resolver.ResolveBool("TEST_BOOL_2")).To(BeFalse()) + }) + + it("returns default bool", func() { + Expect(resolver.ResolveBool("TEST_BOOL_3")).To(BeTrue()) + Expect(resolver.ResolveBool("TEST_BOOL_4")).To(BeFalse()) + }) + + it("returns false for unset", func() { + Expect(resolver.ResolveBool("TEST_BOOL_5")).To(BeFalse()) + }) + + it("return false for invalid", func() { + Expect(resolver.ResolveBool("TEST_BOOL_6")).To(BeFalse()) + }) }) context("DependencyResolver", func() {