Skip to content

Commit 33e189e

Browse files
blgmzucchinidev
andauthored
test: automatically download brokerpaks for upgrade tests (#623)
Setting up upgrade tests is painful and error prone as one has to manually download a brokerpak, its associated ".envrc" file and corresponding broker version. This has now been automated, so it's much easier to run upgrade tests. This is a clone of AWS PR: cloudfoundry/csb-brokerpak-aws#1251 Co-authored-by: Andrea Zucchini <[email protected]>
1 parent ef5679e commit 33e189e

File tree

12 files changed

+295
-13
lines changed

12 files changed

+295
-13
lines changed

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Contributing to CSB Brokerpak for AWS
1+
# Contributing to CSB Brokerpak for Azure
22

33
The Cloud Service Broker team uses GitHub and accepts contributions via
44
[pull request](https://help.github.com/articles/using-pull-requests).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Package brokerpaks is used for downloading brokerpaks and associated resources for upgrade tests
2+
package brokerpaks
3+
4+
import (
5+
"fmt"
6+
"io"
7+
"os"
8+
"path/filepath"
9+
"regexp"
10+
)
11+
12+
const brokerpak = "cloudfoundry/csb-brokerpak-azure"
13+
14+
// DownloadBrokerpak will download the brokerpak of the specified
15+
// version and return the directory where it has been placed.
16+
// The download is skipped if it has previously been downloaded.
17+
// Includes downloading the corresponding broker and ".envrc" file
18+
func DownloadBrokerpak(version, dir string) string {
19+
// Brokerpak
20+
basename := fmt.Sprintf("azure-services-%s.brokerpak", version)
21+
uri := fmt.Sprintf("https://github.com/%s/releases/download/%s/%s", brokerpak, version, basename)
22+
downloadUnlessCached(dir, basename, uri)
23+
24+
// ".envrc" file
25+
envrcURI := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/.envrc", brokerpak, version)
26+
downloadUnlessCached(dir, ".envrc", envrcURI)
27+
28+
// broker
29+
brokerVersion := readBrokerVersion(version)
30+
brokerURI := fmt.Sprintf("https://github.com/cloudfoundry/cloud-service-broker/releases/download/%s/cloud-service-broker.linux", brokerVersion)
31+
downloadUnlessCached(dir, "cloud-service-broker", brokerURI)
32+
if err := os.Chmod(filepath.Join(dir, "cloud-service-broker"), 0777); err != nil {
33+
panic(err)
34+
}
35+
36+
return dir
37+
}
38+
39+
// readBrokerVersion will use the specified brokerpak version to determine the corresponding broker version
40+
func readBrokerVersion(version string) string {
41+
body := newClient().get(fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/go.mod", brokerpak, version), "text/plain")
42+
defer body.Close()
43+
data := must(io.ReadAll(body))
44+
45+
matches := regexp.MustCompile(`(?m)^\s*github\.com/cloudfoundry/cloud-service-broker\s+(\S+)\s*$`).FindSubmatch(data)
46+
if len(matches) != 2 {
47+
panic(fmt.Sprintf("Could not extract CSB version from go.mod file: %q", data))
48+
}
49+
50+
brokerVersion := string(matches[1])
51+
fmt.Printf("Brokerpak version %q uses broker version %q\n", version, brokerVersion)
52+
return brokerVersion
53+
}
54+
55+
// downloadUnlessCached will download a file to a known location, unless it's already there
56+
func downloadUnlessCached(dir, basename, uri string) {
57+
target := filepath.Join(dir, basename)
58+
59+
_, err := os.Stat(target)
60+
switch err {
61+
case nil:
62+
fmt.Printf("Found %q cached at %q.\n", uri, target)
63+
default:
64+
fmt.Printf("Downloading %q to %q.\n", uri, target)
65+
newClient().download(target, uri)
66+
}
67+
}
68+
69+
// TargetDir will determine the target directory for a version and make sure that it exists
70+
func TargetDir(version string) string {
71+
pwd, err := os.Getwd()
72+
if err != nil {
73+
panic(err)
74+
}
75+
76+
dir := filepath.Join(pwd, "versions", version)
77+
if err := os.MkdirAll(dir, 0777); err != nil {
78+
panic(err)
79+
}
80+
81+
return dir
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package brokerpaks
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"net/http"
7+
"os"
8+
)
9+
10+
func newClient() *client {
11+
return &client{
12+
token: os.Getenv("GITHUB_TOKEN"),
13+
}
14+
}
15+
16+
// client is a microscopic GitHub client allowing HTTP GET
17+
type client struct {
18+
token string
19+
}
20+
21+
// get will do a HTTP GET to a body
22+
func (c client) get(path, mimeType string) io.ReadCloser {
23+
req := must(http.NewRequest(http.MethodGet, path, nil))
24+
25+
req.Header.Add("Accept", mimeType)
26+
if c.token != "" {
27+
req.Header.Add("Authorization", fmt.Sprintf("token %s", c.token))
28+
}
29+
30+
res := must(http.DefaultClient.Do(req))
31+
if res.StatusCode != http.StatusOK {
32+
panic(fmt.Sprintf("expected HTTP 200 but got %d: %s", res.StatusCode, res.Status))
33+
}
34+
return res.Body
35+
}
36+
37+
// download will do an HTTP GET to a file
38+
func (c client) download(target, uri string) {
39+
fh := must(os.Create(target))
40+
defer fh.Close()
41+
42+
body := c.get(uri, "application/octet-stream")
43+
defer body.Close()
44+
45+
_, err := io.Copy(fh, body)
46+
if err != nil {
47+
panic(err)
48+
}
49+
}
50+
51+
func must[A any](input A, err error) A {
52+
if err != nil {
53+
panic(err)
54+
}
55+
56+
return input
57+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
6+
"csbbrokerpakazure/acceptance-tests/helpers/brokerpaks"
7+
)
8+
9+
func main() {
10+
var version, dir string
11+
flag.StringVar(&version, "version", "", "version to upgrade from")
12+
flag.StringVar(&dir, "dir", "", "directory to install to")
13+
flag.Parse()
14+
15+
if version == "" {
16+
version = brokerpaks.LatestVersion()
17+
}
18+
19+
if dir == "" {
20+
dir = brokerpaks.TargetDir(version)
21+
}
22+
23+
brokerpaks.DownloadBrokerpak(version, dir)
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package brokerpaks
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"sort"
8+
9+
"github.com/blang/semver/v4"
10+
)
11+
12+
// LatestVersion will determine the latest released version of the brokerpak
13+
// according to semantic versioning
14+
func LatestVersion() string {
15+
versions := Versions()
16+
latest := versions[len(versions)-1].String()
17+
fmt.Printf("Latest brokerpak version: %s\n", latest)
18+
return latest
19+
}
20+
21+
// Versions will get all the released Versions
22+
func Versions() []semver.Version {
23+
body := newClient().get(fmt.Sprintf("https://api.github.com/repos/%s/releases?per_page=100", brokerpak), "application/json") // max per page
24+
defer body.Close()
25+
data := must(io.ReadAll(body))
26+
27+
var receiver []struct {
28+
TagName string `json:"tag_name"`
29+
}
30+
if err := json.Unmarshal(data, &receiver); err != nil {
31+
panic(err)
32+
}
33+
34+
var versions []semver.Version
35+
for _, r := range receiver {
36+
v := must(semver.ParseTolerant(r.TagName))
37+
if len(v.Pre) == 0 { // skip pre-release
38+
versions = append(versions, v)
39+
}
40+
}
41+
sort.SliceStable(versions, func(i, j int) bool { return versions[i].LT(versions[j]) })
42+
43+
return versions
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
6+
"csbbrokerpakazure/acceptance-tests/helpers/brokerpaks"
7+
)
8+
9+
func main() {
10+
for _, v := range brokerpaks.Versions() {
11+
fmt.Println(v)
12+
}
13+
}

acceptance-tests/upgrade/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
versions

acceptance-tests/upgrade/README.md

+31-5
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,38 @@ These tests perform the following flow:
77
- Check that the resources are still accessible
88
- Create some more resources
99

10-
Two flags may be specified to control the versions used:
10+
### Usage
11+
By default, version A will be the highest brokerpak version on GitHub and
12+
version B will be whatever has been built locally with `make build`.
13+
14+
### Specifying version A
15+
To specify version A, use the `-from-version` flag:
16+
```
17+
ginkgo -v --label-filter <label> -- -from-version 1.10.0
18+
```
19+
Note the `--` to separate Ginkgo flags from test flags.
20+
21+
### Advanced flags
22+
Two other flags may be specified to control the versions used:
1123
- `-releasedBuildDir` specifies a directory with a built brokerpak at version A
12-
- `-developmentBuildDir` specifies a directory with a built brokerpak at version B
24+
- `-developmentBuildDir` specifies a directory with a built brokerpak at version B.
25+
By default it is `../..` which is the root of this repo.
26+
- `intermediateBuildDirs` comma separated locations of intermediate versions of built broker and brokerpak
1327

14-
Note that when running Ginkgo, these flags should go after a `--` to distinguish
15-
them from Ginkgo flags, for example:
1628
```
1729
ginkgo -v -- -releasedBuildDir ... -developmentBuildDir ...
18-
```
30+
```
31+
Note the `--` to separate Ginkgo flags from test flags.
32+
33+
### Helper commands
34+
35+
To list brokerpak versions available:
36+
```
37+
go run -C ../helpers/brokerpaks/versions .
38+
```
39+
40+
To prepare a brokerpak without running a test:
41+
```
42+
go run -C ../helpers/brokerpaks/prepare . -version 1.10.0 -dir /tmp/versions
43+
```
44+
Both flags are optional.

acceptance-tests/upgrade/upgrade_suite_test.go

+37-5
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,65 @@ package upgrade_test
22

33
import (
44
"flag"
5+
"os"
56
"testing"
67

8+
"csbbrokerpakazure/acceptance-tests/helpers/brokerpaks"
79
"csbbrokerpakazure/acceptance-tests/helpers/environment"
810

911
. "github.com/onsi/ginkgo/v2"
1012
. "github.com/onsi/gomega"
1113
)
1214

1315
var (
16+
fromVersion string
1417
developmentBuildDir string
1518
releasedBuildDir string
1619
intermediateBuildDirs string
1720
metadata environment.Metadata
1821
)
1922

2023
func init() {
21-
flag.StringVar(&releasedBuildDir, "releasedBuildDir", "../../../azure-released", "location of released version of built broker and brokerpak")
22-
flag.StringVar(&developmentBuildDir, "developmentBuildDir", "../../dev-release", "location of development version of built broker and brokerpak")
24+
flag.StringVar(&fromVersion, "from-version", "", "version to upgrade from")
25+
flag.StringVar(&releasedBuildDir, "releasedBuildDir", "", "location of released version of built broker and brokerpak")
26+
flag.StringVar(&developmentBuildDir, "developmentBuildDir", "../..", "location of development version of built broker and brokerpak")
2327
flag.StringVar(&intermediateBuildDirs, "intermediateBuildDirs", "", "comma separated locations of intermediate versions of built broker and brokerpak")
28+
}
2429

30+
func TestUpgrade(t *testing.T) {
31+
RegisterFailHandler(Fail)
32+
RunSpecs(t, "Upgrade Suite")
2533
}
2634

2735
var _ = BeforeSuite(func() {
2836
metadata = environment.ReadMetadata()
37+
38+
if releasedBuildDir == "" { // Released dir not specified, so we should download a brokerpak
39+
if fromVersion == "" { // Version not specified, so use latest
40+
fromVersion = brokerpaks.LatestVersion()
41+
}
42+
43+
releasedBuildDir = brokerpaks.DownloadBrokerpak(fromVersion, brokerpaks.TargetDir(fromVersion))
44+
}
45+
46+
preflight(developmentBuildDir) // faster feedback as no download
47+
preflight(releasedBuildDir)
2948
})
3049

31-
func TestUpgrade(t *testing.T) {
32-
RegisterFailHandler(Fail)
33-
RunSpecs(t, "Upgrade Suite")
50+
// preflight checks that a specified broker dir is viable so that the user gets fast feedback
51+
func preflight(dir string) {
52+
GinkgoHelper()
53+
54+
entries, err := os.ReadDir(dir)
55+
Expect(err).NotTo(HaveOccurred())
56+
names := make([]string, len(entries))
57+
for i := range entries {
58+
names[i] = entries[i].Name()
59+
}
60+
61+
Expect(names).To(ContainElements(
62+
Equal("cloud-service-broker"),
63+
Equal(".envrc"),
64+
MatchRegexp(`azure-services-\S+\.brokerpak`),
65+
))
3466
}

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1
1010
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/sql/armsql v1.1.0
1111
github.com/Pallinder/go-randomdata v1.2.0
12+
github.com/blang/semver/v4 v4.0.0
1213
github.com/cloudfoundry/cloud-service-broker v0.20.0
1314
github.com/hashicorp/terraform-plugin-sdk/v2 v2.30.0
1415
github.com/onsi/ginkgo/v2 v2.13.1

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,8 @@ github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
245245
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
246246
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas=
247247
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4=
248+
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
249+
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
248250
github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA=
249251
github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8=
250252
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=

scripts/run-example.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ fi
1010

1111
SERVICE_NAME=${1}; shift
1212

13-
SECURITY_USER_NAME=${SECURITY_USER_NAME:=aws-broker}
14-
SECURITY_USER_PASSWORD=${SECURITY_USER_PASSWORD:=aws-broker-pw}
13+
SECURITY_USER_NAME=${SECURITY_USER_NAME:=azure-broker}
14+
SECURITY_USER_PASSWORD=${SECURITY_USER_PASSWORD:=azure-broker-pw}
1515
DOCKER_OPTS="--rm -v $(PWD):/brokerpak -w /brokerpak --network=host"
1616
CSB=cfplatformeng/csb
1717

0 commit comments

Comments
 (0)