Skip to content

Commit 99633fa

Browse files
authored
Acceptance test for file based service bindings (#1374)
Acceptance tests for file based service bindings * pushes buildpack and CNB app, enables app feature "file-based-service-bindings" and binds a user-provided service * then checks service binding files via app /file endpoint
1 parent 2848e56 commit 99633fa

File tree

12 files changed

+199
-0
lines changed

12 files changed

+199
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ include_app_syslog_tcp
120120
* `include_deployments`: Flag to include tests for the cloud controller rolling deployments. V3 must also be enabled.
121121
* `include_detect`: Flag to include tests in the detect group.
122122
* `include_docker`: Flag to include tests related to running Docker apps on Diego. Diego must be deployed and the CC API diego_docker feature flag must be enabled for these tests to pass.
123+
* `include_file_based_service_bindings`: Flag to include file-based service binding tests. For details, see [RFC0030](https://github.com/cloudfoundry/community/blob/main/toc/rfc/rfc-0030-add-support-for-file-based-service-binding.md)
123124
* `include_http2_routing`: Flag to include the HTTP/2 Routing tests.
124125
* `include_internet_dependent`: Flag to include tests that require the deployment to have internet access.
125126
* `include_isolation_segments`: Flag to include isolation segment tests.
@@ -358,6 +359,7 @@ Test Group Name| Description
358359
`cnb` | Tests our ability to use cloud native buildpacks.
359360
`detect` | Tests the ability of the platform to detect the correct buildpack for compiling an application if no buildpack is explicitly specified.
360361
`docker`| Tests our ability to run docker containers on Diego and that we handle docker metadata correctly.
362+
`file-based service bindings`| Tests file-based service bindings for a buildpack and a CNB app.
361363
`internet_dependent`| Tests the feature of being able to specify a buildpack via a Github URL. As such, this depends on your Cloud Foundry application containers having access to the Internet. You should take into account the configuration of the network into which you've deployed your Cloud Foundry, as well as any security group settings applied to application containers.
362364
`isolation_segments` | This test group requires that Diego be deployed with a minimum of 2 cells. One of those cells must have been deployed with a `placement_tag`. If the deployment has been deployed with a routing isolation segment, `isolation_segment_domain` must also be set. For more information, please refer to the [Isolation Segments documentation](https://docs.cloudfoundry.org/adminguide/isolation-segments.html).
363365
`route_services` | Tests the [Route Services](https://docs.cloudfoundry.org/services/route-services.html) feature of Cloud Foundry.

assets/catnip/Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: catnip

assets/catnip/file/file.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package file
2+
3+
import (
4+
"fmt"
5+
"github.com/go-chi/chi/v5"
6+
"net/http"
7+
"net/url"
8+
"os"
9+
)
10+
11+
func FileHandler(res http.ResponseWriter, req *http.Request) {
12+
filename := chi.URLParam(req, "filename")
13+
decodedFilename, err := url.PathUnescape(filename)
14+
if err != nil {
15+
http.Error(res, fmt.Sprintf("Cannot unescape file name: %s", filename), http.StatusBadRequest)
16+
return
17+
}
18+
19+
_, err = os.Stat(decodedFilename)
20+
if err != nil {
21+
http.Error(res, http.StatusText(404) + ": " + decodedFilename, 404)
22+
return
23+
}
24+
25+
content, err := os.ReadFile(decodedFilename)
26+
if err != nil {
27+
http.Error(res, http.StatusText(500) + ": " + err.Error(), 500)
28+
return
29+
}
30+
res.Write(append(content, '\n'))
31+
}

assets/catnip/router/router.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/cloudfoundry/cf-acceptance-tests/assets/catnip/session"
1616
"github.com/cloudfoundry/cf-acceptance-tests/assets/catnip/signal"
1717
"github.com/cloudfoundry/cf-acceptance-tests/assets/catnip/text"
18+
"github.com/cloudfoundry/cf-acceptance-tests/assets/catnip/file"
1819
)
1920

2021
func New(out io.Writer, clock clock.Clock) *chi.Mux {
@@ -38,6 +39,7 @@ func New(out io.Writer, clock clock.Clock) *chi.Mux {
3839
r.Get("/curl/{host}", linux.CurlHandler)
3940
r.Get("/curl/{host}/", linux.CurlHandler)
4041
r.Get("/curl/{host}/{port}", linux.CurlHandler)
42+
r.Get("/file/{filename}", file.FileHandler)
4143

4244
return r
4345
}

cats_suite_helpers/cats_suite_helpers.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,25 @@ func CNBDescribe(description string, callback func()) bool {
109109
})
110110
}
111111

112+
const (
113+
BuildpackLifecycle string = "buildpack"
114+
CNBLifecycle = "CNB"
115+
)
116+
117+
func FileBasedServiceBindingsDescribe(description string, lifecycle string, callback func()) bool {
118+
return Describe(fmt.Sprintf("[file-based service bindings]", lifecycle), func() {
119+
BeforeEach(func() {
120+
if lifecycle == BuildpackLifecycle && !Config.GetIncludeFileBasedServiceBindings() {
121+
Skip(skip_messages.SkipFileBasedServiceBindingsBuildpackApp)
122+
}
123+
if lifecycle == CNBLifecycle && (!Config.GetIncludeFileBasedServiceBindings() || !Config.GetIncludeCNB()) {
124+
Skip(skip_messages.SkipFileBasedServiceBindingsCnbApp)
125+
}
126+
})
127+
Describe(description, callback)
128+
})
129+
}
130+
112131
func InternetDependentDescribe(description string, callback func()) bool {
113132
return Describe("[internet_dependent]", func() {
114133
BeforeEach(func() {

cats_suite_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
_ "github.com/cloudfoundry/cf-acceptance-tests/credhub"
1919
_ "github.com/cloudfoundry/cf-acceptance-tests/detect"
2020
_ "github.com/cloudfoundry/cf-acceptance-tests/docker"
21+
_ "github.com/cloudfoundry/cf-acceptance-tests/file_based_service_bindings"
2122
_ "github.com/cloudfoundry/cf-acceptance-tests/http2_routing"
2223
_ "github.com/cloudfoundry/cf-acceptance-tests/internet_dependent"
2324
_ "github.com/cloudfoundry/cf-acceptance-tests/isolation_segments"
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
package file_based_service_bindings
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
. "github.com/cloudfoundry/cf-acceptance-tests/cats_suite_helpers"
7+
"github.com/cloudfoundry/cf-acceptance-tests/helpers/app_helpers"
8+
"github.com/cloudfoundry/cf-acceptance-tests/helpers/assets"
9+
"github.com/cloudfoundry/cf-acceptance-tests/helpers/random_name"
10+
"github.com/cloudfoundry/cf-acceptance-tests/services"
11+
"github.com/cloudfoundry/cf-test-helpers/v2/cf"
12+
"github.com/cloudfoundry/cf-test-helpers/v2/generator"
13+
"github.com/cloudfoundry/cf-test-helpers/v2/helpers"
14+
. "github.com/onsi/ginkgo/v2"
15+
. "github.com/onsi/gomega"
16+
. "github.com/onsi/gomega/gexec"
17+
"strings"
18+
)
19+
20+
var _ = FileBasedServiceBindingsDescribe("Enabling file based service binding for a buildpack app", BuildpackLifecycle, func() {
21+
callback(BuildpackLifecycle)
22+
})
23+
24+
var _ = FileBasedServiceBindingsDescribe("Enabling file based service binding for a CNB app", CNBLifecycle, func() {
25+
callback(CNBLifecycle)
26+
})
27+
28+
var callback = func(lifecycle string) {
29+
var appName, serviceName string
30+
31+
getEncodedFilepath := func(serviceName string, fileName string) string {
32+
path := fmt.Sprintf("/etc/cf-service-bindings/%s/%s", serviceName, fileName)
33+
return strings.Replace(path, "/", "%2F", -1)
34+
}
35+
36+
checkFileContent := func(fileName string, content string) {
37+
curlResponse := helpers.CurlApp(Config, appName, "/file/"+getEncodedFilepath(serviceName, fileName))
38+
Expect(curlResponse).Should(ContainSubstring(content))
39+
}
40+
41+
getServiceInstanceGuid := func(serviceName string) string {
42+
serviceGuidCmd := cf.Cf("service", serviceName, "--guid")
43+
Eventually(serviceGuidCmd).Should(Exit(0))
44+
return strings.TrimSpace(string(serviceGuidCmd.Out.Contents()))
45+
}
46+
47+
getServiceBindingGuid := func(appGuid string, instanceGuid string) string {
48+
jsonResults := services_test.Response{}
49+
bindingCurl := cf.Cf("curl", fmt.Sprintf("/v3/service_credential_bindings?app_guids=%s&service_instance_guids=%s", appGuid, instanceGuid)).Wait()
50+
Expect(bindingCurl).To(Exit(0))
51+
Expect(json.Unmarshal(bindingCurl.Out.Contents(), &jsonResults)).NotTo(HaveOccurred())
52+
Expect(len(jsonResults.Resources)).To(BeNumerically(">", 0), "Expected to find at least one service binding.")
53+
return jsonResults.Resources[0].GUID
54+
}
55+
56+
BeforeEach(func() {
57+
appName = random_name.CATSRandomName("APP")
58+
serviceName = generator.PrefixedRandomName("cats", "svin") // uppercase characters are not valid
59+
})
60+
61+
AfterEach(func() {
62+
app_helpers.AppReport(appName)
63+
Eventually(cf.Cf("unbind-service", appName, serviceName).Wait()).Should(Exit(0))
64+
Eventually(cf.Cf("delete", appName, "-f")).Should(Exit(0))
65+
Eventually(cf.Cf("delete-service", serviceName, "-f").Wait()).Should(Exit(0))
66+
})
67+
68+
It("creates the required files in the app container", func() {
69+
tags := "list, of, tags"
70+
creds := `{"username": "admin", "password":"pa55woRD"}`
71+
Expect(cf.Cf("create-user-provided-service", serviceName, "-p", creds, "-t", tags).Wait()).To(Exit(0))
72+
serviceGuid := getServiceInstanceGuid(serviceName)
73+
74+
if lifecycle == BuildpackLifecycle {
75+
Expect(cf.Cf("create-app", appName).Wait()).To(Exit(0))
76+
}
77+
if lifecycle == CNBLifecycle {
78+
Expect(cf.Cf("create-app", appName, "--app-type", "cnb", "--buildpack", Config.GetGoBuildpackName()).Wait()).To(Exit(0))
79+
}
80+
appGuid := app_helpers.GetAppGuid(appName)
81+
82+
appFeatureUrl := fmt.Sprintf("/v3/apps/%s/features/file-based-service-bindings", appGuid)
83+
Expect(cf.Cf("curl", appFeatureUrl, "-X", "PATCH", "-d", `{"enabled": true}`).Wait()).To(Exit(0))
84+
85+
Expect(cf.Cf("bind-service", appName, serviceName).Wait()).To(Exit(0))
86+
87+
if lifecycle == BuildpackLifecycle {
88+
Expect(cf.Cf(app_helpers.CatnipWithArgs(
89+
appName,
90+
"-m", DEFAULT_MEMORY_LIMIT)...,
91+
).Wait(Config.CfPushTimeoutDuration())).To(Exit(0))
92+
}
93+
if lifecycle == CNBLifecycle {
94+
Expect(cf.Cf(
95+
"push",
96+
appName,
97+
"--lifecycle", "cnb",
98+
"--buildpack", Config.GetCNBGoBuildpackName(),
99+
"-m", DEFAULT_MEMORY_LIMIT,
100+
"-p", assets.NewAssets().CatnipSrc,
101+
).Wait(Config.CfPushTimeoutDuration())).To(Exit(0))
102+
}
103+
104+
checkFileContent("binding-guid", getServiceBindingGuid(appGuid, serviceGuid))
105+
checkFileContent("instance-guid", serviceGuid)
106+
checkFileContent("instance-name", serviceName)
107+
checkFileContent("label", "user-provided")
108+
checkFileContent("name", serviceName)
109+
checkFileContent("password", "pa55woRD")
110+
checkFileContent("provider", "user-provided")
111+
checkFileContent("tags", `["list","of","tags"]`)
112+
checkFileContent("type", "user-provided")
113+
checkFileContent("username", "admin")
114+
})
115+
}

helpers/assets/assets.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package assets
33
type Assets struct {
44
AspClassic string
55
Catnip string
6+
CatnipSrc string
67
CredHubEnabledApp string
78
CredHubServiceBroker string
89
Dora string
@@ -49,6 +50,7 @@ func NewAssets() Assets {
4950
return Assets{
5051
AspClassic: "assets/asp-classic",
5152
Catnip: "assets/catnip/bin",
53+
CatnipSrc: "assets/catnip",
5254
CredHubEnabledApp: "assets/credhub-enabled-app/credhub-enabled-app.jar",
5355
CredHubServiceBroker: "assets/credhub-service-broker",
5456
Dora: "assets/dora",

helpers/config/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type CatsConfig interface {
1313
GetIncludeDetect() bool
1414
GetIncludeDocker() bool
1515
GetIncludeCNB() bool
16+
GetIncludeFileBasedServiceBindings() bool
1617
GetIncludeInternetDependent() bool
1718
GetIncludePrivateDockerRegistry() bool
1819
GetIncludeRouteServices() bool
@@ -73,6 +74,7 @@ type CatsConfig interface {
7374
GetNamePrefix() string
7475
GetNginxBuildpackName() string
7576
GetNodejsBuildpackName() string
77+
GetCNBGoBuildpackName() string
7678
GetCNBNodejsBuildpackName() string
7779
GetPrivateDockerRegistryImage() string
7880
GetPrivateDockerRegistryUsername() string

helpers/config/config_struct.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type config struct {
6363
RubyBuildpackName *string `json:"ruby_buildpack_name"`
6464
StaticFileBuildpackName *string `json:"staticfile_buildpack_name"`
6565

66+
CNBGoBuildpackName *string `json:"cnb_go_buildpack_name"`
6667
CNBNodejsBuildpackName *string `json:"cnb_nodejs_buildpack_name"`
6768

6869
VolumeServiceName *string `json:"volume_service_name"`
@@ -78,6 +79,7 @@ type config struct {
7879
IncludeDetect *bool `json:"include_detect"`
7980
IncludeDocker *bool `json:"include_docker"`
8081
IncludeCNB *bool `json:"include_cnb"`
82+
IncludeFileBasedServiceBindings *bool `json:"include_file_based_service_bindings"`
8183
IncludeInternetDependent *bool `json:"include_internet_dependent"`
8284
IncludeIsolationSegments *bool `json:"include_isolation_segments"`
8385
IncludePrivateDockerRegistry *bool `json:"include_private_docker_registry"`
@@ -164,6 +166,7 @@ func getDefaults() config {
164166
defaults.RubyBuildpackName = ptrToString("ruby_buildpack")
165167
defaults.StaticFileBuildpackName = ptrToString("staticfile_buildpack")
166168

169+
defaults.CNBGoBuildpackName = ptrToString("docker://gcr.io/paketo-buildpacks/go:latest")
167170
defaults.CNBNodejsBuildpackName = ptrToString("docker://gcr.io/paketo-buildpacks/nodejs:latest")
168171

169172
defaults.IncludeAppSyslogTCP = ptrToBool(true)
@@ -180,6 +183,7 @@ func getDefaults() config {
180183
defaults.CredhubClientSecret = ptrToString("")
181184
defaults.IncludeDocker = ptrToBool(false)
182185
defaults.IncludeCNB = ptrToBool(false)
186+
defaults.IncludeFileBasedServiceBindings = ptrToBool(false)
183187
defaults.IncludeInternetDependent = ptrToBool(false)
184188
defaults.IncludeIsolationSegments = ptrToBool(false)
185189
defaults.IncludeTCPIsolationSegments = ptrToBool(false)
@@ -412,6 +416,9 @@ func validateConfig(config *config) error {
412416
if config.StaticFileBuildpackName == nil {
413417
errs = errors.Join(errs, fmt.Errorf("* 'staticfile_buildpack_name' must not be null"))
414418
}
419+
if config.CNBGoBuildpackName == nil {
420+
errs = errors.Join(errs, fmt.Errorf("* 'cnb_go_buildpack_name' must not be null"))
421+
}
415422
if config.CNBNodejsBuildpackName == nil {
416423
errs = errors.Join(errs, fmt.Errorf("* 'cnb_nodejs_buildpack_name' must not be null"))
417424
}
@@ -430,6 +437,9 @@ func validateConfig(config *config) error {
430437
if config.IncludeDocker == nil {
431438
errs = errors.Join(errs, fmt.Errorf("* 'include_docker' must not be null"))
432439
}
440+
if config.IncludeFileBasedServiceBindings == nil {
441+
errs = errors.Join(errs, fmt.Errorf("* 'include_file_based_service_bindings' must not be null"))
442+
}
433443
if config.IncludeCNB == nil {
434444
errs = errors.Join(errs, fmt.Errorf("* 'include_cnb' must not be null"))
435445
}
@@ -935,6 +945,8 @@ func (c *config) GetIncludeCNB() bool {
935945
return *c.IncludeCNB
936946
}
937947

948+
func (c *config) GetIncludeFileBasedServiceBindings() bool { return *c.IncludeFileBasedServiceBindings }
949+
938950
func (c *config) GetIncludeInternetDependent() bool {
939951
return *c.IncludeInternetDependent
940952
}
@@ -1087,6 +1099,10 @@ func (c *config) GetStaticFileBuildpackName() string {
10871099
return *c.StaticFileBuildpackName
10881100
}
10891101

1102+
func (c *config) GetCNBGoBuildpackName() string {
1103+
return *c.CNBGoBuildpackName
1104+
}
1105+
10901106
func (c *config) GetCNBNodejsBuildpackName() string {
10911107
return *c.CNBNodejsBuildpackName
10921108
}

0 commit comments

Comments
 (0)