From 60df949646066458cd9016e53dc5d7a128e2429f Mon Sep 17 00:00:00 2001 From: Ralf Pannemans Date: Fri, 5 May 2023 04:12:05 +0200 Subject: [PATCH] Read bindings from VCAP_SERVICES (#228) Read bindings from VCAP_SERVICES Signed-off-by: Ralf Pannemans --- platform.go | 40 +++++++++++++++++++++++++++++ platform_test.go | 51 +++++++++++++++++++++++++++++++++++++ testdata/vcap_services.json | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+) create mode 100644 testdata/vcap_services.json diff --git a/platform.go b/platform.go index 937cbcf..0f52485 100644 --- a/platform.go +++ b/platform.go @@ -17,6 +17,7 @@ package libcnb import ( + "encoding/json" "fmt" "os" "path/filepath" @@ -41,6 +42,9 @@ const ( // See the Service Binding Specification for Kubernetes for more details - https://k8s-service-bindings.github.io/spec/ EnvServiceBindings = "SERVICE_BINDING_ROOT" + // EnvVcapServices is the name of the environment variable that contains the bindings in cloudfoundry + EnvVcapServices = "VCAP_SERVICES" + // EnvCNBBindings is the name of the environment variable that contains the path to the CNB bindings directory. // See the CNB bindings extension spec for more details - https://github.com/buildpacks/spec/blob/main/extensions/bindings.md // @@ -113,6 +117,34 @@ func NewBindingFromPath(path string) (Binding, error) { return NewBinding(filepath.Base(path), path, secret), nil } +type vcapServicesBinding struct { + Name string `json:"name"` + Credentials map[string]string `json:"credentials"` +} + +// NewBindingsFromVcapServicesEnv creates a new instance from all the bindings given from the VCAP_SERVICES. +func NewBindingsFromVcapServicesEnv(content string) (Bindings, error) { + var contentTyped map[string][]vcapServicesBinding + + err := json.Unmarshal([]byte(content), &contentTyped) + if err != nil { + return Bindings{}, nil + } + + bindings := Bindings{} + for t, bArray := range contentTyped { + for _, b := range bArray { + bindings = append(bindings, Binding{ + Name: b.Name, + Type: t, + Secret: b.Credentials, + }) + } + } + + return bindings, nil +} + func (b Binding) String() string { var s []string for k := range b.Secret { @@ -163,6 +195,10 @@ func NewBindingsForLaunch() (Bindings, error) { return NewBindingsFromPath(path) } + if content, ok := os.LookupEnv(EnvVcapServices); ok { + return NewBindingsFromVcapServicesEnv(content) + } + return Bindings{}, nil } @@ -201,6 +237,10 @@ func NewBindingsForBuild(platformDir string) (Bindings, error) { if path, ok := os.LookupEnv(EnvCNBBindings); ok { return NewBindingsFromPath(path) } + + if content, ok := os.LookupEnv(EnvVcapServices); ok { + return NewBindingsFromVcapServicesEnv(content) + } return NewBindingsFromPath(filepath.Join(platformDir, "bindings")) } diff --git a/platform_test.go b/platform_test.go index f935f76..735c977 100644 --- a/platform_test.go +++ b/platform_test.go @@ -40,6 +40,57 @@ func testPlatform(t *testing.T, context spec.G, it spec.S) { path = filepath.Join(platformPath, "bindings") }) + context("Cloudfoundry VCAP_SERVICES", func() { + context("Build", func() { + it("creates a bindings from VCAP_SERVICES", func() { + content, err := os.ReadFile("testdata/vcap_services.json") + Expect(err).NotTo(HaveOccurred()) + t.Setenv(libcnb.EnvVcapServices, string(content)) + + bindings, err := libcnb.NewBindingsForBuild("") + Expect(err).NotTo(HaveOccurred()) + + Expect(bindings).To(HaveLen(2)) + + types := []string{bindings[0].Type, bindings[1].Type} + Expect(types).To(ContainElements("elephantsql", "sendgrid")) + }) + + it("creates empty bindings from empty VCAP_SERVICES", func() { + t.Setenv(libcnb.EnvVcapServices, "{}") + + bindings, err := libcnb.NewBindingsForBuild("") + Expect(err).NotTo(HaveOccurred()) + + Expect(bindings).To(HaveLen(0)) + }) + }) + + context("Launch", func() { + it("creates a bindings from VCAP_SERVICES", func() { + content, err := os.ReadFile("testdata/vcap_services.json") + Expect(err).NotTo(HaveOccurred()) + t.Setenv(libcnb.EnvVcapServices, string(content)) + + bindings, err := libcnb.NewBindingsForLaunch() + Expect(err).NotTo(HaveOccurred()) + + Expect(bindings).To(HaveLen(2)) + types := []string{bindings[0].Type, bindings[1].Type} + Expect(types).To(ContainElements("elephantsql", "sendgrid")) + }) + + it("creates empty bindings from empty VCAP_SERVICES", func() { + t.Setenv(libcnb.EnvVcapServices, "{}") + + bindings, err := libcnb.NewBindingsForLaunch() + Expect(err).NotTo(HaveOccurred()) + + Expect(bindings).To(HaveLen(0)) + }) + }) + }) + context("CNB Bindings", func() { it.Before(func() { Expect(os.MkdirAll(filepath.Join(path, "alpha", "metadata"), 0755)).To(Succeed()) diff --git a/testdata/vcap_services.json b/testdata/vcap_services.json new file mode 100644 index 0000000..f9fbe60 --- /dev/null +++ b/testdata/vcap_services.json @@ -0,0 +1,44 @@ +{ + "elephantsql": [ + { + "name": "elephantsql-binding-c6c60", + "binding_guid": "44ceb72f-100b-4f50-87a2-7809c8b42b8d", + "binding_name": "elephantsql-binding-c6c60", + "instance_guid": "391308e8-8586-4c42-b464-c7831aa2ad22", + "instance_name": "elephantsql-c6c60", + "label": "elephantsql", + "tags": [ + "postgres", + "postgresql", + "relational" + ], + "plan": "turtle", + "credentials": { + "uri": "postgres://exampleuser:examplepass@postgres.example.com:5432/exampleuser" + }, + "syslog_drain_url": null, + "volume_mounts": [] + } + ], + "sendgrid": [ + { + "name": "mysendgrid", + "binding_guid": "6533b1b6-7916-488d-b286-ca33d3fa0081", + "binding_name": null, + "instance_guid": "8c907d0f-ec0f-44e4-87cf-e23c9ba3925d", + "instance_name": "mysendgrid", + "label": "sendgrid", + "tags": [ + "smtp" + ], + "plan": "free", + "credentials": { + "hostname": "smtp.example.com", + "username": "QvsXMbJ3rK", + "password": "HCHMOYluTv" + }, + "syslog_drain_url": null, + "volume_mounts": [] + } + ] +} \ No newline at end of file