Skip to content

Commit dcb3d01

Browse files
committed
Add support for setting DATABASE_URL from vcap services
- This is replicating behavior that exists in buildpack app lifecycle Signed-off-by: Tom Kennedy <tom.kennedy@broadcom.com>
1 parent 1f2925d commit dcb3d01

4 files changed

Lines changed: 187 additions & 2 deletions

File tree

cmd/builder/cli/cli.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"code.cloudfoundry.org/cnbapplifecycle/pkg/archive"
1414
"code.cloudfoundry.org/cnbapplifecycle/pkg/buildpacks"
1515
"code.cloudfoundry.org/cnbapplifecycle/pkg/credhub"
16+
"code.cloudfoundry.org/cnbapplifecycle/pkg/databaseuri"
1617
"code.cloudfoundry.org/cnbapplifecycle/pkg/errors"
1718
"code.cloudfoundry.org/cnbapplifecycle/pkg/keychain"
1819
"code.cloudfoundry.org/cnbapplifecycle/pkg/log"
@@ -54,7 +55,6 @@ var (
5455
systemBuildpacksDir string
5556
extensionsDir string
5657
downloadCacheDir string
57-
err error
5858
credhubConnectionAttempts int
5959
credhubRetryDelay time.Duration
6060
)
@@ -95,7 +95,20 @@ var builderCmd = &cobra.Command{
9595

9696
if err := credhub.InterpolateServiceRefs(credhubConnectionAttempts, credhubRetryDelay); err != nil {
9797
logger.Error(err.Error())
98-
return errors.ErrLaunching
98+
return errors.ErrGenericBuild
99+
}
100+
101+
databaseUrl, err := databaseuri.ParseDatabaseURI(os.Getenv("VCAP_SERVICES"))
102+
if err != nil {
103+
logger.Errorf("failed to parse database URI, error: %s\n", err.Error())
104+
return errors.ErrGenericBuild
105+
}
106+
if databaseUrl != "" {
107+
err = os.Setenv("DATABASE_URL", databaseUrl)
108+
if err != nil {
109+
logger.Errorf("Unable to set DATABASE_URL envirionment variable: %v", err)
110+
return errors.ErrGenericBuild
111+
}
99112
}
100113

101114
tempDirs := map[string]*string{

cmd/launcher/cli/cli.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717

1818
builderCli "code.cloudfoundry.org/cnbapplifecycle/cmd/builder/cli"
1919
"code.cloudfoundry.org/cnbapplifecycle/pkg/credhub"
20+
"code.cloudfoundry.org/cnbapplifecycle/pkg/databaseuri"
2021
"code.cloudfoundry.org/cnbapplifecycle/pkg/errors"
2122
"code.cloudfoundry.org/cnbapplifecycle/pkg/log"
2223
)
@@ -93,6 +94,19 @@ func Launch(osArgs []string, theLauncher TheLauncher) error {
9394
return errors.ErrLaunching
9495
}
9596

97+
databaseUrl, err := databaseuri.ParseDatabaseURI(os.Getenv("VCAP_SERVICES"))
98+
if err != nil {
99+
logger.Errorf("failed to parse database URI, error: %s\n", err.Error())
100+
return errors.ErrLaunching
101+
}
102+
if databaseUrl != "" {
103+
err = os.Setenv("DATABASE_URL", databaseUrl)
104+
if err != nil {
105+
logger.Errorf("Unable to set DATABASE_URL envirionment variable: %v", err)
106+
return errors.ErrLaunching
107+
}
108+
}
109+
96110
var self string
97111
var isSidecar bool
98112
if len(osArgs) > 1 {

pkg/databaseuri/databaseuri.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package databaseuri
2+
3+
import (
4+
"encoding/json"
5+
"net/url"
6+
)
7+
8+
func ParseDatabaseURI(services string) (string, error) {
9+
if services == "" {
10+
return "", nil
11+
}
12+
13+
data := map[string][]struct {
14+
Credentials struct {
15+
Uri string `json:"uri"`
16+
} `json:"credentials"`
17+
}{}
18+
if err := json.Unmarshal([]byte(services), &data); err != nil {
19+
return "", err
20+
}
21+
22+
var creds []string
23+
for _, v1 := range data {
24+
for _, v2 := range v1 {
25+
if v2.Credentials.Uri != "" {
26+
creds = append(creds, v2.Credentials.Uri)
27+
}
28+
}
29+
}
30+
31+
schemes := map[string]string{
32+
"mysql": "mysql2",
33+
"mysql2": "",
34+
"postgres": "",
35+
"postgresql": "postgres",
36+
}
37+
for _, service_uri := range creds {
38+
if uri, err := url.Parse(service_uri); err == nil {
39+
if val, ok := schemes[uri.Scheme]; ok {
40+
if val != "" {
41+
uri.Scheme = val
42+
}
43+
return uri.String(), nil
44+
}
45+
}
46+
}
47+
return "", nil
48+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package databaseuri_test
2+
3+
import (
4+
"testing"
5+
6+
"code.cloudfoundry.org/cnbapplifecycle/pkg/databaseuri"
7+
. "github.com/onsi/ginkgo/v2"
8+
. "github.com/onsi/gomega"
9+
)
10+
11+
func TestDatabaseuri(t *testing.T) {
12+
RegisterFailHandler(Fail)
13+
RunSpecs(t, "Databaseuri Suite")
14+
}
15+
16+
var _ = Describe("ParseDatabaseURI", func() {
17+
It("ignores services without credentials.uri", func() {
18+
services := `{"eg":[{}]}`
19+
uri, err := databaseuri.ParseDatabaseURI(services)
20+
Expect(err).NotTo(HaveOccurred())
21+
Expect(uri).To(BeEmpty())
22+
})
23+
24+
It("returns empty when there are non relational database services", func() {
25+
services := `{"eg":[{"credentials":{"uri":"sendgrid://foo:bar@host/db"}}]}`
26+
uri, err := databaseuri.ParseDatabaseURI(services)
27+
Expect(err).NotTo(HaveOccurred())
28+
Expect(uri).To(BeEmpty())
29+
})
30+
31+
It("returns empty when there are no services", func() {
32+
services := "{}"
33+
uri, err := databaseuri.ParseDatabaseURI(services)
34+
Expect(err).NotTo(HaveOccurred())
35+
Expect(uri).To(BeEmpty())
36+
})
37+
38+
It("returns empty when services are empty", func() {
39+
services := ""
40+
uri, err := databaseuri.ParseDatabaseURI(services)
41+
Expect(err).NotTo(HaveOccurred())
42+
Expect(uri).To(BeEmpty())
43+
})
44+
45+
Context("when there are relational database services", func() {
46+
Context("with a mysql URI", func() {
47+
It("changes the scheme to mysql2", func() {
48+
services := `{"eg":[{"credentials":{"uri":"mysql://username:password@host/db"}}]}`
49+
uri, err := databaseuri.ParseDatabaseURI(services)
50+
Expect(err).NotTo(HaveOccurred())
51+
Expect(uri).To(Equal("mysql2://username:password@host/db"))
52+
})
53+
})
54+
Context("with a mysql2 URI", func() {
55+
It("returns the URI unchanged", func() {
56+
services := `{"eg":[{"credentials":{"uri":"mysql2://username:password@host/db"}}]}`
57+
uri, err := databaseuri.ParseDatabaseURI(services)
58+
Expect(err).NotTo(HaveOccurred())
59+
Expect(uri).To(Equal("mysql2://username:password@host/db"))
60+
})
61+
})
62+
Context("with a postgres URI", func() {
63+
It("returns the URI unchanged", func() {
64+
services := `{"eg":[{"credentials":{"uri":"postgres://username:password@host/db"}}]}`
65+
uri, err := databaseuri.ParseDatabaseURI(services)
66+
Expect(err).NotTo(HaveOccurred())
67+
Expect(uri).To(Equal("postgres://username:password@host/db"))
68+
})
69+
})
70+
Context("with a postgresql URI", func() {
71+
It("changes the scheme to postgres", func() {
72+
services := `{"eg":[{"credentials":{"uri":"postgresql://username:password@host/db"}}]}`
73+
uri, err := databaseuri.ParseDatabaseURI(services)
74+
Expect(err).NotTo(HaveOccurred())
75+
Expect(uri).To(Equal("postgres://username:password@host/db"))
76+
})
77+
})
78+
Context("with multiple relational database URIs", func() {
79+
It("returns the first one found", func() {
80+
services := `{
81+
"abc":[{"credentials":{"uri":"postgres://username:password@host/db1"}}],
82+
"def":[{"credentials":{"uri":"postgres://username:password@host/db2"}}]
83+
}`
84+
uri, err := databaseuri.ParseDatabaseURI(services)
85+
Expect(err).NotTo(HaveOccurred())
86+
Expect(uri).To(Equal("postgres://username:password@host/db1"))
87+
})
88+
})
89+
Context("with an invalid URI", func() {
90+
It("returns an empty string", func() {
91+
services := `{"eg":[{"credentials":{"uri":"postgresql://invalid:password@host/%a"}}]}`
92+
uri, err := databaseuri.ParseDatabaseURI(services)
93+
Expect(err).NotTo(HaveOccurred())
94+
Expect(uri).To(Equal(""))
95+
})
96+
})
97+
})
98+
99+
It("handles multiple services correctly", func() {
100+
services := `{
101+
"abc":[{"credentials":{"uri":"u1"}}],
102+
"def":[{"other":"data"}],
103+
"ghi":[{"credentials":{"other":"data"}}],
104+
"jkl":[{},{"credentials":{"uri":"mysql://username:password@host/db"}}]
105+
}`
106+
uri, err := databaseuri.ParseDatabaseURI(services)
107+
Expect(err).NotTo(HaveOccurred())
108+
Expect(uri).To(Equal("mysql2://username:password@host/db"))
109+
})
110+
})

0 commit comments

Comments
 (0)