├── .boilerplate.json ├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ └── default.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── OWNERS ├── README.md ├── SECURITY_CONTACTS ├── build ├── charts.sh ├── deploy.sh ├── image.sh ├── release-charts.sh ├── release-images.sh ├── verify-boilerplate.sh ├── verify-modified-files.sh └── version.sh ├── charts ├── minibroker │ ├── Chart.yaml │ ├── templates │ │ ├── _helpers.tpl │ │ ├── broker.yaml │ │ ├── deployment.yaml │ │ ├── provisioning_settings.yaml │ │ ├── rbac.yaml │ │ └── service.yaml │ └── values.yaml └── wordpress │ ├── .helmignore │ ├── Chart.yaml │ ├── README.md │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── ingress.yaml │ ├── mysql-binding.yaml │ ├── mysql-instance.yaml │ ├── secrets.yaml │ └── svc.yaml │ └── values.yaml ├── ci ├── install │ ├── integration.sh │ └── release.sh ├── release.sh └── test_integration.sh ├── cmd └── minibroker │ └── main.go ├── code-of-conduct.md ├── docker ├── Dockerfile └── rootfs │ ├── etc │ └── passwd │ └── home │ └── minibroker │ └── tmp │ └── .gitkeep ├── go.mod ├── go.sum ├── hack └── create-cluster.sh ├── pkg ├── broker │ ├── broker.go │ ├── broker_suite_test.go │ ├── broker_test.go │ ├── mocks │ │ └── mock_broker.go │ └── options.go ├── helm │ ├── chart.go │ ├── chart_test.go │ ├── config.go │ ├── config_test.go │ ├── helm.go │ ├── helm_suite_test.go │ ├── helm_test.go │ ├── http.go │ ├── mocks │ │ ├── mock_chart.go │ │ ├── mock_config.go │ │ ├── mock_http.go │ │ ├── mock_io.go │ │ ├── mock_repository.go │ │ └── mock_testutil_chart.go │ ├── repository.go │ ├── repository_test.go │ └── testutil │ │ ├── chart.go │ │ └── config.go ├── kubernetes │ ├── cluster.go │ ├── cluster_test.go │ └── kubernetes_suite_test.go ├── log │ ├── doc.go │ ├── klog.go │ ├── log.go │ └── noop.go ├── minibroker │ ├── mariadb.go │ ├── minibroker.go │ ├── minibroker_test.go │ ├── mongodb.go │ ├── mysql.go │ ├── postgres.go │ ├── provider.go │ ├── provider_test.go │ ├── rabbitmq.go │ └── redis.go └── nameutil │ ├── doc.go │ ├── generator.go │ ├── generator_test.go │ ├── mocks │ └── mock_generator.go │ └── nameutil_suite_test.go ├── tests └── integration │ ├── README.md │ ├── consumer_test.go │ ├── doc.go │ ├── go.mod │ ├── go.sum │ ├── integration_suite_test.go │ ├── minibroker_test.go │ ├── resources │ ├── mariadb_client.tmpl.yaml │ ├── mongodb_client.tmpl.yaml │ ├── mysql_client.tmpl.yaml │ ├── postgresql_client.tmpl.yaml │ ├── rabbitmq_client.tmpl.yaml │ └── redis_client.tmpl.yaml │ └── testutil │ ├── helm.go │ └── testutil.go └── third-party └── go.mod /.boilerplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "dirs_to_skip" : [ 3 | "third-party", 4 | "mocks" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | image/ 2 | output/ 3 | third-party/ 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | - package-ecosystem: "gomod" 8 | directory: "/tests/integration/" 9 | schedule: 10 | interval: "weekly" 11 | -------------------------------------------------------------------------------- /.github/workflows/default.yml: -------------------------------------------------------------------------------- 1 | name: Default 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | verify: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | submodules: recursive 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.17 20 | - uses: actions/cache@v2 21 | with: 22 | path: | 23 | ~/.cache/go-build 24 | ~/go/pkg/mod 25 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 26 | restore-keys: | 27 | ${{ runner.os }}-go- 28 | - name: Verify 29 | run: make verify 30 | - name: Lint 31 | run: |- 32 | set -o errexit 33 | go install github.com/golang/mock/mockgen@v1.5.0 34 | make lint 35 | test-unit: 36 | runs-on: ubuntu-latest 37 | steps: 38 | - uses: actions/checkout@v2 39 | with: 40 | submodules: recursive 41 | - name: Set up Go 42 | uses: actions/setup-go@v2 43 | with: 44 | go-version: 1.17 45 | - uses: actions/cache@v2 46 | with: 47 | path: | 48 | ~/.cache/go-build 49 | ~/go/pkg/mod 50 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 51 | restore-keys: | 52 | ${{ runner.os }}-go- 53 | - name: Unit Tests 54 | run: |- 55 | set -o errexit 56 | go install github.com/onsi/ginkgo/ginkgo@v1.15.0 57 | make test-unit 58 | test-integration: 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v2 62 | with: 63 | submodules: recursive 64 | - name: Set up Go 65 | uses: actions/setup-go@v2 66 | with: 67 | go-version: 1.17 68 | - uses: actions/cache@v2 69 | with: 70 | path: | 71 | ~/.cache/go-build 72 | ~/go/pkg/mod 73 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 74 | restore-keys: | 75 | ${{ runner.os }}-go- 76 | - uses: actions/cache@v2 77 | with: 78 | path: | 79 | ~/.cache/binaries 80 | key: ${{ runner.os }}-binaries-${{ hashFiles('ci/install/**') }} 81 | restore-keys: | 82 | ${{ runner.os }}-binaries- 83 | - name: Integration Tests 84 | env: 85 | CHANGE_MINIKUBE_NONE_USER: "false" 86 | TEST_ASSERT_TIMEOUT: 2m 87 | TEST_BIND_TIMEOUT: 5m 88 | TEST_PROVISION_TIMEOUT: 5m 89 | VERSION_FORCE_TAG_FETCH: "true" 90 | run: |- 91 | set -o errexit 92 | go install github.com/onsi/ginkgo/ginkgo@v1.15.0 93 | sudo apt-get -qq -y install conntrack 94 | ci/install/integration.sh 95 | ci/test_integration.sh 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /output/ 2 | 3 | /bin/charts 4 | /minibroker 5 | /minibroker-linux 6 | /image/minibroker 7 | /vendor 8 | /tmp 9 | 10 | *.test 11 | *.coverprofile 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third-party/repo-infra"] 2 | path = third-party/repo-infra 3 | url = https://github.com/pivotal-k8s/repo-infra.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 1.14.x 3 | 4 | os: linux 5 | dist: bionic 6 | 7 | # Don't indent any line more than one level to avoid '\n' characters in the converter string, 8 | # which breaks the regex free-spacing modifier. Also, do NOT include comments in the regex string. 9 | # The logic: 10 | # * Build on push to branches on forks that setup Travis - not tied to the 11 | # kubernetes-sigs/minibroker Travis account. 12 | # * Build on branch master that is not tagged. 13 | # * Build on kubernetes-sigs/minibroker when a tag is present and the branch matches a semver 14 | # string. 15 | if: >- 16 | (repo != "kubernetes-sigs/minibroker") 17 | OR ((branch = master) AND (tag IS NOT present)) 18 | OR ( 19 | (repo = "kubernetes-sigs/minibroker") 20 | AND (tag IS present) 21 | AND (branch ~= /^(?x) 22 | v(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*) 23 | (?:-((?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9][0-9]*|[0-9]*[a-zA-Z-][0-9a-zA-Z-]*))*))? 24 | (?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))? 25 | $/) 26 | ) 27 | 28 | cache: 29 | directories: 30 | - ${HOME}/assets 31 | - ${GOPATH}/pkg/mod 32 | - ${GOPATH}/bin 33 | 34 | jobs: 35 | include: 36 | - name: Verify 37 | stage: verify 38 | script: make verify 39 | - name: Lint 40 | stage: lint 41 | before_script: 42 | - GO111MODULE=on go install github.com/golang/mock/mockgen 43 | script: make lint 44 | - name: Test 45 | stage: test 46 | before_script: 47 | - go install github.com/onsi/ginkgo/ginkgo 48 | script: make test-unit 49 | - name: Integration tests 50 | stage: test-integration 51 | services: 52 | - docker 53 | env: 54 | - CHANGE_MINIKUBE_NONE_USER=false 55 | - MINIKUBE_HOME=${HOME} 56 | - TEST_PROVISION_TIMEOUT=5m 57 | - TEST_BIND_TIMEOUT=5m 58 | - TEST_ASSERT_TIMEOUT=2m 59 | # Forces the version.sh script to fetch tags and unshallow the git repository. 60 | - VERSION_FORCE_TAG_FETCH=true 61 | before_install: 62 | - sudo apt-get -qq -y install conntrack 63 | before_script: 64 | - ci/install/integration.sh 65 | - go install github.com/onsi/ginkgo/ginkgo 66 | script: ci/test_integration.sh 67 | - name: Release 68 | stage: release 69 | services: 70 | - docker 71 | env: 72 | # Forces the version.sh script to fetch tags and unshallow the git repository. 73 | - VERSION_FORCE_TAG_FETCH=true 74 | before_script: 75 | - ci/install/release.sh 76 | script: ci/release.sh 77 | 78 | stages: 79 | - verify 80 | - lint 81 | - test 82 | - test-integration 83 | - name: release 84 | if: (repo = "kubernetes-sigs/minibroker") AND (tag IS present) AND (branch != master) 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Welcome to Minibroker. We are excited about the prospect of you joining our [community](https://git.k8s.io/community)! The Kubernetes community abides by the CNCF [code of conduct](code-of-conduct.md). Here is an excerpt: 4 | 5 | _As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities._ 6 | 7 | ## Getting Started 8 | 9 | We have full documentation on how to get started contributing here: 10 | 11 | 12 | - Learn how to [change minibroker and try out your changes on a local cluster](README.md#local-development) 13 | - [Contributor License Agreement](https://git.k8s.io/community/CLA.md) Kubernetes projects require that you sign a Contributor License Agreement (CLA) before we can accept your pull requests 14 | - [Kubernetes Contributor Guide](https://git.k8s.io/community/contributors/guide) - Main contributor documentation, or you can just jump directly to the [contributing section](https://git.k8s.io/community/contributors/guide#contributing) 15 | - [Contributor Cheat Sheet](https://git.k8s.io/community/contributors/guide/contributor-cheatsheet/) - Common resources for existing developers 16 | 17 | ## Mentorship 18 | 19 | - [Mentoring Initiatives](https://git.k8s.io/community/mentoring) - Kubernetes has a diverse set of mentorship programs available that are always looking for volunteers! 20 | 21 | 22 | ## Contact Information 23 | 24 | We are part of SIG Service Catalog, so the best way to reach us is to use their 25 | communication channels: 26 | 27 | - [Slack channel](https://kubernetes.slack.com/messages/sig-service-catalog) 28 | - [Mailing list](https://groups.google.com/forum/#!forum/kubernetes-sig-service-catalog) 29 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | REPO ?= github.com/kubernetes-sigs/minibroker 16 | BINARY ?= minibroker 17 | PKG ?= $(REPO)/cmd/$(BINARY) 18 | OUTPUT_DIR ?= output 19 | OUTPUT_CHARTS_DIR ?= $(OUTPUT_DIR)/charts 20 | REGISTRY ?= quay.io/kubernetes-service-catalog/ 21 | IMAGE ?= $(REGISTRY)minibroker 22 | TAG ?= $(shell ./build/version.sh) 23 | DATE ?= $(shell date --utc) 24 | CHART_SIGN_KEY ?= 25 | IMAGE_PULL_POLICY ?= Never 26 | TMP_BUILD_DIR ?= tmp 27 | WORDPRESS_CHART ?= $(shell pwd)/charts/wordpress 28 | 29 | # The base images for the Dockerfile stages. 30 | BUILDER_IMAGE ?= golang:1.14.9-buster@sha256:15f72b7c8f18323f8553510a791f68b78ecdf2f1b0acae865fa3d8a1636b3fd4 31 | DOWNLOADER_IMAGE ?= alpine:latest 32 | CERT_BUILDER_IMAGE ?= opensuse/leap:15.2@sha256:48c4dbacfbc8f6200096e6b327f3b346ccff4e4075618017848aa53c44f75eea 33 | RUNNING_IMAGE ?= scratch 34 | 35 | lint: lint-go-vet lint-go-mod lint-modified-files 36 | 37 | lint-go-vet: 38 | go vet ./... 39 | 40 | lint-go-mod: 41 | go mod tidy 42 | 43 | lint-modified-files: | lint-go-mod generate 44 | ./build/verify-modified-files.sh 45 | 46 | generate: 47 | find . -type d -name '*mocks' -print -prune -exec rm -rf '{}' \; 48 | go generate ./... 49 | 50 | build: 51 | CGO_ENABLED=0 go build -ldflags="-s -w -X 'main.version=$(TAG)' -X 'main.buildDate=$(DATE)'" -o $(OUTPUT_DIR)/minibroker $(PKG) 52 | 53 | image: 54 | BUILD_IN_MINIKUBE=0 \ 55 | BUILDER_IMAGE="$(BUILDER_IMAGE)" \ 56 | DOWNLOADER_IMAGE="$(DOWNLOADER_IMAGE)" \ 57 | CERT_BUILDER_IMAGE="$(CERT_BUILDER_IMAGE)" \ 58 | RUNNING_IMAGE="$(RUNNING_IMAGE)" \ 59 | IMAGE="$(IMAGE)" \ 60 | TAG="$(TAG)" \ 61 | ./build/image.sh 62 | 63 | image-in-minikube: 64 | BUILD_IN_MINIKUBE=1 \ 65 | BUILDER_IMAGE="$(BUILDER_IMAGE)" \ 66 | DOWNLOADER_IMAGE="$(DOWNLOADER_IMAGE)" \ 67 | CERT_BUILDER_IMAGE="$(CERT_BUILDER_IMAGE)" \ 68 | RUNNING_IMAGE="$(RUNNING_IMAGE)" \ 69 | IMAGE="$(IMAGE)" \ 70 | TAG="$(TAG)" \ 71 | ./build/image.sh 72 | 73 | charts: 74 | CHART_SRC="charts/minibroker" \ 75 | TMP_BUILD_DIR="$(TMP_BUILD_DIR)" \ 76 | OUTPUT_CHARTS_DIR="$(OUTPUT_CHARTS_DIR)" \ 77 | APP_VERSION="$(TAG)" \ 78 | VERSION="$(TAG)" \ 79 | CHART_SIGN_KEY="$(CHART_SIGN_KEY)" \ 80 | IMAGE="$(IMAGE)" \ 81 | TAG="$(TAG)" \ 82 | ./build/charts.sh 83 | 84 | clean: 85 | -rm -rf "$(OUTPUT_DIR)" 86 | -rm -rf "$(TMP_BUILD_DIR)" 87 | 88 | verify: verify-boilerplate 89 | 90 | verify-boilerplate: 91 | ./build/verify-boilerplate.sh 92 | 93 | test-unit: 94 | ginkgo -cover cmd/... pkg/... 95 | 96 | test-integration: 97 | (cd ./tests/integration; NAMESPACE=minibroker-tests WORDPRESS_CHART="$(WORDPRESS_CHART)" ginkgo --nodes 4 -v --stream --slowSpecThreshold 180 .) 98 | 99 | test: test-unit test-integration 100 | 101 | log: 102 | kubectl logs -n minibroker deploy/minibroker-minibroker -c minibroker 103 | 104 | create-cluster: 105 | ./hack/create-cluster.sh 106 | 107 | minikube-load-image: 108 | minikube image load "$(IMAGE):$(TAG)" 109 | 110 | deploy: 111 | IMAGE="$(IMAGE)" \ 112 | TAG="$(TAG)" \ 113 | IMAGE_PULL_POLICY="$(IMAGE_PULL_POLICY)" \ 114 | OUTPUT_CHARTS_DIR="$(OUTPUT_CHARTS_DIR)" \ 115 | ./build/deploy.sh 116 | 117 | deploy-cf: 118 | IMAGE="$(IMAGE)" \ 119 | TAG="$(TAG)" \ 120 | IMAGE_PULL_POLICY="$(IMAGE_PULL_POLICY)" \ 121 | OUTPUT_CHARTS_DIR="$(OUTPUT_CHARTS_DIR)" \ 122 | CLOUDFOUNDRY=true \ 123 | ./build/deploy.sh 124 | 125 | deploy-dev: image-in-minikube charts deploy 126 | 127 | deploy-dev-cf: image-in-minikube charts deploy-cf 128 | 129 | release: clean release-images release-charts 130 | 131 | release-images: image 132 | IMAGE="$(IMAGE)" TAG="$(TAG)" ./build/release-images.sh 133 | 134 | release-charts: charts 135 | OUTPUT_CHARTS_DIR="$(OUTPUT_CHARTS_DIR)" ./build/release-charts.sh 136 | 137 | .PHONY: build log test image charts clean push create-cluster deploy release 138 | -------------------------------------------------------------------------------- /OWNERS: -------------------------------------------------------------------------------- 1 | # See the OWNERS docs at https://go.k8s.io/owners 2 | 3 | approvers: 4 | - carolynvs 5 | - f0rmiga 6 | - mook-as 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/kubernetes-sigs/minibroker.svg?branch=master)](https://travis-ci.org/kubernetes-sigs/minibroker) 2 | 3 | # Minibroker 4 | 5 | > A minibroker for your minikube! 6 | 7 | Minibroker is an implementation of the [Open Service Broker API](https://openservicebrokerapi.org) 8 | suited for local development and testing. Rather than provisioning services 9 | from a cloud provider, Minibroker provisions services in containers on the cluster. 10 | 11 | Minibroker uses the [Kubernetes Helm Charts](https://github.com/kubernetes/charts) 12 | as its source of provisionable services. 13 | 14 | While it can deploy any stable chart, Minibroker provides the following Service Catalog Enabled 15 | services: 16 | 17 | * mysql 18 | * postgres 19 | * mariadb 20 | * mongodb 21 | * redis 22 | * rabbitmq 23 | 24 | Minibroker has built-in support for these charts so that the credentials are formatted 25 | in a format that Service Catalog Ready charts expect. 26 | 27 | # Prerequisites 28 | 29 | * Kubernetes 1.9+ cluster 30 | * [Helm 3](https://helm.sh) 31 | * [Service Catalog](https://svc-cat.io/docs/install) 32 | * [Service Catalog CLI (svcat)](http://svc-cat.io/docs/install/#installing-the-service-catalog-cli) 33 | 34 | Run the following commands to set up a cluster: 35 | 36 | ``` 37 | minikube start 38 | 39 | helm repo add svc-cat https://kubernetes-sigs.github.io/service-catalog 40 | kubectl create namespace svc-cat 41 | helm install catalog --namespace svc-cat svc-cat/catalog 42 | ``` 43 | 44 | # Install Minibroker 45 | 46 | ``` 47 | helm repo add minibroker https://minibroker.blob.core.windows.net/charts 48 | kubectl create namespace minibroker 49 | helm install minibroker --namespace minibroker minibroker/minibroker 50 | ``` 51 | 52 | *NOTE*: Platform users provisioning service instances will be able to set 53 | arbitrary parameters, which can be potentially dangerous, e.g. if setting a 54 | high number of replicas. 55 | To prevent this, it is possible to define override parameters per service in 56 | the according fields of the `provisioning` chart value. If defined, the 57 | user-defined parameters are dropped and the override parameters are used 58 | instead. 59 | 60 | ## Installation Options 61 | * Only Service Catalog Enabled services are included with Minibroker by default, 62 | to include all available charts specify `--set serviceCatalogEnabledOnly=false`. 63 | * The stable Helm chart repository is the default source for services, to change 64 | the source Helm repository, specify 65 | `--set helmRepoUrl=https://example.com/custom-chart-repo/`. 66 | 67 | # Update Minibroker 68 | 69 | ``` 70 | helm upgrade minibroker minibroker/minibroker \ 71 | --install \ 72 | --set deploymentStrategy="Recreate" 73 | ``` 74 | 75 | # Usage with Cloud Foundry 76 | 77 | The Open Service Broker API is compatible with Cloud Foundry, and minibroker 78 | can be used to respond to requests from a CF system. 79 | 80 | ## Installation 81 | 82 | CF doesn't require the Service Catalog to be installed. The Cloud Controller, 83 | which is part of the CFAR (Clouf Foundry Application Runtime), is the Platform 84 | as specified in the OSBAPI. 85 | 86 | ``` 87 | helm repo add minibroker https://minibroker.blob.core.windows.net/charts 88 | kubectl create namespace minibroker 89 | helm install minibroker minibroker/minibroker \ 90 | --namespace minibroker \ 91 | --set "deployServiceCatalog=false" \ 92 | --set "defaultNamespace=minibroker" 93 | ``` 94 | 95 | ## Usage 96 | 97 | The following usage instructions assume a successful login to the CF system, 98 | with an Org and Space available. It also assumes a CF system like [KubeCF](https://github.com/cloudfoundry-incubator/kubecf) 99 | that runs in the same Kubernetes cluster as the minibroker. It should be 100 | possible to run the minibroker separately, but this would need a proper 101 | ingress setup. 102 | 103 | ``` 104 | cf create-service-broker minibroker user pass http://minibroker-minibroker.minibroker.svc 105 | cf enable-service-access redis 106 | echo > redis.json '[{ "protocol": "tcp", "destination": "10.0.0.0/8", "ports": "6379", "description": "Allow Redis traffic" }]' 107 | cf create-security-group redis_networking redis.json 108 | cf bind-security-group redis_networking org space 109 | cf create-service redis 4-0-10 redis-example-svc 110 | ``` 111 | 112 | The service is then available for users of the CF system. 113 | 114 | ``` 115 | git clone https://github.com/scf-samples/cf-redis-example-app 116 | cd cf-redis-example-app 117 | cf push --no-start 118 | cf bind-service redis-example-app redis-example-svc 119 | cf start redis-example-app 120 | ``` 121 | 122 | The app can then be tested to confirm it can access the Redis service. 123 | 124 | ``` 125 | export APP=redis-example-app.cf-dev.io 126 | curl -X GET $APP/foo # Returns 'key not present' 127 | curl -X PUT $APP/foo -d 'data=bar' 128 | curl -X GET $APP/foo # Returns 'bar' 129 | ``` 130 | 131 | # Examples 132 | 133 | ``` 134 | $ svcat get classes 135 | NAME DESCRIPTION 136 | +------------+---------------------------+ 137 | mariadb Helm Chart for mariadb 138 | mongodb Helm Chart for mongodb 139 | mysql Helm Chart for mysql 140 | postgresql Helm Chart for postgresql 141 | 142 | $ svcat describe class mysql 143 | Name: mysql 144 | Description: Helm Chart for mysql 145 | UUID: mysql 146 | Status: Active 147 | Tags: 148 | Broker: minibroker 149 | 150 | Plans: 151 | NAME DESCRIPTION 152 | +--------+--------------------------------+ 153 | 5-7-14 Fast, reliable, scalable, 154 | and easy to use open-source 155 | relational database system. 156 | 157 | $ svcat provision mysqldb --class mysql --plan 5-7-14 -p mysqlDatabase=mydb -p mysqlUser=admin 158 | Name: mysqldb 159 | Namespace: minibroker 160 | Status: 161 | Class: mysql 162 | Plan: 5-7-14 163 | 164 | Parameters: 165 | mysqlDatabase: mydb 166 | mysqlUser: admin 167 | 168 | $ svcat bind mysqldb 169 | Name: mysqldb 170 | Namespace: minibroker 171 | Status: 172 | Secret: mysqldb 173 | Instance: mysqldb 174 | 175 | $ svcat describe binding mysqldb --show-secrets 176 | Name: mysqldb 177 | Namespace: minibroker 178 | Status: Ready - Injected bind result @ 2018-04-27 03:53:09 +0000 UTC 179 | Secret: mysqldb 180 | Instance: mysqldb 181 | 182 | Parameters: 183 | {} 184 | 185 | Secret Data: 186 | database mydb 187 | host lucky-dragon-mysql.minibroker.svc 188 | mysql-password gsIpB8dBEn 189 | mysql-root-password F8aBHuo8zb 190 | password gsIpB8dBEn 191 | port 3306 192 | uri mysql://admin:gsIpB8dBEn@lucky-dragon-mysql.minibroker.svc:3306/mydb 193 | username admin 194 | 195 | $ svcat unbind mysqldb 196 | $ svcat deprovision mysqldb 197 | ``` 198 | 199 | To see Minibroker in action try out our Wordpress chart, that relies on Minibroker 200 | to supply a database: 201 | 202 | ``` 203 | helm install minipress minibroker/wordpress 204 | ``` 205 | 206 | Follow the instructions output to the console to log into Wordpress. 207 | 208 | ## Helm Chart Parameters 209 | Minibroker passes parameters specified during provisioning to the underlying 210 | Helm Chart. This lets you customize the service to specify a non-root user, or the name of 211 | the database to create, etc. 212 | 213 | # Local Development 214 | 215 | ## Requirements 216 | 217 | * Docker 218 | * [Minikube](https://minikube.sigs.k8s.io/) 219 | * [Helm 3](https://helm.sh) 220 | * [Service Catalog CLI (svcat)](http://svc-cat.io/docs/install/#installing-the-service-catalog-cli) 221 | 222 | ## Setup 223 | 224 | 1. Create a Minikube cluster for local development by running `make create-cluster`. It defaults to 225 | using Docker as a VM driver. If you want to use a different VM driver, set the `VM_DRIVER` 226 | environment variable. E.g. `VM_DRIVER=kvm2 make create-cluster`. 227 | 2. Point your Docker to use the Minikube Docker daemon on the current shell session by running 228 | `eval $(minikube docker-env)`. 229 | 230 | ## Deploy 231 | 232 | Compile and deploy the broker to your local cluster by running 233 | `IMAGE_PULL_POLICY="Never" make image deploy`. 234 | 235 | ## Test 236 | 237 | `make test` 238 | 239 | There is an example chart for Wordpress that has been tweaked to use Minibroker for the 240 | database provider, run `make setup-wordpress` to try it out. 241 | -------------------------------------------------------------------------------- /SECURITY_CONTACTS: -------------------------------------------------------------------------------- 1 | # Defined below are the security contacts for this repo. 2 | # 3 | # They are the contact point for the Product Security Committee to reach out 4 | # to for triaging and handling of incoming issues. 5 | # 6 | # The below names agree to abide by the 7 | # [Embargo Policy](https://git.k8s.io/security/private-distributors-list.md#embargo-policy) 8 | # and will be removed and replaced if they violate that agreement. 9 | # 10 | # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE 11 | # INSTRUCTIONS AT https://kubernetes.io/security/ 12 | 13 | carolynvs -------------------------------------------------------------------------------- /build/charts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail -o xtrace 18 | 19 | mkdir -p "${TMP_BUILD_DIR}" 20 | tmp_chart_build_dir="${TMP_BUILD_DIR}/minibroker" 21 | rm -rf "${tmp_chart_build_dir}" 22 | cp -R "${CHART_SRC}" "${tmp_chart_build_dir}" 23 | 24 | image="${IMAGE//\//\\\/}:${TAG}" 25 | sed -i "s/<%image%>/${image}/" "${tmp_chart_build_dir}/values.yaml" 26 | 27 | helm package ${CHART_SIGN_KEY:+--sign --key "${CHART_SIGN_KEY}"} \ 28 | --destination "${OUTPUT_CHARTS_DIR}" \ 29 | --app-version "${APP_VERSION}" \ 30 | --version "${VERSION}" \ 31 | "${tmp_chart_build_dir}" 32 | 33 | rm -rf "${tmp_chart_build_dir}" 34 | -------------------------------------------------------------------------------- /build/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail ${XTRACE:+-o xtrace} 18 | 19 | until svcat version | grep -m 1 'Server Version: v' ; do 20 | sleep 1; 21 | done 22 | 23 | if ! kubectl get namespace minibroker 1> /dev/null 2> /dev/null; then 24 | kubectl create namespace minibroker 25 | fi 26 | 27 | helm upgrade minibroker \ 28 | --install \ 29 | --namespace minibroker \ 30 | --wait \ 31 | --set "image=${IMAGE}:${TAG}" \ 32 | --set "imagePullPolicy=${IMAGE_PULL_POLICY}" \ 33 | --set "deploymentStrategy=Recreate" \ 34 | --set "logLevel=${LOG_LEVEL:-4}" \ 35 | ${CLOUDFOUNDRY:+--set "deployServiceCatalog=false"} \ 36 | ${CLOUDFOUNDRY:+--set "defaultNamespace=minibroker"} \ 37 | "${OUTPUT_CHARTS_DIR}/minibroker-${TAG}.tgz" 38 | -------------------------------------------------------------------------------- /build/image.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | if [[ "${BUILD_IN_MINIKUBE}" == "1" ]]; then 20 | if ! type -t minikube &>/dev/null; then 21 | >&2 echo "minikube not found in \$PATH" 22 | exit 1 23 | fi 24 | # shellcheck disable=SC2046 25 | eval $(minikube -p minikube docker-env) 26 | fi 27 | 28 | docker ${USE_BUILDX:+buildx} build \ 29 | --tag "${IMAGE}:${TAG}" \ 30 | ${BUILDER_IMAGE:+--build-arg "BUILDER_IMAGE=${BUILDER_IMAGE}"} \ 31 | ${DOWNLOADER_IMAGE:+--build-arg "DOWNLOADER_IMAGE=${DOWNLOADER_IMAGE}"} \ 32 | ${CERT_BUILDER_IMAGE:+--build-arg "CERT_BUILDER_IMAGE=${CERT_BUILDER_IMAGE}"} \ 33 | ${RUNNING_IMAGE:+--build-arg "RUNNING_IMAGE=${RUNNING_IMAGE}"} \ 34 | --build-arg "TAG=${TAG}" \ 35 | --file docker/Dockerfile . 36 | -------------------------------------------------------------------------------- /build/release-charts.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | CHART_REPOSITORY_ROOT=https://minibroker.blob.core.windows.net 20 | AZURE_STORAGE_CONTAINER=charts 21 | index_url="${CHART_REPOSITORY_ROOT}/${AZURE_STORAGE_CONTAINER}" 22 | 23 | >&2 echo "Generating final index.yaml..." 24 | helm repo index \ 25 | --url "${index_url}" \ 26 | --merge <(curl -L --silent --fail "${index_url}/index.yaml") \ 27 | "${OUTPUT_CHARTS_DIR}" 28 | 29 | if [ ! -v AZURE_STORAGE_CONNECTION_STRING ]; then 30 | >&2 echo "AZURE_STORAGE_CONNECTION_STRING env var required to publish" 31 | exit 1 32 | fi 33 | 34 | >&2 echo "Uploading from ${OUTPUT_CHARTS_DIR}" 35 | az storage blob upload-batch \ 36 | --destination "${AZURE_STORAGE_CONTAINER}" \ 37 | --source "${OUTPUT_CHARTS_DIR}" 38 | -------------------------------------------------------------------------------- /build/release-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | docker push "${IMAGE}:${TAG}" 20 | -------------------------------------------------------------------------------- /build/verify-boilerplate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -euo pipefail 18 | 19 | # REPO_ROOT is used by verify-boilerplate.sh. 20 | : "${REPO_ROOT:=$(git rev-parse --show-toplevel)}" 21 | export REPO_ROOT 22 | 23 | "${REPO_ROOT}/third-party/repo-infra/verify/verify-boilerplate.sh" 24 | -------------------------------------------------------------------------------- /build/verify-modified-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | if [[ "$(git status --porcelain | wc --lines)" != "0" ]]; then 18 | >&2 echo "ERROR: The source tree contains modified files that should be committed or git-ignored." 19 | git status --porcelain --verbose 20 | exit 1 21 | fi 22 | -------------------------------------------------------------------------------- /build/version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | if [ -z "$(git tag --list)" ]; then 20 | if [ -n "${VERSION_FORCE_TAG_FETCH:-}" ]; then 21 | >&2 echo "fetching git tags" 22 | git fetch --tags --all 1> /dev/null 2> /dev/null 23 | >&2 echo "unshallowing git repository" 24 | git fetch --unshallow 1> /dev/null 2> /dev/null 25 | else 26 | >&2 echo "failed to fetch git tags" 27 | exit 1 28 | fi 29 | fi 30 | 31 | git_tag=$(git describe --tags) 32 | 33 | # This dirty check also takes into consideration new files not staged for 34 | # commit. 35 | git_dirty=$([[ -z "$(git status --short)" ]] || echo "-dirty") 36 | 37 | # Use the git tag, removing the leading 'v' if it exists, appended with -dirty 38 | # if there are changes in the tree. 39 | echo "${git_tag/#v/}${git_dirty}" 40 | -------------------------------------------------------------------------------- /charts/minibroker/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: minibroker 2 | description: A minibroker for your minikube 3 | version: 0.0.0 4 | appVersion: 0.0.0 5 | -------------------------------------------------------------------------------- /charts/minibroker/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | 3 | {{/* 4 | Create a default fully qualified app name. 5 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 6 | */}} 7 | {{- define "minibroker.fullname" -}} 8 | {{- printf "%s-%s" .Release.Name .Chart.Name | trunc 63 | trimSuffix "-" -}} 9 | {{- end -}} 10 | 11 | {{/* 12 | Define the standard labels that will be applied to all objects in this chart. 13 | */}} 14 | {{- define "minibroker.labels" -}} 15 | app: {{ include "minibroker.fullname" . | quote }} 16 | chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | quote }} 17 | release: {{ .Release.Name | quote }} 18 | heritage: {{ .Release.Service | quote }} 19 | {{- end -}} 20 | -------------------------------------------------------------------------------- /charts/minibroker/templates/broker.yaml: -------------------------------------------------------------------------------- 1 | {{ if .Values.deployServiceCatalog }} 2 | apiVersion: servicecatalog.k8s.io/v1beta1 3 | kind: ClusterServiceBroker 4 | metadata: 5 | name: minibroker 6 | labels: 7 | {{- include "minibroker.labels" . | nindent 4 }} 8 | spec: 9 | url: {{ printf "http://%s.%s.svc:%d" (include "minibroker.fullname" .) .Release.Namespace (int .Values.broker.service.port) | quote }} 10 | {{ end }} 11 | -------------------------------------------------------------------------------- /charts/minibroker/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | {{- $deploymentPort := 8080 }} 2 | {{- $configPath := "/minibroker" }} 3 | --- 4 | apiVersion: apps/v1 5 | kind: Deployment 6 | metadata: 7 | name: {{ template "minibroker.fullname" . }} 8 | labels: 9 | {{- include "minibroker.labels" . | nindent 4 }} 10 | spec: 11 | replicas: 1 12 | strategy: 13 | type: {{ .Values.deploymentStrategy }} 14 | selector: 15 | matchLabels: 16 | app: {{ template "minibroker.fullname" . }} 17 | template: 18 | metadata: 19 | labels: 20 | {{- include "minibroker.labels" . | nindent 8 }} 21 | spec: 22 | serviceAccountName: minibroker 23 | containers: 24 | - name: minibroker 25 | image: {{ .Values.image | quote }} 26 | imagePullPolicy: {{ .Values.imagePullPolicy }} 27 | securityContext: 28 | runAsNonRoot: true 29 | readOnlyRootFilesystem: true 30 | args: 31 | - minibroker 32 | {{- if .Values.serviceCatalogEnabledOnly }} 33 | - --service-catalog-enabled-only 34 | {{- end }} 35 | {{- if .Values.helmRepoUrl }} 36 | - -helmUrl 37 | - "{{ .Values.helmRepoUrl }}" 38 | {{- end }} 39 | {{- if .Values.defaultNamespace }} 40 | - -defaultNamespace 41 | - "{{ .Values.defaultNamespace }}" 42 | {{- end }} 43 | - --port 44 | - {{ $deploymentPort | quote }} 45 | {{- if .Values.tls.cert }} 46 | - --tlsCert 47 | - "{{ .Values.tls.cert }}" 48 | {{- end }} 49 | {{- if .Values.tls.key }} 50 | - --tlsKey 51 | - "{{ .Values.tls.key }}" 52 | {{- end }} 53 | - -v 54 | - {{ .Values.logLevel | quote }} 55 | - -logtostderr 56 | - --provisioningSettings 57 | - {{ printf "%s/provisioning-settings.yaml" $configPath }} 58 | ports: 59 | - name: broker 60 | containerPort: {{ $deploymentPort }} 61 | env: 62 | - name: CONFIG_NAMESPACE 63 | valueFrom: 64 | fieldRef: 65 | fieldPath: metadata.namespace 66 | readinessProbe: 67 | httpGet: &readinessHTTPGet 68 | path: /healthz 69 | port: {{ $deploymentPort }} 70 | initialDelaySeconds: 5 71 | periodSeconds: 3 72 | livenessProbe: 73 | httpGet: *readinessHTTPGet 74 | initialDelaySeconds: 10 75 | periodSeconds: 15 76 | failureThreshold: 2 77 | volumeMounts: 78 | - name: cache 79 | mountPath: /home/minibroker/.cache 80 | - name: provisioning-settings 81 | mountPath: {{ $configPath | quote}} 82 | readOnly: true 83 | volumes: 84 | - name: cache 85 | emptyDir: {} 86 | - name: provisioning-settings 87 | configMap: 88 | name: {{ printf "%s-provisioning-settings" .Release.Name | quote }} 89 | -------------------------------------------------------------------------------- /charts/minibroker/templates/provisioning_settings.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: ConfigMap 4 | metadata: 5 | name: {{ printf "%s-provisioning-settings" .Release.Name | quote }} 6 | namespace: {{ .Release.Namespace | quote }} 7 | data: 8 | provisioning-settings.yaml: | 9 | {{- toYaml .Values.provisioning | nindent 4 }} 10 | -------------------------------------------------------------------------------- /charts/minibroker/templates/rbac.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.rbac.create }} 2 | {{- if .Values.rbac.serviceAccount.create }} 3 | --- 4 | apiVersion: v1 5 | kind: ServiceAccount 6 | metadata: 7 | name: {{ .Values.rbac.serviceAccount.name | quote }} 8 | labels: 9 | {{- include "minibroker.labels" . | nindent 4 }} 10 | {{- range $k, $v := .Values.rbac.serviceAccount.labels }} 11 | {{ $k | quote }}: {{ $v | quote }} 12 | {{- end }} 13 | annotations: 14 | {{- range $k, $v := .Values.rbac.serviceAccount.annotations }} 15 | {{ $k | quote }}: {{ $v | quote }} 16 | {{- end }} 17 | {{- end }}{{/* if .Values.rbac.serviceAccount.create */}} 18 | 19 | {{- if .Values.rbac.namespaced.enabled }} 20 | {{/* Checks for when the rbac is namespaced. */}} 21 | {{- if .Values.defaultNamespace }} 22 | {{- if not has .Values.defaultNamespace .Values.rbac.namespaced.whitelist }} 23 | {{- fail "The default namespace is not whitelisted in rbac.namespaced.whitelist" }} 24 | {{- end }}{{/* if not has .Values.defaultNamespace .Values.rbac.namespaced.whitelist */}} 25 | {{- end }}{{/* if .Values.defaultNamespace */}} 26 | 27 | --- 28 | apiVersion: rbac.authorization.k8s.io/v1 29 | kind: ClusterRole 30 | metadata: 31 | name: {{ printf "minibroker-%s" .Release.Name | quote }} 32 | labels: 33 | {{- include "minibroker.labels" . | nindent 4 }} 34 | rules: 35 | - apiGroups: [""] 36 | resources: 37 | - namespaces 38 | verbs: 39 | - get 40 | - list 41 | --- 42 | apiVersion: rbac.authorization.k8s.io/v1 43 | kind: ClusterRoleBinding 44 | metadata: 45 | name: {{ printf "minibroker-%s" .Release.Name | quote }} 46 | labels: 47 | {{- include "minibroker.labels" . | nindent 4 }} 48 | roleRef: 49 | apiGroup: rbac.authorization.k8s.io 50 | kind: ClusterRole 51 | name: {{ printf "minibroker-%s" .Release.Name | quote }} 52 | subjects: 53 | - kind: ServiceAccount 54 | name: {{ .Values.rbac.serviceAccount.name | quote }} 55 | namespace: {{ .Release.Namespace | quote }} 56 | --- 57 | apiVersion: rbac.authorization.k8s.io/v1 58 | kind: Role 59 | metadata: 60 | name: minibroker 61 | labels: 62 | {{- include "minibroker.labels" . | nindent 4 }} 63 | rules: 64 | - apiGroups: [""] 65 | resources: ["configmaps"] 66 | verbs: ["*"] 67 | --- 68 | apiVersion: rbac.authorization.k8s.io/v1 69 | kind: RoleBinding 70 | metadata: 71 | name: minibroker 72 | labels: 73 | {{- include "minibroker.labels" . | nindent 4 }} 74 | roleRef: 75 | apiGroup: rbac.authorization.k8s.io 76 | kind: Role 77 | name: minibroker 78 | subjects: 79 | - kind: ServiceAccount 80 | name: {{ .Values.rbac.serviceAccount.name | quote }} 81 | namespace: {{ .Release.Namespace | quote }} 82 | {{- range $namespace := .Values.rbac.namespaced.whitelist }} 83 | --- 84 | apiVersion: rbac.authorization.k8s.io/v1 85 | kind: Role 86 | metadata: 87 | name: minibroker 88 | namespace: {{ $namespace | quote }} 89 | labels: 90 | {{- include "minibroker.labels" . | nindent 4 }} 91 | rules: 92 | - apiGroups: ["*"] 93 | resources: ["*"] 94 | verbs: ["*"] 95 | --- 96 | apiVersion: rbac.authorization.k8s.io/v1 97 | kind: RoleBinding 98 | metadata: 99 | name: minibroker 100 | namespace: {{ $namespace | quote }} 101 | labels: 102 | {{- include "minibroker.labels" . | nindent 4 }} 103 | roleRef: 104 | apiGroup: rbac.authorization.k8s.io 105 | kind: Role 106 | name: minibroker 107 | subjects: 108 | - kind: ServiceAccount 109 | name: {{ $.Values.rbac.serviceAccount.name | quote }} 110 | namespace: {{ $.Release.Namespace | quote }} 111 | {{- end }}{{/* range $namespace := .Values.rbac.namespaced.whitelist */}} 112 | {{- else }}{{/* if .Values.rbac.namespaced.enabled */}} 113 | --- 114 | apiVersion: rbac.authorization.k8s.io/v1 115 | kind: ClusterRole 116 | metadata: 117 | name: {{ printf "minibroker-%s" .Release.Name | quote }} 118 | labels: 119 | {{- include "minibroker.labels" . | nindent 4 }} 120 | rules: 121 | - apiGroups: [""] 122 | resources: 123 | - configmaps 124 | - endpoints 125 | - limitranges 126 | - persistentvolumeclaims 127 | - pods 128 | - podtemplates 129 | - replicationcontrollers 130 | - resourcequotas 131 | - secrets 132 | - serviceaccounts 133 | - services 134 | verbs: ["*"] 135 | - apiGroups: [""] 136 | resources: 137 | - namespaces 138 | verbs: 139 | - get 140 | - list 141 | - apiGroups: 142 | - apps 143 | - autoscaling 144 | - batch 145 | - networking.k8s.io 146 | resources: ["*"] 147 | verbs: ["*"] 148 | - apiGroups: 149 | - rbac.authorization.k8s.io 150 | resources: 151 | - rolebindings 152 | - roles 153 | verbs: ["*"] 154 | - apiGroups: 155 | - policy 156 | resources: 157 | - poddisruptionbudgets 158 | verbs: ["*"] 159 | --- 160 | apiVersion: rbac.authorization.k8s.io/v1 161 | kind: ClusterRoleBinding 162 | metadata: 163 | name: minibroker 164 | labels: 165 | {{- include "minibroker.labels" . | nindent 4 }} 166 | roleRef: 167 | apiGroup: rbac.authorization.k8s.io 168 | kind: ClusterRole 169 | name: {{ printf "minibroker-%s" .Release.Name | quote }} 170 | subjects: 171 | - kind: ServiceAccount 172 | name: {{ .Values.rbac.serviceAccount.name | quote }} 173 | namespace: {{ .Release.Namespace | quote }} 174 | {{- end }}{{/* if .Values.rbac.namespaced.enabled */}} 175 | 176 | {{- end }}{{/* if .Values.rbac.create */}} 177 | -------------------------------------------------------------------------------- /charts/minibroker/templates/service.yaml: -------------------------------------------------------------------------------- 1 | kind: Service 2 | apiVersion: v1 3 | metadata: 4 | name: {{ template "minibroker.fullname" . }} 5 | labels: 6 | {{- include "minibroker.labels" . | nindent 4 }} 7 | spec: 8 | selector: 9 | app: {{ template "minibroker.fullname" . }} 10 | ports: 11 | - protocol: TCP 12 | port: {{ .Values.broker.service.port }} 13 | targetPort: broker 14 | -------------------------------------------------------------------------------- /charts/minibroker/values.yaml: -------------------------------------------------------------------------------- 1 | # Default values for the minibroker 2 | # Image to use 3 | image: <%image%> 4 | # ImagePullPolicy; valid values are "IfNotPresent", "Never", and "Always" 5 | imagePullPolicy: IfNotPresent 6 | deploymentPolicy: RollingUpdate 7 | # Certificate details to use for TLS. Leave blank to not use TLS 8 | tls: 9 | # base-64 encoded PEM data for the TLS certificate 10 | cert: ~ 11 | # base-64 encoded PEM data for the private key matching the certificate 12 | key: ~ 13 | 14 | # The service broker server configuration. 15 | broker: 16 | # The service configuration. 17 | service: 18 | # A port for the service broker HTTP API. 19 | port: 80 20 | 21 | # The logging level to use; higher values emit more information 22 | logLevel: 3 23 | 24 | serviceCatalogEnabledOnly: true 25 | 26 | deployServiceCatalog: true 27 | 28 | # A default namespace where Minibroker deploys service instances. 29 | defaultNamespace: ~ 30 | 31 | rbac: 32 | create: true 33 | namespaced: 34 | # Whether Minibroker will be namespaced or not. When enabled, Minibroker will only be able to 35 | # manage resources in the whitelisted namespaces. 36 | enabled: false 37 | # A list of namespaces Minibroker can use to deploy service instances. The namespaces are 38 | # expected to be created ahead of time. 39 | whitelist: [] 40 | serviceAccount: 41 | create: true 42 | name: minibroker 43 | annotations: {} 44 | labels: {} 45 | 46 | # Optional override parameters for each of the supported service classes. 47 | # If defined, user-provided parameters during provisioning are ignored and 48 | # these overrides are used. 49 | # Example: 50 | # 51 | # provisioning: 52 | # rabbitmq: 53 | # replicas: 1 54 | # ingress: 55 | # enabled: false 56 | provisioning: 57 | mariadb: 58 | overrideParams: ~ 59 | mongodb: 60 | overrideParams: ~ 61 | mysql: 62 | overrideParams: ~ 63 | postgresql: 64 | overrideParams: ~ 65 | rabbitmq: 66 | overrideParams: ~ 67 | redis: 68 | overrideParams: ~ 69 | -------------------------------------------------------------------------------- /charts/wordpress/.helmignore: -------------------------------------------------------------------------------- 1 | .git 2 | # OWNERS file for Kubernetes 3 | OWNERS 4 | -------------------------------------------------------------------------------- /charts/wordpress/Chart.yaml: -------------------------------------------------------------------------------- 1 | name: wordpress 2 | version: 0.1.0 3 | appVersion: 4.9.4 4 | description: Web publishing platform for building blogs and websites. 5 | icon: https://bitnami.com/assets/stacks/wordpress/img/wordpress-stack-220x234.png 6 | keywords: 7 | - wordpress 8 | - cms 9 | - blog 10 | - http 11 | - web 12 | - application 13 | - php 14 | home: http://www.wordpress.com/ 15 | sources: 16 | - https://github.com/bitnami/bitnami-docker-wordpress 17 | maintainers: 18 | - name: Kent Rancourt 19 | email: kent.rancourt@microsoft.com 20 | engine: gotpl 21 | -------------------------------------------------------------------------------- /charts/wordpress/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | 1. Get the WordPress URL: 2 | 3 | export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ template "fullname" . }}) 4 | export MINIKUBE_IP=$(minikube ip) 5 | echo http://$MINIKUBE_IP:$NODE_PORT/admin 6 | 7 | 2. Login with the following credentials to see your blog 8 | 9 | echo Username: {{ .Values.wordpressUsername }} 10 | echo Password: $(kubectl get secret --namespace {{ .Release.Namespace }} {{ template "fullname" . }} -o jsonpath="{.data.wordpress-password}" | base64 --decode) 11 | -------------------------------------------------------------------------------- /charts/wordpress/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | */}} 13 | {{- define "fullname" -}} 14 | {{- $name := default .Chart.Name .Values.nameOverride -}} 15 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 16 | {{- end -}} 17 | 18 | {{/* 19 | Create a default fully qualified app name. 20 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 21 | */}} 22 | {{- define "mariadb.fullname" -}} 23 | {{- printf "%s-%s" .Release.Name "mariadb" | trunc 63 | trimSuffix "-" -}} 24 | {{- end -}} 25 | -------------------------------------------------------------------------------- /charts/wordpress/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | spec: 11 | selector: 12 | matchLabels: 13 | app: {{ template "fullname" . }} 14 | replicas: 1 15 | template: 16 | metadata: 17 | labels: 18 | app: {{ template "fullname" . }} 19 | spec: 20 | hostAliases: 21 | - ip: "127.0.0.1" 22 | hostnames: 23 | - status.localhost 24 | securityContext: 25 | runAsUser: 1001 26 | fsGroup: 1001 27 | containers: 28 | - name: {{ template "fullname" . }} 29 | image: "{{ .Values.image }}" 30 | imagePullPolicy: {{ default "" .Values.imagePullPolicy | quote }} 31 | env: 32 | - name: ALLOW_EMPTY_PASSWORD 33 | value: "yes" 34 | - name: WORDPRESS_HTACCESS_OVERRIDE_NONE 35 | value: "no" 36 | - name: WORDPRESS_HTACCESS_PERSISTENCE_ENABLED 37 | value: "no" 38 | - name: WORDPRESS_SKIP_INSTALL 39 | value: "no" 40 | - name: WORDPRESS_TABLE_PREFIX 41 | value: "wp_" 42 | - name: WORDPRESS_SCHEME 43 | value: "http" 44 | - name: WORDPRESS_EXTRA_WP_CONFIG_CONTENT 45 | value: "" 46 | - name: MARIADB_HOST 47 | valueFrom: 48 | secretKeyRef: 49 | name: {{ template "fullname" . }}-mysql-secret 50 | key: host 51 | - name: MARIADB_PORT_NUMBER 52 | valueFrom: 53 | secretKeyRef: 54 | name: {{ template "fullname" . }}-mysql-secret 55 | key: port 56 | - name: WORDPRESS_DATABASE_NAME 57 | valueFrom: 58 | secretKeyRef: 59 | name: {{ template "fullname" . }}-mysql-secret 60 | key: database 61 | - name: WORDPRESS_DATABASE_USER 62 | valueFrom: 63 | secretKeyRef: 64 | name: {{ template "fullname" . }}-mysql-secret 65 | key: username 66 | - name: WORDPRESS_DATABASE_PASSWORD 67 | valueFrom: 68 | secretKeyRef: 69 | name: {{ template "fullname" . }}-mysql-secret 70 | key: password 71 | - name: WORDPRESS_USERNAME 72 | value: {{ default "" .Values.wordpressUsername | quote }} 73 | - name: WORDPRESS_PASSWORD 74 | valueFrom: 75 | secretKeyRef: 76 | name: {{ template "fullname" . }} 77 | key: wordpress-password 78 | - name: WORDPRESS_EMAIL 79 | value: {{ default "" .Values.wordpressEmail | quote }} 80 | - name: WORDPRESS_FIRST_NAME 81 | value: {{ default "" .Values.wordpressFirstName | quote }} 82 | - name: WORDPRESS_LAST_NAME 83 | value: {{ default "" .Values.wordpressLastName | quote }} 84 | - name: WORDPRESS_BLOG_NAME 85 | value: {{ default "" .Values.wordpressBlogName | quote }} 86 | - name: SMTP_HOST 87 | value: {{ default "" .Values.smtpHost | quote }} 88 | - name: SMTP_PORT 89 | value: {{ default "" .Values.smtpPort | quote }} 90 | - name: SMTP_USER 91 | value: {{ default "" .Values.smtpUser | quote }} 92 | - name: SMTP_PASSWORD 93 | valueFrom: 94 | secretKeyRef: 95 | name: {{ template "fullname" . }} 96 | key: smtp-password 97 | - name: SMTP_USERNAME 98 | value: {{ default "" .Values.smtpUsername | quote }} 99 | - name: SMTP_PROTOCOL 100 | value: {{ default "" .Values.smtpProtocol | quote }} 101 | ports: 102 | - name: http 103 | containerPort: 8080 104 | - name: https 105 | containerPort: 8443 106 | livenessProbe: 107 | httpGet: 108 | path: /wp-login.php 109 | {{- if not .Values.healthcheckHttps }} 110 | port: http 111 | {{- else }} 112 | port: https 113 | scheme: HTTPS 114 | {{- end }} 115 | {{ toYaml .Values.livenessProbe | indent 10 }} 116 | readinessProbe: 117 | httpGet: 118 | path: /wp-login.php 119 | {{- if not .Values.healthcheckHttps }} 120 | port: http 121 | {{- else }} 122 | port: https 123 | scheme: HTTPS 124 | {{- end }} 125 | {{ toYaml .Values.readinessProbe | indent 10 }} 126 | volumeMounts: 127 | - mountPath: /bitnami/wordpress 128 | name: wordpress-data 129 | subPath: wordpress 130 | resources: 131 | {{ toYaml .Values.resources | indent 10 }} 132 | volumes: 133 | - name: wordpress-data 134 | emptyDir: {} 135 | {{- if .Values.nodeSelector }} 136 | nodeSelector: 137 | {{ toYaml .Values.nodeSelector | indent 8 }} 138 | {{- end -}} 139 | -------------------------------------------------------------------------------- /charts/wordpress/templates/ingress.yaml: -------------------------------------------------------------------------------- 1 | {{- if .Values.ingress.enabled }} 2 | {{- range .Values.ingress.hosts }} 3 | apiVersion: extensions/v1beta1 4 | kind: Ingress 5 | metadata: 6 | name: "{{- printf "%s-%s" .name $.Release.Name | trunc 63 | trimSuffix "-" -}}" 7 | labels: 8 | app: {{ template "fullname" $ }} 9 | chart: "{{ $.Chart.Name }}-{{ $.Chart.Version }}" 10 | release: "{{ $.Release.Name }}" 11 | heritage: "{{ $.Release.Service }}" 12 | annotations: 13 | {{- if .tls }} 14 | ingress.kubernetes.io/secure-backends: "true" 15 | {{- end }} 16 | {{- range $key, $value := .annotations }} 17 | {{ $key }}: {{ $value | quote }} 18 | {{- end }} 19 | spec: 20 | rules: 21 | - host: {{ .name }} 22 | http: 23 | paths: 24 | - path: {{ default "/" .path }} 25 | backend: 26 | serviceName: {{ template "fullname" $ }} 27 | servicePort: 80 28 | {{- if .tls }} 29 | tls: 30 | - hosts: 31 | - {{ .name }} 32 | secretName: {{ .tlsSecret }} 33 | {{- end }} 34 | --- 35 | {{- end }} 36 | {{- end }} 37 | -------------------------------------------------------------------------------- /charts/wordpress/templates/mysql-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: servicecatalog.k8s.io/v1beta1 2 | kind: ServiceBinding 3 | metadata: 4 | name: {{ template "fullname" . }}-mysql-binding 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | spec: 11 | instanceRef: 12 | name: {{ template "fullname" . }}-mysql-instance 13 | secretName: {{ template "fullname" . }}-mysql-secret 14 | -------------------------------------------------------------------------------- /charts/wordpress/templates/mysql-instance.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: servicecatalog.k8s.io/v1beta1 2 | kind: ServiceInstance 3 | metadata: 4 | name: {{ template "fullname" . }}-mysql-instance 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | spec: 11 | clusterServiceClassExternalName: {{ .Values.externalDatabase.minibroker.class }} 12 | clusterServicePlanExternalName: {{ .Values.externalDatabase.minibroker.servicePlan }} 13 | {{- if .Values.externalDatabase.minibroker.parameters }} 14 | parameters: {{ toJson .Values.externalDatabase.minibroker.parameters }} 15 | {{- end }} 16 | -------------------------------------------------------------------------------- /charts/wordpress/templates/secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | type: Opaque 11 | data: 12 | {{ if .Values.wordpressPassword }} 13 | wordpress-password: {{ default "" .Values.wordpressPassword | b64enc | quote }} 14 | {{ else }} 15 | wordpress-password: {{ randAlphaNum 10 | b64enc | quote }} 16 | {{ end }} 17 | smtp-password: {{ default "" .Values.smtpPassword | b64enc | quote }} 18 | -------------------------------------------------------------------------------- /charts/wordpress/templates/svc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: {{ template "fullname" . }} 5 | labels: 6 | app: {{ template "fullname" . }} 7 | chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" 8 | release: "{{ .Release.Name }}" 9 | heritage: "{{ .Release.Service }}" 10 | spec: 11 | type: {{ .Values.serviceType }} 12 | ports: 13 | - name: http 14 | port: 80 15 | targetPort: http 16 | - name: https 17 | port: 443 18 | targetPort: https 19 | selector: 20 | app: {{ template "fullname" . }} 21 | -------------------------------------------------------------------------------- /charts/wordpress/values.yaml: -------------------------------------------------------------------------------- 1 | ## Bitnami WordPress image version 2 | ## ref: https://hub.docker.com/r/bitnami/wordpress/tags/ 3 | ## 4 | image: bitnami/wordpress:5.5.1 5 | 6 | ## Specify a imagePullPolicy 7 | ## ref: http://kubernetes.io/docs/user-guide/images/#pre-pulling-images 8 | ## 9 | imagePullPolicy: IfNotPresent 10 | 11 | ## User of the application 12 | ## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables 13 | ## 14 | wordpressUsername: user 15 | 16 | ## Application password 17 | ## Defaults to a random 10-character alphanumeric string if not set 18 | ## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables 19 | ## 20 | # wordpressPassword: 21 | 22 | ## Admin email 23 | ## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables 24 | ## 25 | wordpressEmail: user@example.com 26 | 27 | ## First name 28 | ## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables 29 | ## 30 | wordpressFirstName: FirstName 31 | 32 | ## Last name 33 | ## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables 34 | ## 35 | wordpressLastName: LastName 36 | 37 | ## Blog name 38 | ## ref: https://github.com/bitnami/bitnami-docker-wordpress#environment-variables 39 | ## 40 | wordpressBlogName: User's Blog! 41 | 42 | ## SMTP mail delivery configuration 43 | ## ref: https://github.com/bitnami/bitnami-docker-wordpress/#smtp-configuration 44 | ## 45 | # smtpHost: 46 | # smtpPort: 47 | # smtpUser: 48 | # smtpPassword: 49 | # smtpUsername: 50 | # smtpProtocol: 51 | 52 | externalDatabase: 53 | minibroker: 54 | class: mariadb 55 | ## The plan to request from Minibroker, use svcat get plans --class mariadb to see your options. 56 | servicePlan: 10-3-22 57 | parameters: 58 | db: 59 | name: bitnami_wordpress 60 | user: bn_wordpress 61 | replication: 62 | enabled: false 63 | 64 | ## Kubernetes configuration 65 | ## For minikube, set this to NodePort, elsewhere use LoadBalancer 66 | ## 67 | serviceType: NodePort 68 | 69 | ## Allow health checks to be pointed at the https port 70 | healthcheckHttps: false 71 | 72 | ## Configure extra options for liveness and readiness probes 73 | ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/#configure-probes) 74 | livenessProbe: 75 | initialDelaySeconds: 120 76 | periodSeconds: 10 77 | timeoutSeconds: 5 78 | failureThreshold: 6 79 | successThreshold: 1 80 | readinessProbe: 81 | initialDelaySeconds: 30 82 | periodSeconds: 10 83 | timeoutSeconds: 5 84 | failureThreshold: 6 85 | successThreshold: 1 86 | 87 | ## Configure ingress resource that allow you to access the 88 | ## Wordpress instalation. Set up the URL 89 | ## ref: http://kubernetes.io/docs/user-guide/ingress/ 90 | ## 91 | ingress: 92 | ## Set to true to enable ingress record generation 93 | enabled: false 94 | 95 | ## The list of hostnames to be covered with this ingress record. 96 | ## Most likely this will be just one host, but in the event more hosts are needed, this is an array 97 | hosts: 98 | - name: wordpress.local 99 | 100 | ## Set this to true in order to enable TLS on the ingress record 101 | ## A side effect of this will be that the backend wordpress service will be connected at port 443 102 | tls: false 103 | 104 | ## If TLS is set to true, you must declare what secret will store the key/certificate for TLS 105 | tlsSecret: wordpress.local-tls 106 | 107 | ## Ingress annotations done as key:value pairs 108 | ## If you're using kube-lego, you will want to add: 109 | ## kubernetes.io/tls-acme: true 110 | ## 111 | ## For a full list of possible ingress annotations, please see 112 | ## ref: https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/nginx-configuration/annotations.md 113 | ## 114 | ## If tls is set to true, annotation ingress.kubernetes.io/secure-backends: "true" will automatically be set 115 | annotations: 116 | # kubernetes.io/ingress.class: nginx 117 | # kubernetes.io/tls-acme: true 118 | 119 | secrets: 120 | ## If you're providing your own certificates, please use this to add the certificates as secrets 121 | ## key and certificate should start with -----BEGIN CERTIFICATE----- or 122 | ## -----BEGIN RSA PRIVATE KEY----- 123 | ## 124 | ## name should line up with a tlsSecret set further up 125 | ## If you're using kube-lego, this is unneeded, as it will create the secret for you if it is not set 126 | ## 127 | ## It is also possible to create and manage the certificates outside of this helm chart 128 | ## Please see README.md for more information 129 | # - name: wordpress.local-tls 130 | # key: 131 | # certificate: 132 | 133 | ## Configure resource requests and limits 134 | ## ref: http://kubernetes.io/docs/user-guide/compute-resources/ 135 | ## 136 | resources: 137 | requests: 138 | memory: 512Mi 139 | cpu: 300m 140 | 141 | ## Node labels for pod assignment 142 | ## Ref: https://kubernetes.io/docs/user-guide/node-selection/ 143 | ## 144 | nodeSelector: {} 145 | -------------------------------------------------------------------------------- /ci/install/integration.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | readonly cache_dir="${HOME}/.cache/binaries" 20 | mkdir -p "${cache_dir}" 21 | 22 | # Install kubectl. 23 | version="v1.18.1" 24 | sha256="f5144823e6d8a0b78611a8d12e7a25202126d079c3a232b18f37e61e872ff563" 25 | asset_path="${cache_dir}/kubectl" 26 | asset_url="https://dl.k8s.io/release/${version}/bin/linux/amd64/kubectl" 27 | if [ ! -f "${asset_path}" ] || [[ "$(sha256sum "${asset_path}" | awk '{ print $1 }')" != "${sha256}" ]]; then 28 | curl -Lo "${asset_path}" "${asset_url}" 29 | chmod +x "${asset_path}" 30 | fi 31 | sudo cp "${asset_path}" /usr/local/bin/kubectl 32 | 33 | # Install minikube. 34 | version="v1.24.0" 35 | sha256="3bc218476cf205acf11b078d45210a4882e136d24a3cbb7d8d645408e423b8fe" 36 | asset_path="${cache_dir}/minikube" 37 | asset_url="https://storage.googleapis.com/minikube/releases/${version}/minikube-linux-amd64" 38 | if [ ! -f "${asset_path}" ] || [[ "$(sha256sum "${asset_path}" | awk '{ print $1 }')" != "${sha256}" ]]; then 39 | curl -Lo "${asset_path}" "${asset_url}" 40 | chmod +x "${asset_path}" 41 | fi 42 | sudo cp "${asset_path}" /usr/local/bin/minikube 43 | 44 | # Install helm. 45 | version="v3.2.1" 46 | sha256="018f9908cb950701a5d59e757653a790c66d8eda288625dbb185354ca6f41f6b" 47 | asset_path="${cache_dir}/helm.tar.gz" 48 | asset_url="https://get.helm.sh/helm-${version}-linux-amd64.tar.gz" 49 | if [ ! -f "${asset_path}" ] || [[ "$(sha256sum "${asset_path}" | awk '{ print $1 }')" != "${sha256}" ]]; then 50 | curl -Lo "${asset_path}" "${asset_url}" 51 | fi 52 | sudo tar zxf "${asset_path}" --strip-components=1 --directory /usr/local/bin/ linux-amd64/helm 53 | 54 | # Install svcat. 55 | version="v0.3.0" 56 | sha256="84ec798e8837982dfe13e5a02bf83e801af2461323ab2c441787d7d9f7bad60a" 57 | asset_path="${cache_dir}/svcat" 58 | asset_url="https://download.svcat.sh/cli/${version}/linux/amd64/svcat" 59 | if [ ! -f "${asset_path}" ] || [[ "$(sha256sum "${asset_path}" | awk '{ print $1 }')" != "${sha256}" ]]; then 60 | curl -Lo "${asset_path}" "${asset_url}" 61 | chmod +x "${asset_path}" 62 | fi 63 | sudo cp "${asset_path}" /usr/local/bin/svcat 64 | -------------------------------------------------------------------------------- /ci/install/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | # Install helm. 20 | version="v3.2.1" 21 | sha256="018f9908cb950701a5d59e757653a790c66d8eda288625dbb185354ca6f41f6b" 22 | asset_path="${HOME}/assets/helm.tar.gz" 23 | asset_url="https://get.helm.sh/helm-${version}-linux-amd64.tar.gz" 24 | if ! echo "${sha256} ${asset_path}" | sha256sum --check; then 25 | curl -Lo "${asset_path}" "${asset_url}" 26 | fi 27 | sudo tar zxf "${asset_path}" --strip-components=1 --directory /usr/local/bin/ linux-amd64/helm 28 | 29 | # Install Azure CLI. 30 | curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash 31 | -------------------------------------------------------------------------------- /ci/release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | docker login -u "$DOCKER_USERNAME" --password-stdin "${DOCKER_SERVER}" <<<"${DOCKER_PASSWORD}" 20 | make release 21 | -------------------------------------------------------------------------------- /ci/test_integration.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2020 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | timeout 3m minikube start \ 20 | --cpus="$(nproc)" \ 21 | --memory=6g \ 22 | --kubernetes-version=v1.21.8 23 | 24 | catalog_repository="svc-cat" 25 | catalog_release="catalog" 26 | catalog_namespace="svc-cat" 27 | helm repo add "${catalog_repository}" https://kubernetes-sigs.github.io/service-catalog 28 | timeout 30s helm repo update 29 | timeout 1m helm install "${catalog_release}" \ 30 | --namespace "${catalog_namespace}" \ 31 | --create-namespace \ 32 | --wait \ 33 | "${catalog_repository}/catalog" 34 | 35 | timeout 1m kubectl create namespace minibroker-tests 36 | timeout 10m make image 37 | timeout 1m make charts 38 | timeout 1m make minikube-load-image 39 | timeout 5m make deploy 40 | timeout 15m make test-integration 41 | -------------------------------------------------------------------------------- /cmd/minibroker/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "encoding/json" 22 | "flag" 23 | "fmt" 24 | "os" 25 | "os/signal" 26 | "strconv" 27 | "syscall" 28 | 29 | "github.com/kubernetes-sigs/minibroker/pkg/broker" 30 | "github.com/kubernetes-sigs/minibroker/pkg/kubernetes" 31 | "github.com/pmorie/osb-broker-lib/pkg/metrics" 32 | prom "github.com/prometheus/client_golang/prometheus" 33 | klog "k8s.io/klog/v2" 34 | 35 | "github.com/pmorie/osb-broker-lib/pkg/rest" 36 | "github.com/pmorie/osb-broker-lib/pkg/server" 37 | ) 38 | 39 | var ( 40 | version = "0.0.0" 41 | buildDate = "" 42 | ) 43 | 44 | var options struct { 45 | broker.Options 46 | 47 | Port int 48 | TLSCert string 49 | TLSKey string 50 | } 51 | 52 | func main() { 53 | flag.BoolVar(&options.ServiceCatalogEnabledOnly, "service-catalog-enabled-only", false, 54 | "Only list Service Catalog Enabled services") 55 | flag.IntVar(&options.Port, "port", 8005, 56 | "use '--port' option to specify the port for broker to listen on") 57 | flag.StringVar(&options.TLSCert, "tlsCert", "", 58 | "base-64 encoded PEM block to use as the certificate for TLS. If '--tlsCert' is used, then '--tlsKey' must also be used. If '--tlsCert' is not used, then TLS will not be used.") 59 | flag.StringVar(&options.TLSKey, "tlsKey", "", 60 | "base-64 encoded PEM block to use as the private key matching the TLS certificate. If '--tlsKey' is used, then '--tlsCert' must also be used") 61 | flag.StringVar(&options.CatalogPath, "catalogPath", "", 62 | "The path to the catalog") 63 | flag.StringVar(&options.HelmRepoURL, "helmUrl", "", 64 | "The url to the helm repo") 65 | flag.StringVar(&options.DefaultNamespace, "defaultNamespace", "", 66 | "The default namespace for brokers when the request doesn't specify") 67 | flag.StringVar(&options.ProvisioningSettingsPath, "provisioningSettings", "", 68 | "The path to the YAML file where the optional provisioning settings are stored") 69 | flag.StringVar(&options.ClusterDomain, "clusterDomain", "", 70 | "The k8s cluster domain - if not set, Minibroker infers from /etc/resolv.conf") 71 | flag.Parse() 72 | 73 | klogFlags := flag.NewFlagSet("klog", flag.ExitOnError) 74 | klog.InitFlags(klogFlags) 75 | // Sync the glog and klog flags. 76 | flag.CommandLine.VisitAll(func(f1 *flag.Flag) { 77 | f2 := klogFlags.Lookup(f1.Name) 78 | if f2 != nil { 79 | value := f1.Value.String() 80 | f2.Value.Set(value) 81 | } 82 | }) 83 | defer klog.Flush() 84 | 85 | if options.ClusterDomain == "" { 86 | resolvConf, err := os.Open("/etc/resolv.conf") 87 | if err != nil { 88 | klog.Fatalln(err) 89 | } 90 | // An assurance for the future-proof copy-paste of this block! Yes, this 91 | // is not necessary here but shall be if other events happen in the 92 | // future. 93 | defer resolvConf.Close() 94 | 95 | if options.ClusterDomain, err = kubernetes.ClusterDomain(resolvConf); err != nil { 96 | klog.Fatalln(err) 97 | } 98 | resolvConf.Close() 99 | } 100 | 101 | if err := run(); err != nil && err != context.Canceled && err != context.DeadlineExceeded { 102 | klog.Fatalln(err) 103 | } 104 | } 105 | 106 | func run() error { 107 | ctx, cancelFunc := context.WithCancel(context.Background()) 108 | defer cancelFunc() 109 | go cancelOnInterrupt(ctx, cancelFunc) 110 | 111 | return runWithContext(ctx) 112 | } 113 | 114 | func runWithContext(ctx context.Context) error { 115 | if flag.Arg(0) == "version" { 116 | printVersion() 117 | return nil 118 | } 119 | if (options.TLSCert != "" || options.TLSKey != "") && 120 | (options.TLSCert == "" || options.TLSKey == "") { 121 | err := fmt.Errorf("failed to start Minibroker: to use TLS, both --tlsCert and --tlsKey must be used") 122 | return err 123 | } 124 | 125 | addr := ":" + strconv.Itoa(options.Port) 126 | 127 | options.Options.ConfigNamespace = os.Getenv("CONFIG_NAMESPACE") 128 | 129 | b, err := broker.NewBrokerFromOptions(options.Options) 130 | if err != nil { 131 | return err 132 | } 133 | 134 | // Prometheus metrics 135 | reg := prom.NewRegistry() 136 | osbMetrics := metrics.New() 137 | reg.MustRegister(osbMetrics) 138 | 139 | api, err := rest.NewAPISurface(b, osbMetrics) 140 | if err != nil { 141 | return err 142 | } 143 | 144 | s := server.New(api, reg) 145 | 146 | klog.V(1).Infof("starting broker!") 147 | 148 | if options.TLSCert == "" && options.TLSKey == "" { 149 | err = s.Run(ctx, addr) 150 | } else { 151 | err = s.RunTLS(ctx, addr, options.TLSCert, options.TLSKey) 152 | } 153 | return err 154 | } 155 | 156 | func cancelOnInterrupt(ctx context.Context, f context.CancelFunc) { 157 | term := make(chan os.Signal, 1) 158 | signal.Notify(term, os.Interrupt, syscall.SIGTERM) 159 | 160 | for { 161 | select { 162 | case <-term: 163 | klog.V(1).Infof("received SIGTERM, exiting gracefully...") 164 | f() 165 | os.Exit(0) 166 | case <-ctx.Done(): 167 | os.Exit(0) 168 | } 169 | } 170 | } 171 | 172 | func printVersion() { 173 | v := map[string]interface{}{ 174 | "version": version, 175 | "build_date": buildDate, 176 | } 177 | encoder := json.NewEncoder(os.Stderr) 178 | encoder.SetIndent("", " ") 179 | encoder.Encode(v) 180 | } 181 | -------------------------------------------------------------------------------- /code-of-conduct.md: -------------------------------------------------------------------------------- 1 | # Kubernetes Community Code of Conduct 2 | 3 | Please refer to our [Kubernetes Community Code of Conduct](https://git.k8s.io/community/code-of-conduct.md) 4 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2019 The Kubernetes Authors. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | ARG BUILDER_IMAGE 16 | ARG DOWNLOADER_IMAGE 17 | ARG CERT_BUILDER_IMAGE 18 | ARG RUNNING_IMAGE 19 | 20 | # -------------------------------------------------------------------------------------------------- 21 | # The building stage. 22 | FROM ${BUILDER_IMAGE} AS builder 23 | 24 | WORKDIR /build/minibroker 25 | # Copy the go.mod over so docker can cache the module downloads if possible. 26 | COPY go.mod go.sum ./ 27 | RUN go mod download 28 | 29 | COPY cmd/ ./cmd/ 30 | COPY pkg/ ./pkg/ 31 | COPY Makefile ./ 32 | ARG TAG 33 | ENV TAG $TAG 34 | RUN make build 35 | 36 | # -------------------------------------------------------------------------------------------------- 37 | # The downloading stage. 38 | FROM ${DOWNLOADER_IMAGE} AS downloader 39 | 40 | RUN wget -O /tmp/dumb-init https://github.com/Yelp/dumb-init/releases/download/v1.2.2/dumb-init_1.2.2_amd64 41 | RUN chmod +x /tmp/dumb-init 42 | 43 | # -------------------------------------------------------------------------------------------------- 44 | # The cert building stage. 45 | FROM ${CERT_BUILDER_IMAGE} AS cert_builder 46 | 47 | ARG CURL_VERSION="7.70.0" 48 | 49 | RUN zypper refresh 50 | RUN zypper --non-interactive install perl-Encode make tar gzip curl 51 | 52 | RUN curl -L -o /tmp/curl-${CURL_VERSION}.tar.gz https://github.com/curl/curl/releases/download/curl-${CURL_VERSION//\./_}/curl-${CURL_VERSION}.tar.gz 53 | RUN tar zxf /tmp/curl-${CURL_VERSION}.tar.gz 54 | WORKDIR /curl-${CURL_VERSION} 55 | 56 | RUN make ca-bundle 57 | RUN cp lib/ca-bundle.crt /tmp/ca-bundle.crt 58 | 59 | # -------------------------------------------------------------------------------------------------- 60 | # The running stage. 61 | FROM ${RUNNING_IMAGE} 62 | 63 | COPY --from=cert_builder /tmp/ca-bundle.crt /etc/ssl/certs/ca-bundle.crt 64 | COPY --from=downloader /tmp/dumb-init /usr/local/bin/dumb-init 65 | COPY --from=builder /build/minibroker/output/minibroker /usr/local/bin/minibroker 66 | 67 | COPY docker/rootfs/etc/passwd /etc/passwd 68 | COPY --chown=1000 docker/rootfs/home/minibroker /home/minibroker 69 | USER 1000 70 | 71 | VOLUME /home/minibroker 72 | ENV TMPDIR /home/minibroker/tmp 73 | ENTRYPOINT ["dumb-init", "--"] 74 | CMD ["minibroker", "--help"] 75 | -------------------------------------------------------------------------------- /docker/rootfs/etc/passwd: -------------------------------------------------------------------------------- 1 | minibroker:x:1000:1000::/home/minibroker:/bin/bash 2 | -------------------------------------------------------------------------------- /docker/rootfs/home/minibroker/tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubernetes-retired/minibroker/46134f2de11b27875e82eccdf6594bacc2526af8/docker/rootfs/home/minibroker/tmp/.gitkeep -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubernetes-sigs/minibroker 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Masterminds/semver v1.4.0 7 | github.com/containers/libpod v1.9.3 8 | github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 9 | github.com/golang/mock v1.5.0 10 | github.com/onsi/ginkgo v1.16.4 11 | github.com/onsi/gomega v1.17.0 12 | github.com/pkg/errors v0.9.1 13 | github.com/pmorie/go-open-service-broker-client v0.0.0-20180304212357-e8aa16c90363 14 | github.com/pmorie/osb-broker-lib v0.0.0-20180516212803-87d71cfbf342 15 | github.com/prometheus/client_golang v1.6.0 16 | helm.sh/helm/v3 v3.2.3 17 | k8s.io/api v0.18.0 18 | k8s.io/apimachinery v0.18.0 19 | k8s.io/cli-runtime v0.18.0 20 | k8s.io/client-go v0.18.0 21 | k8s.io/klog/v2 v2.5.0 22 | rsc.io/letsencrypt v0.0.3 // indirect 23 | ) 24 | -------------------------------------------------------------------------------- /hack/create-cluster.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Copyright 2019 The Kubernetes Authors. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | set -o errexit -o nounset -o pipefail 18 | 19 | : "${VM_DRIVER:=docker}" 20 | : "${VM_CPUS:=2}" 21 | : "${VM_MEMORY:=$(( 1024 * 4 ))}" 22 | : "${KUBERNETES_VERSION:=v1.15.12}" 23 | 24 | export MINIKUBE_WANTUPDATENOTIFICATION=false 25 | 26 | if [[ "$(minikube status)" != *"Running"* ]]; then 27 | set -o xtrace 28 | minikube start \ 29 | --driver="${VM_DRIVER}" \ 30 | --cpus="${VM_CPUS}" \ 31 | --memory="${VM_MEMORY}" \ 32 | --kubernetes-version="${KUBERNETES_VERSION}" 33 | else 34 | >&2 echo "A Minikube instance is already running..." 35 | exit 1 36 | fi 37 | 38 | catalog_repository="svc-cat" 39 | catalog_release="catalog" 40 | catalog_namespace="svc-cat" 41 | helm repo add "${catalog_repository}" https://kubernetes-sigs.github.io/service-catalog 42 | helm repo update 43 | kubectl create namespace "${catalog_namespace}" 44 | helm install "${catalog_release}" \ 45 | --namespace "${catalog_namespace}" \ 46 | "${catalog_repository}/catalog" 47 | 48 | set +o xtrace 49 | while [[ "$(kubectl get pods --namespace "${catalog_namespace}" --selector "release=${catalog_release}" --output=go-template='{{.items | len}}')" == 0 ]]; do 50 | sleep 1; 51 | done 52 | set -o xtrace 53 | kubectl wait pods \ 54 | --for condition=ready \ 55 | --namespace "${catalog_namespace}" \ 56 | --selector "release=${catalog_release}" \ 57 | --timeout 90s 58 | -------------------------------------------------------------------------------- /pkg/broker/broker_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package broker_test 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestBroker(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Broker Suite") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/broker/broker_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package broker_test 18 | 19 | import ( 20 | "github.com/ghodss/yaml" 21 | "github.com/golang/mock/gomock" 22 | osb "github.com/pmorie/go-open-service-broker-client/v2" 23 | osbbroker "github.com/pmorie/osb-broker-lib/pkg/broker" 24 | 25 | . "github.com/onsi/ginkgo" 26 | . "github.com/onsi/gomega" 27 | 28 | "github.com/kubernetes-sigs/minibroker/pkg/broker" 29 | "github.com/kubernetes-sigs/minibroker/pkg/broker/mocks" 30 | "github.com/kubernetes-sigs/minibroker/pkg/minibroker" 31 | ) 32 | 33 | //go:generate mockgen -destination=./mocks/mock_broker.go -package=mocks github.com/kubernetes-sigs/minibroker/pkg/broker MinibrokerClient 34 | 35 | const ( 36 | overrideParamsYaml = ` 37 | mariadb: 38 | overrideParams: 39 | mariadb: value 40 | mongodb: 41 | overrideParams: 42 | mongodb: value 43 | mysql: 44 | overrideParams: 45 | mysql: value 46 | postgresql: 47 | overrideParams: 48 | postgresql: value 49 | rabbitmq: 50 | overrideParams: 51 | rabbitmq: value 52 | redis: 53 | overrideParams: 54 | redis: value 55 | ` 56 | ) 57 | 58 | var _ = Describe("Broker", func() { 59 | var ( 60 | ctrl *gomock.Controller 61 | 62 | b *broker.Broker 63 | mbclient *mocks.MockMinibrokerClient 64 | 65 | provisioningSettings = &broker.ProvisioningSettings{} 66 | namespace = "namespace" 67 | ) 68 | 69 | BeforeEach(func() { 70 | ctrl = gomock.NewController(GinkgoT()) 71 | mbclient = mocks.NewMockMinibrokerClient(ctrl) 72 | }) 73 | 74 | JustBeforeEach(func() { 75 | b = broker.NewBroker(mbclient, namespace, provisioningSettings) 76 | }) 77 | 78 | AfterEach(func() { 79 | ctrl.Finish() 80 | }) 81 | 82 | Describe("Provision", func() { 83 | var ( 84 | provisionParams = minibroker.NewProvisionParams(map[string]interface{}{ 85 | "key": "value", 86 | }) 87 | provisionRequest = &osb.ProvisionRequest{ 88 | ServiceID: "redis", 89 | Parameters: provisionParams.Object, 90 | } 91 | requestContext = &osbbroker.RequestContext{} 92 | ) 93 | 94 | Context("without default chart values", func() { 95 | It("passes on unaltered provision params", func() { 96 | mbclient.EXPECT(). 97 | Provision(gomock.Any(), gomock.Eq("redis"), gomock.Any(), gomock.Eq(namespace), gomock.Any(), gomock.Eq(provisionParams)) 98 | 99 | b.Provision(provisionRequest, requestContext) 100 | }) 101 | }) 102 | 103 | Context("with default chart values", func() { 104 | BeforeEach(func() { 105 | provisioningSettings = &broker.ProvisioningSettings{} 106 | err := provisioningSettings.LoadYaml([]byte(overrideParamsYaml)) 107 | Expect(err).ToNot(HaveOccurred()) 108 | }) 109 | 110 | It("passes on default chart values", func() { 111 | services := []string{"mariadb", "mongodb", "mysql", "postgresql", "rabbitmq", "redis"} 112 | 113 | for _, service := range services { 114 | provisionRequest.ServiceID = service 115 | provisioningSettings, found := provisioningSettings.ForService(service) 116 | Expect(found).To(BeTrue()) 117 | params := minibroker.NewProvisionParams(provisioningSettings.OverrideParams) 118 | 119 | mbclient.EXPECT(). 120 | Provision(gomock.Any(), gomock.Eq(service), gomock.Any(), gomock.Eq(namespace), gomock.Any(), gomock.Eq(params)) 121 | 122 | b.Provision(provisionRequest, requestContext) 123 | } 124 | }) 125 | }) 126 | }) 127 | }) 128 | 129 | var _ = Describe("OverrideChartParams", func() { 130 | Describe("LoadYaml", func() { 131 | var ( 132 | ocp = &broker.ProvisioningSettings{} 133 | ) 134 | 135 | It("Loads valid data", func() { 136 | yamlStr, _ := yaml.Marshal(map[string]interface{}{ 137 | "rabbitmq": map[string]interface{}{ 138 | "overrideParams": map[string]interface{}{ 139 | "rabbitmqdata": "thevalue", 140 | }, 141 | }, 142 | }) 143 | 144 | err := ocp.LoadYaml(yamlStr) 145 | 146 | Expect(err).ToNot(HaveOccurred()) 147 | p, _ := ocp.ForService("rabbitmq") 148 | Expect(p.OverrideParams["rabbitmqdata"]).To(Equal("thevalue")) 149 | }) 150 | 151 | It("returns an error on unknown fields", func() { 152 | yamlStr, _ := yaml.Marshal(map[string]interface{}{ 153 | "unknownservice": map[string]interface{}{ 154 | "overrideParams": map[string]interface{}{ 155 | "key": "value", 156 | }, 157 | }, 158 | }) 159 | 160 | err := ocp.LoadYaml(yamlStr) 161 | 162 | Expect(err).To(HaveOccurred()) 163 | }) 164 | }) 165 | }) 166 | -------------------------------------------------------------------------------- /pkg/broker/mocks/mock_broker.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/kubernetes-sigs/minibroker/pkg/broker (interfaces: MinibrokerClient) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | minibroker "github.com/kubernetes-sigs/minibroker/pkg/minibroker" 12 | v2 "github.com/pmorie/go-open-service-broker-client/v2" 13 | ) 14 | 15 | // MockMinibrokerClient is a mock of MinibrokerClient interface. 16 | type MockMinibrokerClient struct { 17 | ctrl *gomock.Controller 18 | recorder *MockMinibrokerClientMockRecorder 19 | } 20 | 21 | // MockMinibrokerClientMockRecorder is the mock recorder for MockMinibrokerClient. 22 | type MockMinibrokerClientMockRecorder struct { 23 | mock *MockMinibrokerClient 24 | } 25 | 26 | // NewMockMinibrokerClient creates a new mock instance. 27 | func NewMockMinibrokerClient(ctrl *gomock.Controller) *MockMinibrokerClient { 28 | mock := &MockMinibrokerClient{ctrl: ctrl} 29 | mock.recorder = &MockMinibrokerClientMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockMinibrokerClient) EXPECT() *MockMinibrokerClientMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // Bind mocks base method. 39 | func (m *MockMinibrokerClient) Bind(arg0, arg1, arg2 string, arg3 bool, arg4 *minibroker.BindParams) (string, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "Bind", arg0, arg1, arg2, arg3, arg4) 42 | ret0, _ := ret[0].(string) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // Bind indicates an expected call of Bind. 48 | func (mr *MockMinibrokerClientMockRecorder) Bind(arg0, arg1, arg2, arg3, arg4 interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bind", reflect.TypeOf((*MockMinibrokerClient)(nil).Bind), arg0, arg1, arg2, arg3, arg4) 51 | } 52 | 53 | // Deprovision mocks base method. 54 | func (m *MockMinibrokerClient) Deprovision(arg0 string, arg1 bool) (string, error) { 55 | m.ctrl.T.Helper() 56 | ret := m.ctrl.Call(m, "Deprovision", arg0, arg1) 57 | ret0, _ := ret[0].(string) 58 | ret1, _ := ret[1].(error) 59 | return ret0, ret1 60 | } 61 | 62 | // Deprovision indicates an expected call of Deprovision. 63 | func (mr *MockMinibrokerClientMockRecorder) Deprovision(arg0, arg1 interface{}) *gomock.Call { 64 | mr.mock.ctrl.T.Helper() 65 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Deprovision", reflect.TypeOf((*MockMinibrokerClient)(nil).Deprovision), arg0, arg1) 66 | } 67 | 68 | // GetBinding mocks base method. 69 | func (m *MockMinibrokerClient) GetBinding(arg0, arg1 string) (*v2.GetBindingResponse, error) { 70 | m.ctrl.T.Helper() 71 | ret := m.ctrl.Call(m, "GetBinding", arg0, arg1) 72 | ret0, _ := ret[0].(*v2.GetBindingResponse) 73 | ret1, _ := ret[1].(error) 74 | return ret0, ret1 75 | } 76 | 77 | // GetBinding indicates an expected call of GetBinding. 78 | func (mr *MockMinibrokerClientMockRecorder) GetBinding(arg0, arg1 interface{}) *gomock.Call { 79 | mr.mock.ctrl.T.Helper() 80 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBinding", reflect.TypeOf((*MockMinibrokerClient)(nil).GetBinding), arg0, arg1) 81 | } 82 | 83 | // Init mocks base method. 84 | func (m *MockMinibrokerClient) Init(arg0 string) error { 85 | m.ctrl.T.Helper() 86 | ret := m.ctrl.Call(m, "Init", arg0) 87 | ret0, _ := ret[0].(error) 88 | return ret0 89 | } 90 | 91 | // Init indicates an expected call of Init. 92 | func (mr *MockMinibrokerClientMockRecorder) Init(arg0 interface{}) *gomock.Call { 93 | mr.mock.ctrl.T.Helper() 94 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Init", reflect.TypeOf((*MockMinibrokerClient)(nil).Init), arg0) 95 | } 96 | 97 | // LastBindingOperationState mocks base method. 98 | func (m *MockMinibrokerClient) LastBindingOperationState(arg0, arg1 string) (*v2.LastOperationResponse, error) { 99 | m.ctrl.T.Helper() 100 | ret := m.ctrl.Call(m, "LastBindingOperationState", arg0, arg1) 101 | ret0, _ := ret[0].(*v2.LastOperationResponse) 102 | ret1, _ := ret[1].(error) 103 | return ret0, ret1 104 | } 105 | 106 | // LastBindingOperationState indicates an expected call of LastBindingOperationState. 107 | func (mr *MockMinibrokerClientMockRecorder) LastBindingOperationState(arg0, arg1 interface{}) *gomock.Call { 108 | mr.mock.ctrl.T.Helper() 109 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastBindingOperationState", reflect.TypeOf((*MockMinibrokerClient)(nil).LastBindingOperationState), arg0, arg1) 110 | } 111 | 112 | // LastOperationState mocks base method. 113 | func (m *MockMinibrokerClient) LastOperationState(arg0 string, arg1 *v2.OperationKey) (*v2.LastOperationResponse, error) { 114 | m.ctrl.T.Helper() 115 | ret := m.ctrl.Call(m, "LastOperationState", arg0, arg1) 116 | ret0, _ := ret[0].(*v2.LastOperationResponse) 117 | ret1, _ := ret[1].(error) 118 | return ret0, ret1 119 | } 120 | 121 | // LastOperationState indicates an expected call of LastOperationState. 122 | func (mr *MockMinibrokerClientMockRecorder) LastOperationState(arg0, arg1 interface{}) *gomock.Call { 123 | mr.mock.ctrl.T.Helper() 124 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LastOperationState", reflect.TypeOf((*MockMinibrokerClient)(nil).LastOperationState), arg0, arg1) 125 | } 126 | 127 | // ListServices mocks base method. 128 | func (m *MockMinibrokerClient) ListServices() ([]v2.Service, error) { 129 | m.ctrl.T.Helper() 130 | ret := m.ctrl.Call(m, "ListServices") 131 | ret0, _ := ret[0].([]v2.Service) 132 | ret1, _ := ret[1].(error) 133 | return ret0, ret1 134 | } 135 | 136 | // ListServices indicates an expected call of ListServices. 137 | func (mr *MockMinibrokerClientMockRecorder) ListServices() *gomock.Call { 138 | mr.mock.ctrl.T.Helper() 139 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListServices", reflect.TypeOf((*MockMinibrokerClient)(nil).ListServices)) 140 | } 141 | 142 | // Provision mocks base method. 143 | func (m *MockMinibrokerClient) Provision(arg0, arg1, arg2, arg3 string, arg4 bool, arg5 *minibroker.ProvisionParams) (string, error) { 144 | m.ctrl.T.Helper() 145 | ret := m.ctrl.Call(m, "Provision", arg0, arg1, arg2, arg3, arg4, arg5) 146 | ret0, _ := ret[0].(string) 147 | ret1, _ := ret[1].(error) 148 | return ret0, ret1 149 | } 150 | 151 | // Provision indicates an expected call of Provision. 152 | func (mr *MockMinibrokerClientMockRecorder) Provision(arg0, arg1, arg2, arg3, arg4, arg5 interface{}) *gomock.Call { 153 | mr.mock.ctrl.T.Helper() 154 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Provision", reflect.TypeOf((*MockMinibrokerClient)(nil).Provision), arg0, arg1, arg2, arg3, arg4, arg5) 155 | } 156 | 157 | // Unbind mocks base method. 158 | func (m *MockMinibrokerClient) Unbind(arg0, arg1 string) error { 159 | m.ctrl.T.Helper() 160 | ret := m.ctrl.Call(m, "Unbind", arg0, arg1) 161 | ret0, _ := ret[0].(error) 162 | return ret0 163 | } 164 | 165 | // Unbind indicates an expected call of Unbind. 166 | func (mr *MockMinibrokerClientMockRecorder) Unbind(arg0, arg1 interface{}) *gomock.Call { 167 | mr.mock.ctrl.T.Helper() 168 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Unbind", reflect.TypeOf((*MockMinibrokerClient)(nil).Unbind), arg0, arg1) 169 | } 170 | -------------------------------------------------------------------------------- /pkg/broker/options.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package broker 18 | 19 | type Options struct { 20 | HelmRepoURL string 21 | CatalogPath string 22 | // The namespace where Minibroker stores configmaps. 23 | ConfigNamespace string 24 | // The default namespace wheer Minibroker deploys service instances. 25 | DefaultNamespace string 26 | ServiceCatalogEnabledOnly bool 27 | // The YAML file where the optional default chart values are stored. 28 | ProvisioningSettingsPath string 29 | // The k8s cluster domain. If not set via the CLI flags, Minibroker tries to 30 | // infer from the /etc/resolv.conf. 31 | ClusterDomain string 32 | } 33 | -------------------------------------------------------------------------------- /pkg/helm/chart.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "net/http" 23 | 24 | "helm.sh/helm/v3/pkg/action" 25 | "helm.sh/helm/v3/pkg/chart" 26 | "helm.sh/helm/v3/pkg/chart/loader" 27 | "helm.sh/helm/v3/pkg/release" 28 | "helm.sh/helm/v3/pkg/repo" 29 | 30 | "github.com/kubernetes-sigs/minibroker/pkg/log" 31 | "github.com/kubernetes-sigs/minibroker/pkg/nameutil" 32 | ) 33 | 34 | // ChartClient allows users of this client to install and uninstall charts. 35 | type ChartClient struct { 36 | log log.Verboser 37 | chartLoader ChartLoader 38 | nameGenerator nameutil.Generator 39 | ChartHelmClientProvider ChartHelmClientProvider 40 | } 41 | 42 | // NewDefaultChartClient creates a new ChartClient with the default dependencies. 43 | func NewDefaultChartClient() *ChartClient { 44 | return NewChartClient( 45 | log.NewKlog(), 46 | NewDefaultChartManager(), 47 | nameutil.NewDefaultNameGenerator(), 48 | NewDefaultChartHelm(), 49 | ) 50 | } 51 | 52 | // NewChartClient creates a new ChartClient with the explicit dependencies. 53 | func NewChartClient( 54 | log log.Verboser, 55 | chartLoader ChartLoader, 56 | nameGenerator nameutil.Generator, 57 | ChartHelmClientProvider ChartHelmClientProvider, 58 | ) *ChartClient { 59 | return &ChartClient{ 60 | log: log, 61 | chartLoader: chartLoader, 62 | nameGenerator: nameGenerator, 63 | ChartHelmClientProvider: ChartHelmClientProvider, 64 | } 65 | } 66 | 67 | // Install installs a chart version into a specific namespace using the provided values. 68 | func (cc *ChartClient) Install( 69 | chartDef *repo.ChartVersion, 70 | namespace string, 71 | values map[string]interface{}, 72 | ) (*release.Release, error) { 73 | if len(chartDef.URLs) == 0 { 74 | err := fmt.Errorf("missing chart URL for %q", chartDef.Name) 75 | return nil, fmt.Errorf("failed to install chart: %v", err) 76 | } 77 | // TODO(f0rmiga): deal with multiple chart URLs. 78 | chartURL := chartDef.URLs[0] 79 | 80 | chartRequested, err := cc.chartLoader.Load(chartURL) 81 | if err != nil { 82 | return nil, fmt.Errorf("failed to install chart: %v", err) 83 | } 84 | 85 | if chartRequested.Metadata.Deprecated { 86 | cc.log.V(3).Log("minibroker: WARNING: the chart %s:%s is deprecated", chartDef.Name, chartDef.Version) 87 | } 88 | 89 | // TODO(f0rmiga): ensure chart dependencies are fetched. 90 | 91 | releaseName, err := cc.nameGenerator.Generate(fmt.Sprintf("%s-", chartDef.Name)) 92 | if err != nil { 93 | return nil, fmt.Errorf("failed to install chart: %v", err) 94 | } 95 | 96 | if len(releaseName) > helmMaxNameLength { 97 | err := fmt.Errorf( 98 | "invalid release name %q: names cannot exceed %d characters", 99 | releaseName, 100 | helmMaxNameLength) 101 | return nil, fmt.Errorf("failed to install chart: %v", err) 102 | } 103 | 104 | installer, err := cc.ChartHelmClientProvider.ProvideInstaller(releaseName, namespace) 105 | if err != nil { 106 | return nil, fmt.Errorf("failed to install chart: %v", err) 107 | } 108 | 109 | rls, err := installer(chartRequested, values) 110 | if err != nil { 111 | return nil, fmt.Errorf("failed to install chart: %v", err) 112 | } 113 | 114 | return rls, nil 115 | } 116 | 117 | // Uninstall uninstalls a release from a namespace. 118 | func (cc *ChartClient) Uninstall(releaseName, namespace string) error { 119 | uninstaller, err := cc.ChartHelmClientProvider.ProvideUninstaller(namespace) 120 | if err != nil { 121 | return fmt.Errorf("failed to uninstall chart: %v", err) 122 | } 123 | 124 | if _, err := uninstaller(releaseName); err != nil { 125 | return fmt.Errorf("failed to uninstall chart: %v", err) 126 | } 127 | 128 | return nil 129 | } 130 | 131 | // ChartLoader is the interface that wraps the Load method. 132 | type ChartLoader interface { 133 | Load(chartURL string) (*chart.Chart, error) 134 | } 135 | 136 | // ChartManager satisfies the ChartLoader interface. 137 | type ChartManager struct { 138 | httpGetter HTTPGetter 139 | loadChartArchive func(io.Reader) (*chart.Chart, error) 140 | } 141 | 142 | // NewDefaultChartManager creates a new ChartManager with the default dependencies. 143 | func NewDefaultChartManager() *ChartManager { 144 | return NewChartManager( 145 | http.DefaultClient, 146 | loader.LoadArchive, 147 | ) 148 | } 149 | 150 | // NewChartManager creates a new ChartManager with the explicit dependencies. 151 | func NewChartManager( 152 | httpGetter HTTPGetter, 153 | loadChartArchive func(io.Reader) (*chart.Chart, error), 154 | ) *ChartManager { 155 | return &ChartManager{ 156 | httpGetter: httpGetter, 157 | loadChartArchive: loadChartArchive, 158 | } 159 | } 160 | 161 | // Load loads a chart from a URL. 162 | // TODO(f0rmiga): implement caching for chart archives. 163 | func (cm *ChartManager) Load(chartURL string) (*chart.Chart, error) { 164 | chartResp, err := cm.httpGetter.Get(chartURL) 165 | if err != nil { 166 | return nil, fmt.Errorf("failed to load chart: %v", err) 167 | } 168 | defer chartResp.Body.Close() 169 | 170 | chartRequested, err := cm.loadChartArchive(chartResp.Body) 171 | if err != nil { 172 | return nil, fmt.Errorf("failed to load chart: %v", err) 173 | } 174 | 175 | return chartRequested, nil 176 | } 177 | 178 | // ChartHelmClientProvider is the interface that wraps the methods for providing Helm action clients 179 | // for installing and uninstalling charts. 180 | type ChartHelmClientProvider interface { 181 | ProvideInstaller(releaseName, namespace string) (ChartInstallRunner, error) 182 | ProvideUninstaller(namespace string) (ChartUninstallRunner, error) 183 | } 184 | 185 | // ChartHelm satisfies the ChartHelmClientProvider interface. 186 | type ChartHelm struct { 187 | configProvider ConfigProvider 188 | actionNewInstall func(*action.Configuration) *action.Install 189 | actionNewUninstall func(*action.Configuration) *action.Uninstall 190 | } 191 | 192 | // NewDefaultChartHelm creates a new ChartHelm with the default dependencies. 193 | func NewDefaultChartHelm() *ChartHelm { 194 | return NewChartHelm( 195 | NewDefaultConfigProvider(), 196 | action.NewInstall, 197 | action.NewUninstall, 198 | ) 199 | } 200 | 201 | // NewChartHelm creates a new ChartHelm with the explicit dependencies. 202 | func NewChartHelm( 203 | configProvider ConfigProvider, 204 | actionNewInstall func(*action.Configuration) *action.Install, 205 | actionNewUninstall func(*action.Configuration) *action.Uninstall, 206 | ) *ChartHelm { 207 | return &ChartHelm{ 208 | configProvider: configProvider, 209 | actionNewInstall: actionNewInstall, 210 | actionNewUninstall: actionNewUninstall, 211 | } 212 | } 213 | 214 | // ProvideInstaller provides a Helm action client for installing charts. 215 | func (ch *ChartHelm) ProvideInstaller( 216 | releaseName string, 217 | namespace string, 218 | ) (ChartInstallRunner, error) { 219 | cfg, err := ch.configProvider(namespace) 220 | if err != nil { 221 | return nil, fmt.Errorf("failed to provide chart installer: %v", err) 222 | } 223 | client := ch.actionNewInstall(cfg) 224 | client.ReleaseName = releaseName 225 | client.Namespace = namespace 226 | client.Wait = true 227 | return client.Run, nil 228 | } 229 | 230 | // ProvideUninstaller provides a Helm action client for uninstalling charts. 231 | func (ch *ChartHelm) ProvideUninstaller(namespace string) (ChartUninstallRunner, error) { 232 | cfg, err := ch.configProvider(namespace) 233 | if err != nil { 234 | return nil, fmt.Errorf("failed to provide chart uninstaller: %v", err) 235 | } 236 | client := ch.actionNewUninstall(cfg) 237 | return client.Run, nil 238 | } 239 | 240 | // ChartInstallRunner defines the signature for a function that installs a chart. 241 | type ChartInstallRunner func(*chart.Chart, map[string]interface{}) (*release.Release, error) 242 | 243 | // ChartUninstallRunner defines the signature for a function that uninstalls a chart. 244 | type ChartUninstallRunner func(string) (*release.UninstallReleaseResponse, error) 245 | -------------------------------------------------------------------------------- /pkg/helm/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm 18 | 19 | import ( 20 | "fmt" 21 | 22 | "helm.sh/helm/v3/pkg/action" 23 | "helm.sh/helm/v3/pkg/kube" 24 | "k8s.io/cli-runtime/pkg/genericclioptions" 25 | 26 | "github.com/kubernetes-sigs/minibroker/pkg/log" 27 | ) 28 | 29 | const ( 30 | driver = "secret" 31 | 32 | // Empty values for these mean the internal defaults will be used. 33 | defaultKubeConfig = "" 34 | defaultContext = "" 35 | ) 36 | 37 | // ConfigProvider defines the signature for a function that provides *action.Configuration. 38 | type ConfigProvider func(namespace string) (*action.Configuration, error) 39 | 40 | // NewDefaultConfigProvider creates a new ConfigProvider using the default dependencies. 41 | func NewDefaultConfigProvider() ConfigProvider { 42 | return NewConfigProvider( 43 | log.NewKlog(), 44 | DefaultConfigInitializerProvider, 45 | defaultKubeConfig, 46 | defaultContext, 47 | ) 48 | } 49 | 50 | // NewConfigProvider creates a new ConfigProvider using the explicit dependencies. 51 | func NewConfigProvider( 52 | log log.Verboser, 53 | configInitializerProvider ConfigInitializerProvider, 54 | kubeConfig string, 55 | context string, 56 | ) ConfigProvider { 57 | return func(namespace string) (*action.Configuration, error) { 58 | restGetter := kube.GetConfig(kubeConfig, context, namespace) 59 | debug := func(format string, v ...interface{}) { 60 | log.V(4).Log("helm client: "+format, v...) 61 | } 62 | cfg, init := configInitializerProvider() 63 | if err := init(restGetter, namespace, driver, debug); err != nil { 64 | return nil, fmt.Errorf("failed to provide action configuration: %v", err) 65 | } 66 | return cfg, nil 67 | } 68 | } 69 | 70 | // ConfigInitializer is the function signature of the action.Configuration.Init 71 | // method to avoid a hidden dependency call in the Config.Provide method. 72 | type ConfigInitializer func( 73 | getter genericclioptions.RESTClientGetter, 74 | namespace string, 75 | helmDriver string, 76 | log action.DebugLog, 77 | ) error 78 | 79 | // ConfigInitializerProvider defines the signature for a function that provides an 80 | // *action.Configuration and its Init method. 81 | type ConfigInitializerProvider func() (*action.Configuration, ConfigInitializer) 82 | 83 | // DefaultConfigInitializerProvider is the default implementation for ConfigInitializerProvider. 84 | func DefaultConfigInitializerProvider() (*action.Configuration, ConfigInitializer) { 85 | cfg := &action.Configuration{} 86 | return cfg, cfg.Init 87 | } 88 | -------------------------------------------------------------------------------- /pkg/helm/config_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm_test 18 | 19 | import ( 20 | "fmt" 21 | "reflect" 22 | 23 | "github.com/golang/mock/gomock" 24 | . "github.com/onsi/ginkgo" 25 | . "github.com/onsi/gomega" 26 | "k8s.io/cli-runtime/pkg/genericclioptions" 27 | 28 | "helm.sh/helm/v3/pkg/action" 29 | 30 | "github.com/kubernetes-sigs/minibroker/pkg/helm" 31 | "github.com/kubernetes-sigs/minibroker/pkg/helm/mocks" 32 | "github.com/kubernetes-sigs/minibroker/pkg/log" 33 | ) 34 | 35 | //go:generate mockgen -destination=./mocks/mock_config.go -package=mocks github.com/kubernetes-sigs/minibroker/pkg/helm/testutil ConfigProvider,ConfigInitializer,ConfigInitializerProvider 36 | 37 | var _ = Describe("Config", func() { 38 | Describe("Config", func() { 39 | Describe("NewDefaultConfigProvider", func() { 40 | It("should return a new ConfigProvider", func() { 41 | var config helm.ConfigProvider = helm.NewDefaultConfigProvider() 42 | Expect(config).NotTo(BeNil()) 43 | }) 44 | }) 45 | 46 | Describe("ConfigProvider", func() { 47 | var ctrl *gomock.Controller 48 | 49 | BeforeEach(func() { 50 | ctrl = gomock.NewController(GinkgoT()) 51 | }) 52 | 53 | AfterEach(func() { 54 | ctrl.Finish() 55 | }) 56 | 57 | It("should fail when ConfigInitializer fails", func() { 58 | namespace := "my-namespace" 59 | configInitializer := mocks.NewMockConfigInitializer(ctrl) 60 | configInitializer.EXPECT(). 61 | ConfigInitializer(gomock.Any(), namespace, gomock.Any(), gomock.Any()). 62 | Return(fmt.Errorf("some error")). 63 | Times(1) 64 | configInitializerProvider := mocks.NewMockConfigInitializerProvider(ctrl) 65 | configInitializerProvider.EXPECT(). 66 | ConfigInitializerProvider(). 67 | Return(nil, configInitializer.ConfigInitializer). 68 | Times(1) 69 | 70 | configProvider := helm.NewConfigProvider( 71 | log.NewNoop(), 72 | configInitializerProvider.ConfigInitializerProvider, 73 | "", 74 | "", 75 | ) 76 | cfg, err := configProvider(namespace) 77 | Expect(err).To(Equal(fmt.Errorf("failed to provide action configuration: some error"))) 78 | Expect(cfg).To(BeNil()) 79 | }) 80 | 81 | It("should succeed", func() { 82 | namespace := "my-namespace" 83 | actionConfig := &action.Configuration{} 84 | configInitializer := mocks.NewMockConfigInitializer(ctrl) 85 | configInitializer.EXPECT(). 86 | ConfigInitializer(gomock.Any(), namespace, gomock.Any(), gomock.Any()). 87 | Do(func( 88 | _ genericclioptions.RESTClientGetter, 89 | _ string, 90 | _ string, 91 | log action.DebugLog, 92 | ) error { 93 | log("whatever") 94 | return nil 95 | }). 96 | Times(1) 97 | configInitializerProvider := mocks.NewMockConfigInitializerProvider(ctrl) 98 | configInitializerProvider.EXPECT(). 99 | ConfigInitializerProvider(). 100 | Return(actionConfig, configInitializer.ConfigInitializer). 101 | Times(1) 102 | 103 | configProvider := helm.NewConfigProvider( 104 | log.NewNoop(), 105 | configInitializerProvider.ConfigInitializerProvider, 106 | "", 107 | "", 108 | ) 109 | cfg, err := configProvider(namespace) 110 | Expect(err).NotTo(HaveOccurred()) 111 | Expect(cfg).To(Equal(actionConfig)) 112 | }) 113 | }) 114 | }) 115 | 116 | Describe("DefaultConfigInitializerProvider", func() { 117 | It("should return a new action.Configuration and its Init method", func() { 118 | config, initializer := helm.DefaultConfigInitializerProvider() 119 | Expect(config).NotTo(BeNil()) 120 | Expect( 121 | reflect.ValueOf(initializer).Pointer(), 122 | ).To(Equal( 123 | reflect.ValueOf(config.Init).Pointer(), 124 | )) 125 | }) 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /pkg/helm/helm.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm 18 | 19 | import ( 20 | "fmt" 21 | 22 | "helm.sh/helm/v3/pkg/cli" 23 | "helm.sh/helm/v3/pkg/getter" 24 | "helm.sh/helm/v3/pkg/helmpath" 25 | "helm.sh/helm/v3/pkg/repo" 26 | 27 | "github.com/kubernetes-sigs/minibroker/pkg/log" 28 | ) 29 | 30 | const ( 31 | stableURL = "https://charts.helm.sh/stable" 32 | // As old versions of Kubernetes had a limit on names of 63 characters, Helm uses 53, reserving 33 | // 10 characters for charts to add data. 34 | helmMaxNameLength = 53 35 | ) 36 | 37 | // Client represents a Helm client to interact with the k8s cluster. 38 | type Client struct { 39 | log log.Verboser 40 | repositoryClient RepositoryInitializeDownloadLoader 41 | chartClient *ChartClient 42 | 43 | settings *cli.EnvSettings 44 | chartRepo *repo.ChartRepository 45 | } 46 | 47 | // NewDefaultClient creates a new Client with the default dependencies. 48 | func NewDefaultClient() *Client { 49 | return NewClient( 50 | log.NewKlog(), 51 | NewDefaultRepositoryClient(), 52 | NewDefaultChartClient(), 53 | ) 54 | } 55 | 56 | // NewClient creates a new client with explicit dependencies. 57 | func NewClient( 58 | log log.Verboser, 59 | repositoryClient RepositoryInitializeDownloadLoader, 60 | chartClient *ChartClient, 61 | ) *Client { 62 | settings := &cli.EnvSettings{ 63 | RegistryConfig: helmpath.ConfigPath("registry.json"), 64 | RepositoryConfig: helmpath.ConfigPath("repositories.yaml"), 65 | RepositoryCache: helmpath.CachePath("repository"), 66 | } 67 | return &Client{ 68 | log: log, 69 | repositoryClient: repositoryClient, 70 | chartClient: chartClient, 71 | settings: settings, 72 | } 73 | } 74 | 75 | // Initialize initializes a chart repository. 76 | // TODO(f0rmiga): be able to handle multiple repositories. How can we handle charts with the same 77 | // name across repositories? 78 | // TODO(f0rmiga): add a readiness probe for this initialization process. A health endpoint would be 79 | // enough. 80 | func (c *Client) Initialize(repoURL string) error { 81 | c.log.V(3).Log("helm client: initializing") 82 | 83 | // TODO(f0rmiga): Allow private repos with authentication. Entry will need to contain the auth 84 | // configuration. 85 | chartCfg := repo.Entry{ 86 | Name: "stable", 87 | URL: repoURL, 88 | } 89 | if chartCfg.URL == "" { 90 | chartCfg.URL = stableURL 91 | } 92 | chartRepo, err := c.repositoryClient.Initialize(&chartCfg, getter.All(c.settings)) 93 | if err != nil { 94 | return fmt.Errorf("failed to initialize helm client: %v", err) 95 | } 96 | 97 | c.log.V(3).Log("helm client: downloading index file") 98 | indexPath, err := c.repositoryClient.DownloadIndex(chartRepo) 99 | if err != nil { 100 | return fmt.Errorf("failed to initialize helm client: %v", err) 101 | } 102 | 103 | c.log.V(3).Log("helm client: loading repository") 104 | indexFile, err := c.repositoryClient.Load(indexPath) 105 | if err != nil { 106 | return fmt.Errorf("failed to initialize helm client: %v", err) 107 | } 108 | 109 | chartRepo.IndexFile = indexFile 110 | c.chartRepo = chartRepo 111 | 112 | c.log.V(3).Log("helm client: successfully initialized") 113 | 114 | return nil 115 | } 116 | 117 | // ListCharts lists the charts from the chart repository. 118 | func (c *Client) ListCharts() map[string]repo.ChartVersions { 119 | c.log.V(4).Log("helm client: listing charts from %s", c.chartRepo.Config.URL) 120 | defer c.log.V(4).Log("helm client: listed charts from %s", c.chartRepo.Config.URL) 121 | return c.chartRepo.IndexFile.Entries 122 | } 123 | 124 | // GetChart gets a chart that exists in the chart repository. IndexFile.Get() cannot be used here 125 | // since we filter by app version. 126 | func (c *Client) GetChart(name, appVersion string) (*repo.ChartVersion, error) { 127 | c.log.V(4).Log("helm client: getting chart %s:%s", name, appVersion) 128 | 129 | charts := c.ListCharts() 130 | 131 | versions, ok := charts[name] 132 | if !ok { 133 | err := fmt.Errorf("chart not found: %s", name) 134 | c.log.V(4).Log("helm client: %v", err) 135 | return nil, fmt.Errorf("failed to get chart: %v", err) 136 | } 137 | 138 | for _, v := range versions { 139 | if v.AppVersion == appVersion { 140 | c.log.V(4).Log("helm client: got chart %s:%s", name, appVersion) 141 | return v, nil 142 | } 143 | } 144 | 145 | err := fmt.Errorf("chart app version not found for %q: %s", name, appVersion) 146 | c.log.V(4).Log("helm client: %v", err) 147 | return nil, fmt.Errorf("failed to get chart: %v", err) 148 | } 149 | 150 | // ChartClient returns the chart client for installing and uninstalling a chart. 151 | func (c *Client) ChartClient() *ChartClient { 152 | return c.chartClient 153 | } 154 | -------------------------------------------------------------------------------- /pkg/helm/helm_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm_test 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestHelm(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Helm Suite") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/helm/helm_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm_test 18 | 19 | import ( 20 | "fmt" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | "github.com/golang/mock/gomock" 26 | "helm.sh/helm/v3/pkg/chart" 27 | "helm.sh/helm/v3/pkg/repo" 28 | 29 | "github.com/kubernetes-sigs/minibroker/pkg/helm" 30 | "github.com/kubernetes-sigs/minibroker/pkg/helm/mocks" 31 | "github.com/kubernetes-sigs/minibroker/pkg/log" 32 | ) 33 | 34 | var _ = Describe("Helm", func() { 35 | Context("Client", func() { 36 | var ctrl *gomock.Controller 37 | 38 | BeforeEach(func() { 39 | ctrl = gomock.NewController(GinkgoT()) 40 | }) 41 | 42 | AfterEach(func() { 43 | ctrl.Finish() 44 | }) 45 | 46 | Describe("NewDefaultClient", func() { 47 | It("should create a new Client", func() { 48 | client := helm.NewDefaultClient() 49 | Expect(client).NotTo(BeNil()) 50 | }) 51 | }) 52 | 53 | Describe("Initialize", func() { 54 | It("should fail when repoInitializer.Initialize fails", func() { 55 | repoClient := mocks.NewMockRepositoryInitializeDownloadLoader(ctrl) 56 | repoClient.EXPECT(). 57 | Initialize(gomock.Any(), gomock.Any()). 58 | Return(nil, fmt.Errorf("amazing repoInitializer failure")). 59 | Times(1) 60 | 61 | client := helm.NewClient( 62 | log.NewNoop(), 63 | repoClient, 64 | nil, 65 | ) 66 | err := client.Initialize("") 67 | Expect(err).To(Equal(fmt.Errorf("failed to initialize helm client: amazing repoInitializer failure"))) 68 | }) 69 | 70 | It("should fail when repoDownloader.DownloadIndex fails", func() { 71 | repoClient := mocks.NewMockRepositoryInitializeDownloadLoader(ctrl) 72 | chartRepo := &repo.ChartRepository{} 73 | repoClient.EXPECT(). 74 | Initialize(gomock.Any(), gomock.Any()). 75 | Return(chartRepo, nil). 76 | Times(1) 77 | repoClient.EXPECT(). 78 | DownloadIndex(chartRepo). 79 | Return("", fmt.Errorf("awesome repoDownloader error")). 80 | Times(1) 81 | 82 | client := helm.NewClient( 83 | log.NewNoop(), 84 | repoClient, 85 | nil, 86 | ) 87 | err := client.Initialize("") 88 | Expect(err).To(Equal(fmt.Errorf("failed to initialize helm client: awesome repoDownloader error"))) 89 | }) 90 | 91 | It("should fail when repoLoader.Load fails", func() { 92 | repoClient := mocks.NewMockRepositoryInitializeDownloadLoader(ctrl) 93 | chartRepo := &repo.ChartRepository{} 94 | repoClient.EXPECT(). 95 | Initialize(gomock.Any(), gomock.Any()). 96 | Return(chartRepo, nil). 97 | Times(1) 98 | indexPath := "some_path.yaml" 99 | repoClient.EXPECT(). 100 | DownloadIndex(chartRepo). 101 | Return(indexPath, nil). 102 | Times(1) 103 | repoClient.EXPECT(). 104 | Load(indexPath). 105 | Return(nil, fmt.Errorf("marvelous repoLoader fault")). 106 | Times(1) 107 | 108 | client := helm.NewClient( 109 | log.NewNoop(), 110 | repoClient, 111 | nil, 112 | ) 113 | err := client.Initialize("") 114 | Expect(err).To(Equal(fmt.Errorf("failed to initialize helm client: marvelous repoLoader fault"))) 115 | }) 116 | 117 | It("should succeed", func() { 118 | repoClient := mocks.NewMockRepositoryInitializeDownloadLoader(ctrl) 119 | chartRepo := &repo.ChartRepository{} 120 | repoClient.EXPECT(). 121 | Initialize(gomock.Any(), gomock.Any()). 122 | Return(chartRepo, nil). 123 | Times(1) 124 | indexPath := "some_path.yaml" 125 | repoClient.EXPECT(). 126 | DownloadIndex(chartRepo). 127 | Return(indexPath, nil). 128 | Times(1) 129 | repoClient.EXPECT(). 130 | Load(indexPath). 131 | Return(&repo.IndexFile{}, nil). 132 | Times(1) 133 | 134 | client := helm.NewClient( 135 | log.NewNoop(), 136 | repoClient, 137 | nil, 138 | ) 139 | err := client.Initialize("") 140 | Expect(err).NotTo(HaveOccurred()) 141 | }) 142 | }) 143 | 144 | Describe("ListCharts", func() { 145 | It("should return the chart entries", func() { 146 | expectedCharts := map[string]repo.ChartVersions{ 147 | "foo": make(repo.ChartVersions, 0), 148 | "bar": make(repo.ChartVersions, 0), 149 | } 150 | repoClient := newRepoClient(ctrl, expectedCharts) 151 | client := helm.NewClient(log.NewNoop(), repoClient, nil) 152 | err := client.Initialize("") 153 | Expect(err).NotTo(HaveOccurred()) 154 | 155 | charts := client.ListCharts() 156 | Expect(charts).To(Equal(expectedCharts)) 157 | }) 158 | }) 159 | 160 | Describe("GetChart", func() { 161 | It("should fail when the chart doesn't exist", func() { 162 | charts := map[string]repo.ChartVersions{"foo": make(repo.ChartVersions, 0)} 163 | repoClient := newRepoClient(ctrl, charts) 164 | client := helm.NewClient(log.NewNoop(), repoClient, nil) 165 | err := client.Initialize("") 166 | Expect(err).NotTo(HaveOccurred()) 167 | 168 | chart, err := client.GetChart("bar", "") 169 | Expect(err).To(Equal(fmt.Errorf("failed to get chart: chart not found: bar"))) 170 | Expect(chart).To(BeNil()) 171 | }) 172 | 173 | It("should fail when the chart version doesn't exist", func() { 174 | charts := map[string]repo.ChartVersions{"bar": make(repo.ChartVersions, 0)} 175 | repoClient := newRepoClient(ctrl, charts) 176 | client := helm.NewClient(log.NewNoop(), repoClient, nil) 177 | err := client.Initialize("") 178 | Expect(err).NotTo(HaveOccurred()) 179 | 180 | chart, err := client.GetChart("bar", "1.2.3") 181 | Expect(err).To(Equal(fmt.Errorf("failed to get chart: chart app version not found for \"bar\": 1.2.3"))) 182 | Expect(chart).To(BeNil()) 183 | }) 184 | 185 | It("should succeed returning the requested chart", func() { 186 | chartMetadata := &chart.Metadata{AppVersion: "1.2.3"} 187 | expectedChart := &repo.ChartVersion{Metadata: chartMetadata} 188 | versions := repo.ChartVersions{expectedChart} 189 | charts := map[string]repo.ChartVersions{"bar": versions} 190 | repoClient := newRepoClient(ctrl, charts) 191 | client := helm.NewClient(log.NewNoop(), repoClient, nil) 192 | err := client.Initialize("") 193 | Expect(err).NotTo(HaveOccurred()) 194 | 195 | chart, err := client.GetChart("bar", "1.2.3") 196 | Expect(err).NotTo(HaveOccurred()) 197 | Expect(chart).To(Equal(expectedChart)) 198 | }) 199 | }) 200 | 201 | Describe("ChartClient", func() { 202 | It("should return the expected chart client", func() { 203 | chartClient := helm.NewDefaultChartClient() 204 | client := helm.NewClient(nil, nil, chartClient) 205 | Expect(client.ChartClient()).To(Equal(chartClient)) 206 | }) 207 | }) 208 | }) 209 | }) 210 | 211 | func newRepoClient(ctrl *gomock.Controller, charts map[string]repo.ChartVersions) helm.RepositoryInitializeDownloadLoader { 212 | repoClient := mocks.NewMockRepositoryInitializeDownloadLoader(ctrl) 213 | chartRepo := &repo.ChartRepository{Config: &repo.Entry{URL: "https://repository"}} 214 | indexPath := "some_path.yaml" 215 | indexFile := &repo.IndexFile{Entries: charts} 216 | repoClient.EXPECT(). 217 | Initialize(gomock.Any(), gomock.Any()). 218 | Return(chartRepo, nil). 219 | Times(1) 220 | repoClient.EXPECT(). 221 | DownloadIndex(chartRepo). 222 | Return(indexPath, nil). 223 | Times(1) 224 | repoClient.EXPECT(). 225 | Load(indexPath). 226 | Return(indexFile, nil). 227 | Times(1) 228 | return repoClient 229 | } 230 | -------------------------------------------------------------------------------- /pkg/helm/http.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm 18 | 19 | import "net/http" 20 | 21 | // HTTPGetter is the interface that wraps the Get method to be used with the default library 22 | // http.DefaultClient. 23 | type HTTPGetter interface { 24 | Get(string) (*http.Response, error) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/helm/mocks/mock_chart.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/kubernetes-sigs/minibroker/pkg/helm (interfaces: ChartLoader,ChartHelmClientProvider) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | helm "github.com/kubernetes-sigs/minibroker/pkg/helm" 12 | chart "helm.sh/helm/v3/pkg/chart" 13 | ) 14 | 15 | // MockChartLoader is a mock of ChartLoader interface. 16 | type MockChartLoader struct { 17 | ctrl *gomock.Controller 18 | recorder *MockChartLoaderMockRecorder 19 | } 20 | 21 | // MockChartLoaderMockRecorder is the mock recorder for MockChartLoader. 22 | type MockChartLoaderMockRecorder struct { 23 | mock *MockChartLoader 24 | } 25 | 26 | // NewMockChartLoader creates a new mock instance. 27 | func NewMockChartLoader(ctrl *gomock.Controller) *MockChartLoader { 28 | mock := &MockChartLoader{ctrl: ctrl} 29 | mock.recorder = &MockChartLoaderMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockChartLoader) EXPECT() *MockChartLoaderMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // Load mocks base method. 39 | func (m *MockChartLoader) Load(arg0 string) (*chart.Chart, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "Load", arg0) 42 | ret0, _ := ret[0].(*chart.Chart) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // Load indicates an expected call of Load. 48 | func (mr *MockChartLoaderMockRecorder) Load(arg0 interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Load", reflect.TypeOf((*MockChartLoader)(nil).Load), arg0) 51 | } 52 | 53 | // MockChartHelmClientProvider is a mock of ChartHelmClientProvider interface. 54 | type MockChartHelmClientProvider struct { 55 | ctrl *gomock.Controller 56 | recorder *MockChartHelmClientProviderMockRecorder 57 | } 58 | 59 | // MockChartHelmClientProviderMockRecorder is the mock recorder for MockChartHelmClientProvider. 60 | type MockChartHelmClientProviderMockRecorder struct { 61 | mock *MockChartHelmClientProvider 62 | } 63 | 64 | // NewMockChartHelmClientProvider creates a new mock instance. 65 | func NewMockChartHelmClientProvider(ctrl *gomock.Controller) *MockChartHelmClientProvider { 66 | mock := &MockChartHelmClientProvider{ctrl: ctrl} 67 | mock.recorder = &MockChartHelmClientProviderMockRecorder{mock} 68 | return mock 69 | } 70 | 71 | // EXPECT returns an object that allows the caller to indicate expected use. 72 | func (m *MockChartHelmClientProvider) EXPECT() *MockChartHelmClientProviderMockRecorder { 73 | return m.recorder 74 | } 75 | 76 | // ProvideInstaller mocks base method. 77 | func (m *MockChartHelmClientProvider) ProvideInstaller(arg0, arg1 string) (helm.ChartInstallRunner, error) { 78 | m.ctrl.T.Helper() 79 | ret := m.ctrl.Call(m, "ProvideInstaller", arg0, arg1) 80 | ret0, _ := ret[0].(helm.ChartInstallRunner) 81 | ret1, _ := ret[1].(error) 82 | return ret0, ret1 83 | } 84 | 85 | // ProvideInstaller indicates an expected call of ProvideInstaller. 86 | func (mr *MockChartHelmClientProviderMockRecorder) ProvideInstaller(arg0, arg1 interface{}) *gomock.Call { 87 | mr.mock.ctrl.T.Helper() 88 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideInstaller", reflect.TypeOf((*MockChartHelmClientProvider)(nil).ProvideInstaller), arg0, arg1) 89 | } 90 | 91 | // ProvideUninstaller mocks base method. 92 | func (m *MockChartHelmClientProvider) ProvideUninstaller(arg0 string) (helm.ChartUninstallRunner, error) { 93 | m.ctrl.T.Helper() 94 | ret := m.ctrl.Call(m, "ProvideUninstaller", arg0) 95 | ret0, _ := ret[0].(helm.ChartUninstallRunner) 96 | ret1, _ := ret[1].(error) 97 | return ret0, ret1 98 | } 99 | 100 | // ProvideUninstaller indicates an expected call of ProvideUninstaller. 101 | func (mr *MockChartHelmClientProviderMockRecorder) ProvideUninstaller(arg0 interface{}) *gomock.Call { 102 | mr.mock.ctrl.T.Helper() 103 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProvideUninstaller", reflect.TypeOf((*MockChartHelmClientProvider)(nil).ProvideUninstaller), arg0) 104 | } 105 | -------------------------------------------------------------------------------- /pkg/helm/mocks/mock_config.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/kubernetes-sigs/minibroker/pkg/helm/testutil (interfaces: ConfigProvider,ConfigInitializer,ConfigInitializerProvider) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | helm "github.com/kubernetes-sigs/minibroker/pkg/helm" 12 | action "helm.sh/helm/v3/pkg/action" 13 | genericclioptions "k8s.io/cli-runtime/pkg/genericclioptions" 14 | ) 15 | 16 | // MockConfigProvider is a mock of ConfigProvider interface. 17 | type MockConfigProvider struct { 18 | ctrl *gomock.Controller 19 | recorder *MockConfigProviderMockRecorder 20 | } 21 | 22 | // MockConfigProviderMockRecorder is the mock recorder for MockConfigProvider. 23 | type MockConfigProviderMockRecorder struct { 24 | mock *MockConfigProvider 25 | } 26 | 27 | // NewMockConfigProvider creates a new mock instance. 28 | func NewMockConfigProvider(ctrl *gomock.Controller) *MockConfigProvider { 29 | mock := &MockConfigProvider{ctrl: ctrl} 30 | mock.recorder = &MockConfigProviderMockRecorder{mock} 31 | return mock 32 | } 33 | 34 | // EXPECT returns an object that allows the caller to indicate expected use. 35 | func (m *MockConfigProvider) EXPECT() *MockConfigProviderMockRecorder { 36 | return m.recorder 37 | } 38 | 39 | // ConfigProvider mocks base method. 40 | func (m *MockConfigProvider) ConfigProvider(arg0 string) (*action.Configuration, error) { 41 | m.ctrl.T.Helper() 42 | ret := m.ctrl.Call(m, "ConfigProvider", arg0) 43 | ret0, _ := ret[0].(*action.Configuration) 44 | ret1, _ := ret[1].(error) 45 | return ret0, ret1 46 | } 47 | 48 | // ConfigProvider indicates an expected call of ConfigProvider. 49 | func (mr *MockConfigProviderMockRecorder) ConfigProvider(arg0 interface{}) *gomock.Call { 50 | mr.mock.ctrl.T.Helper() 51 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigProvider", reflect.TypeOf((*MockConfigProvider)(nil).ConfigProvider), arg0) 52 | } 53 | 54 | // MockConfigInitializer is a mock of ConfigInitializer interface. 55 | type MockConfigInitializer struct { 56 | ctrl *gomock.Controller 57 | recorder *MockConfigInitializerMockRecorder 58 | } 59 | 60 | // MockConfigInitializerMockRecorder is the mock recorder for MockConfigInitializer. 61 | type MockConfigInitializerMockRecorder struct { 62 | mock *MockConfigInitializer 63 | } 64 | 65 | // NewMockConfigInitializer creates a new mock instance. 66 | func NewMockConfigInitializer(ctrl *gomock.Controller) *MockConfigInitializer { 67 | mock := &MockConfigInitializer{ctrl: ctrl} 68 | mock.recorder = &MockConfigInitializerMockRecorder{mock} 69 | return mock 70 | } 71 | 72 | // EXPECT returns an object that allows the caller to indicate expected use. 73 | func (m *MockConfigInitializer) EXPECT() *MockConfigInitializerMockRecorder { 74 | return m.recorder 75 | } 76 | 77 | // ConfigInitializer mocks base method. 78 | func (m *MockConfigInitializer) ConfigInitializer(arg0 genericclioptions.RESTClientGetter, arg1, arg2 string, arg3 action.DebugLog) error { 79 | m.ctrl.T.Helper() 80 | ret := m.ctrl.Call(m, "ConfigInitializer", arg0, arg1, arg2, arg3) 81 | ret0, _ := ret[0].(error) 82 | return ret0 83 | } 84 | 85 | // ConfigInitializer indicates an expected call of ConfigInitializer. 86 | func (mr *MockConfigInitializerMockRecorder) ConfigInitializer(arg0, arg1, arg2, arg3 interface{}) *gomock.Call { 87 | mr.mock.ctrl.T.Helper() 88 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigInitializer", reflect.TypeOf((*MockConfigInitializer)(nil).ConfigInitializer), arg0, arg1, arg2, arg3) 89 | } 90 | 91 | // MockConfigInitializerProvider is a mock of ConfigInitializerProvider interface. 92 | type MockConfigInitializerProvider struct { 93 | ctrl *gomock.Controller 94 | recorder *MockConfigInitializerProviderMockRecorder 95 | } 96 | 97 | // MockConfigInitializerProviderMockRecorder is the mock recorder for MockConfigInitializerProvider. 98 | type MockConfigInitializerProviderMockRecorder struct { 99 | mock *MockConfigInitializerProvider 100 | } 101 | 102 | // NewMockConfigInitializerProvider creates a new mock instance. 103 | func NewMockConfigInitializerProvider(ctrl *gomock.Controller) *MockConfigInitializerProvider { 104 | mock := &MockConfigInitializerProvider{ctrl: ctrl} 105 | mock.recorder = &MockConfigInitializerProviderMockRecorder{mock} 106 | return mock 107 | } 108 | 109 | // EXPECT returns an object that allows the caller to indicate expected use. 110 | func (m *MockConfigInitializerProvider) EXPECT() *MockConfigInitializerProviderMockRecorder { 111 | return m.recorder 112 | } 113 | 114 | // ConfigInitializerProvider mocks base method. 115 | func (m *MockConfigInitializerProvider) ConfigInitializerProvider() (*action.Configuration, helm.ConfigInitializer) { 116 | m.ctrl.T.Helper() 117 | ret := m.ctrl.Call(m, "ConfigInitializerProvider") 118 | ret0, _ := ret[0].(*action.Configuration) 119 | ret1, _ := ret[1].(helm.ConfigInitializer) 120 | return ret0, ret1 121 | } 122 | 123 | // ConfigInitializerProvider indicates an expected call of ConfigInitializerProvider. 124 | func (mr *MockConfigInitializerProviderMockRecorder) ConfigInitializerProvider() *gomock.Call { 125 | mr.mock.ctrl.T.Helper() 126 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConfigInitializerProvider", reflect.TypeOf((*MockConfigInitializerProvider)(nil).ConfigInitializerProvider)) 127 | } 128 | -------------------------------------------------------------------------------- /pkg/helm/mocks/mock_http.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/kubernetes-sigs/minibroker/pkg/helm (interfaces: HTTPGetter) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | http "net/http" 9 | reflect "reflect" 10 | 11 | gomock "github.com/golang/mock/gomock" 12 | ) 13 | 14 | // MockHTTPGetter is a mock of HTTPGetter interface. 15 | type MockHTTPGetter struct { 16 | ctrl *gomock.Controller 17 | recorder *MockHTTPGetterMockRecorder 18 | } 19 | 20 | // MockHTTPGetterMockRecorder is the mock recorder for MockHTTPGetter. 21 | type MockHTTPGetterMockRecorder struct { 22 | mock *MockHTTPGetter 23 | } 24 | 25 | // NewMockHTTPGetter creates a new mock instance. 26 | func NewMockHTTPGetter(ctrl *gomock.Controller) *MockHTTPGetter { 27 | mock := &MockHTTPGetter{ctrl: ctrl} 28 | mock.recorder = &MockHTTPGetterMockRecorder{mock} 29 | return mock 30 | } 31 | 32 | // EXPECT returns an object that allows the caller to indicate expected use. 33 | func (m *MockHTTPGetter) EXPECT() *MockHTTPGetterMockRecorder { 34 | return m.recorder 35 | } 36 | 37 | // Get mocks base method. 38 | func (m *MockHTTPGetter) Get(arg0 string) (*http.Response, error) { 39 | m.ctrl.T.Helper() 40 | ret := m.ctrl.Call(m, "Get", arg0) 41 | ret0, _ := ret[0].(*http.Response) 42 | ret1, _ := ret[1].(error) 43 | return ret0, ret1 44 | } 45 | 46 | // Get indicates an expected call of Get. 47 | func (mr *MockHTTPGetterMockRecorder) Get(arg0 interface{}) *gomock.Call { 48 | mr.mock.ctrl.T.Helper() 49 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockHTTPGetter)(nil).Get), arg0) 50 | } 51 | -------------------------------------------------------------------------------- /pkg/helm/mocks/mock_io.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: io (interfaces: ReadCloser) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockReadCloser is a mock of ReadCloser interface. 14 | type MockReadCloser struct { 15 | ctrl *gomock.Controller 16 | recorder *MockReadCloserMockRecorder 17 | } 18 | 19 | // MockReadCloserMockRecorder is the mock recorder for MockReadCloser. 20 | type MockReadCloserMockRecorder struct { 21 | mock *MockReadCloser 22 | } 23 | 24 | // NewMockReadCloser creates a new mock instance. 25 | func NewMockReadCloser(ctrl *gomock.Controller) *MockReadCloser { 26 | mock := &MockReadCloser{ctrl: ctrl} 27 | mock.recorder = &MockReadCloserMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockReadCloser) EXPECT() *MockReadCloserMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Close mocks base method. 37 | func (m *MockReadCloser) Close() error { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Close") 40 | ret0, _ := ret[0].(error) 41 | return ret0 42 | } 43 | 44 | // Close indicates an expected call of Close. 45 | func (mr *MockReadCloserMockRecorder) Close() *gomock.Call { 46 | mr.mock.ctrl.T.Helper() 47 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockReadCloser)(nil).Close)) 48 | } 49 | 50 | // Read mocks base method. 51 | func (m *MockReadCloser) Read(arg0 []byte) (int, error) { 52 | m.ctrl.T.Helper() 53 | ret := m.ctrl.Call(m, "Read", arg0) 54 | ret0, _ := ret[0].(int) 55 | ret1, _ := ret[1].(error) 56 | return ret0, ret1 57 | } 58 | 59 | // Read indicates an expected call of Read. 60 | func (mr *MockReadCloserMockRecorder) Read(arg0 interface{}) *gomock.Call { 61 | mr.mock.ctrl.T.Helper() 62 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReadCloser)(nil).Read), arg0) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/helm/mocks/mock_testutil_chart.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/kubernetes-sigs/minibroker/pkg/helm/testutil (interfaces: ChartInstallRunner,ChartUninstallRunner) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | chart "helm.sh/helm/v3/pkg/chart" 12 | release "helm.sh/helm/v3/pkg/release" 13 | ) 14 | 15 | // MockChartInstallRunner is a mock of ChartInstallRunner interface. 16 | type MockChartInstallRunner struct { 17 | ctrl *gomock.Controller 18 | recorder *MockChartInstallRunnerMockRecorder 19 | } 20 | 21 | // MockChartInstallRunnerMockRecorder is the mock recorder for MockChartInstallRunner. 22 | type MockChartInstallRunnerMockRecorder struct { 23 | mock *MockChartInstallRunner 24 | } 25 | 26 | // NewMockChartInstallRunner creates a new mock instance. 27 | func NewMockChartInstallRunner(ctrl *gomock.Controller) *MockChartInstallRunner { 28 | mock := &MockChartInstallRunner{ctrl: ctrl} 29 | mock.recorder = &MockChartInstallRunnerMockRecorder{mock} 30 | return mock 31 | } 32 | 33 | // EXPECT returns an object that allows the caller to indicate expected use. 34 | func (m *MockChartInstallRunner) EXPECT() *MockChartInstallRunnerMockRecorder { 35 | return m.recorder 36 | } 37 | 38 | // ChartInstallRunner mocks base method. 39 | func (m *MockChartInstallRunner) ChartInstallRunner(arg0 *chart.Chart, arg1 map[string]interface{}) (*release.Release, error) { 40 | m.ctrl.T.Helper() 41 | ret := m.ctrl.Call(m, "ChartInstallRunner", arg0, arg1) 42 | ret0, _ := ret[0].(*release.Release) 43 | ret1, _ := ret[1].(error) 44 | return ret0, ret1 45 | } 46 | 47 | // ChartInstallRunner indicates an expected call of ChartInstallRunner. 48 | func (mr *MockChartInstallRunnerMockRecorder) ChartInstallRunner(arg0, arg1 interface{}) *gomock.Call { 49 | mr.mock.ctrl.T.Helper() 50 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChartInstallRunner", reflect.TypeOf((*MockChartInstallRunner)(nil).ChartInstallRunner), arg0, arg1) 51 | } 52 | 53 | // MockChartUninstallRunner is a mock of ChartUninstallRunner interface. 54 | type MockChartUninstallRunner struct { 55 | ctrl *gomock.Controller 56 | recorder *MockChartUninstallRunnerMockRecorder 57 | } 58 | 59 | // MockChartUninstallRunnerMockRecorder is the mock recorder for MockChartUninstallRunner. 60 | type MockChartUninstallRunnerMockRecorder struct { 61 | mock *MockChartUninstallRunner 62 | } 63 | 64 | // NewMockChartUninstallRunner creates a new mock instance. 65 | func NewMockChartUninstallRunner(ctrl *gomock.Controller) *MockChartUninstallRunner { 66 | mock := &MockChartUninstallRunner{ctrl: ctrl} 67 | mock.recorder = &MockChartUninstallRunnerMockRecorder{mock} 68 | return mock 69 | } 70 | 71 | // EXPECT returns an object that allows the caller to indicate expected use. 72 | func (m *MockChartUninstallRunner) EXPECT() *MockChartUninstallRunnerMockRecorder { 73 | return m.recorder 74 | } 75 | 76 | // ChartUninstallRunner mocks base method. 77 | func (m *MockChartUninstallRunner) ChartUninstallRunner(arg0 string) (*release.UninstallReleaseResponse, error) { 78 | m.ctrl.T.Helper() 79 | ret := m.ctrl.Call(m, "ChartUninstallRunner", arg0) 80 | ret0, _ := ret[0].(*release.UninstallReleaseResponse) 81 | ret1, _ := ret[1].(error) 82 | return ret0, ret1 83 | } 84 | 85 | // ChartUninstallRunner indicates an expected call of ChartUninstallRunner. 86 | func (mr *MockChartUninstallRunnerMockRecorder) ChartUninstallRunner(arg0 interface{}) *gomock.Call { 87 | mr.mock.ctrl.T.Helper() 88 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ChartUninstallRunner", reflect.TypeOf((*MockChartUninstallRunner)(nil).ChartUninstallRunner), arg0) 89 | } 90 | -------------------------------------------------------------------------------- /pkg/helm/repository.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm 18 | 19 | import ( 20 | "fmt" 21 | 22 | "helm.sh/helm/v3/pkg/getter" 23 | "helm.sh/helm/v3/pkg/repo" 24 | ) 25 | 26 | // RepositoryInitializer is the interface that wraps the Initialize method for initializing a 27 | // repo.ChartRepository. 28 | type RepositoryInitializer interface { 29 | Initialize(*repo.Entry, getter.Providers) (*repo.ChartRepository, error) 30 | } 31 | 32 | // RepositoryDownloader is the interface that wraps the DownloadIndex method for downloading a 33 | // ChartRepo. 34 | type RepositoryDownloader interface { 35 | DownloadIndex(ChartRepo) (string, error) 36 | } 37 | 38 | // RepositoryLoader is the interface that wraps the chart repository Load method. 39 | type RepositoryLoader interface { 40 | Load(path string) (*repo.IndexFile, error) 41 | } 42 | 43 | // RepositoryInitializeDownloadLoader wraps all the repository interfaces. 44 | type RepositoryInitializeDownloadLoader interface { 45 | RepositoryInitializer 46 | RepositoryDownloader 47 | RepositoryLoader 48 | } 49 | 50 | // ChartRepo is the interface that wraps the DownloadIndexFile method. It exists to be mocked on the 51 | // Downloader.Download. 52 | type ChartRepo interface { 53 | DownloadIndexFile() (string, error) 54 | } 55 | 56 | // RepositoryClient satisfies the RepositoryInitializeDownloadLoader interface. 57 | type RepositoryClient struct { 58 | newChartRepository func(*repo.Entry, getter.Providers) (*repo.ChartRepository, error) 59 | loadIndexFile func(string) (*repo.IndexFile, error) 60 | } 61 | 62 | // NewDefaultRepositoryClient creates a new RepositoryClient with the default dependencies. 63 | func NewDefaultRepositoryClient() *RepositoryClient { 64 | return NewRepositoryClient(repo.NewChartRepository, repo.LoadIndexFile) 65 | } 66 | 67 | // NewRepositoryClient creates a new RepositoryClient with the explicit dependencies. 68 | func NewRepositoryClient( 69 | newChartRepository func(*repo.Entry, getter.Providers) (*repo.ChartRepository, error), 70 | loadIndexFile func(string) (*repo.IndexFile, error), 71 | ) *RepositoryClient { 72 | return &RepositoryClient{ 73 | newChartRepository: newChartRepository, 74 | loadIndexFile: loadIndexFile, 75 | } 76 | } 77 | 78 | // Initialize initializes a repo.ChartRepository. 79 | func (rc *RepositoryClient) Initialize( 80 | cfg *repo.Entry, 81 | providers getter.Providers, 82 | ) (*repo.ChartRepository, error) { 83 | chartRepo, err := rc.newChartRepository(cfg, providers) 84 | if err != nil { 85 | return nil, fmt.Errorf("failed to initialize repository %q: %v", cfg.Name, err) 86 | } 87 | return chartRepo, nil 88 | } 89 | 90 | // DownloadIndex downloads a chart repository index and returns its path. 91 | func (*RepositoryClient) DownloadIndex(chartRepo ChartRepo) (string, error) { 92 | indexPath, err := chartRepo.DownloadIndexFile() 93 | if err != nil { 94 | return "", fmt.Errorf("failed to download repository index: %v", err) 95 | } 96 | return indexPath, nil 97 | } 98 | 99 | // Load loads a chart repository index file using the path on disk. 100 | func (rc *RepositoryClient) Load(path string) (*repo.IndexFile, error) { 101 | index, err := rc.loadIndexFile(path) 102 | if err != nil { 103 | return nil, fmt.Errorf("failed to load repository index %q: %v", path, err) 104 | } 105 | return index, nil 106 | } 107 | -------------------------------------------------------------------------------- /pkg/helm/repository_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package helm_test 18 | 19 | import ( 20 | "fmt" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | "github.com/golang/mock/gomock" 26 | "helm.sh/helm/v3/pkg/getter" 27 | "helm.sh/helm/v3/pkg/repo" 28 | 29 | "github.com/kubernetes-sigs/minibroker/pkg/helm" 30 | "github.com/kubernetes-sigs/minibroker/pkg/helm/mocks" 31 | ) 32 | 33 | //go:generate mockgen -destination=./mocks/mock_repository.go -package=mocks github.com/kubernetes-sigs/minibroker/pkg/helm RepositoryInitializer,RepositoryDownloader,RepositoryLoader,RepositoryInitializeDownloadLoader,ChartRepo 34 | 35 | var _ = Describe("Repository", func() { 36 | Context("RepositoryClient", func() { 37 | Describe("NewDefaultRepositoryClient", func() { 38 | It("should satisfy the RepositoryInitializeDownloadLoader interface", func() { 39 | var rc helm.RepositoryInitializeDownloadLoader = helm.NewDefaultRepositoryClient() 40 | Expect(rc).NotTo(BeNil()) 41 | }) 42 | }) 43 | 44 | Describe("Initialize", func() { 45 | It("should fail when the internal newChartRepository fails", func() { 46 | cfg := &repo.Entry{Name: "foo"} 47 | providers := make(getter.Providers, 0) 48 | newChartRepository := func(arg0 *repo.Entry, arg1 getter.Providers) (*repo.ChartRepository, error) { 49 | Expect(arg0).To(Equal(cfg)) 50 | Expect(arg1).To(Equal(providers)) 51 | return nil, fmt.Errorf("some error") 52 | } 53 | rc := helm.NewRepositoryClient(newChartRepository, nil) 54 | chartRepo, err := rc.Initialize(cfg, providers) 55 | Expect(err).To(Equal(fmt.Errorf("failed to initialize repository \"foo\": some error"))) 56 | Expect(chartRepo).To(BeNil()) 57 | }) 58 | 59 | It("should succeed when the internal newChartRepository succeeds", func() { 60 | cfg := &repo.Entry{} 61 | providers := make(getter.Providers, 0) 62 | expectedChartRepo := &repo.ChartRepository{} 63 | newChartRepository := func(arg0 *repo.Entry, arg1 getter.Providers) (*repo.ChartRepository, error) { 64 | Expect(arg0).To(Equal(cfg)) 65 | Expect(arg1).To(Equal(providers)) 66 | return expectedChartRepo, nil 67 | } 68 | rc := helm.NewRepositoryClient(newChartRepository, nil) 69 | chartRepo, err := rc.Initialize(cfg, providers) 70 | Expect(err).NotTo(HaveOccurred()) 71 | Expect(chartRepo).To(Equal(expectedChartRepo)) 72 | }) 73 | }) 74 | 75 | Describe("DownloadIndex", func() { 76 | var ctrl *gomock.Controller 77 | 78 | BeforeEach(func() { 79 | ctrl = gomock.NewController(GinkgoT()) 80 | }) 81 | 82 | AfterEach(func() { 83 | ctrl.Finish() 84 | }) 85 | 86 | It("should fail when chartRepo.DownloadIndexFile fails", func() { 87 | expectedIndexPath := "" 88 | chartRepo := mocks.NewMockChartRepo(ctrl) 89 | chartRepo.EXPECT(). 90 | DownloadIndexFile(). 91 | Return("", fmt.Errorf("some error")). 92 | Times(1) 93 | rc := helm.NewRepositoryClient(nil, nil) 94 | indexPath, err := rc.DownloadIndex(chartRepo) 95 | Expect(err).To(Equal(fmt.Errorf("failed to download repository index: some error"))) 96 | Expect(indexPath).To(Equal(expectedIndexPath)) 97 | }) 98 | 99 | It("should succeed when chartRepo.DownloadIndexFile succeeds", func() { 100 | expectedIndexPath := "some_index.yaml" 101 | chartRepo := mocks.NewMockChartRepo(ctrl) 102 | chartRepo.EXPECT(). 103 | DownloadIndexFile(). 104 | Return(expectedIndexPath, nil). 105 | Times(1) 106 | rc := helm.NewRepositoryClient(nil, nil) 107 | indexPath, err := rc.DownloadIndex(chartRepo) 108 | Expect(err).NotTo(HaveOccurred()) 109 | Expect(indexPath).To(Equal(expectedIndexPath)) 110 | }) 111 | }) 112 | 113 | Describe("Load", func() { 114 | It("should fail when the internal loadIndexFile fails", func() { 115 | path := "some_index.yaml" 116 | loadIndexFile := func(arg0 string) (*repo.IndexFile, error) { 117 | Expect(arg0).To(Equal(path)) 118 | return nil, fmt.Errorf("some error") 119 | } 120 | rc := helm.NewRepositoryClient(nil, loadIndexFile) 121 | indexFile, err := rc.Load(path) 122 | Expect(err).To(Equal(fmt.Errorf("failed to load repository index \"some_index.yaml\": some error"))) 123 | Expect(indexFile).To(BeNil()) 124 | }) 125 | 126 | It("should succeed when the internal loadIndexFile succeeds", func() { 127 | path := "some_index.yaml" 128 | expectedIndexFile := &repo.IndexFile{} 129 | loadIndexFile := func(arg0 string) (*repo.IndexFile, error) { 130 | Expect(arg0).To(Equal(path)) 131 | return expectedIndexFile, nil 132 | } 133 | rc := helm.NewRepositoryClient(nil, loadIndexFile) 134 | indexFile, err := rc.Load(path) 135 | Expect(err).NotTo(HaveOccurred()) 136 | Expect(indexFile).To(Equal(expectedIndexFile)) 137 | }) 138 | }) 139 | }) 140 | }) 141 | -------------------------------------------------------------------------------- /pkg/helm/testutil/chart.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testutil 18 | 19 | import ( 20 | "helm.sh/helm/v3/pkg/chart" 21 | "helm.sh/helm/v3/pkg/release" 22 | ) 23 | 24 | type ChartInstallRunner interface { 25 | ChartInstallRunner(*chart.Chart, map[string]interface{}) (*release.Release, error) 26 | } 27 | 28 | type ChartUninstallRunner interface { 29 | ChartUninstallRunner(string) (*release.UninstallReleaseResponse, error) 30 | } 31 | -------------------------------------------------------------------------------- /pkg/helm/testutil/config.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testutil 18 | 19 | import ( 20 | "helm.sh/helm/v3/pkg/action" 21 | "k8s.io/cli-runtime/pkg/genericclioptions" 22 | 23 | "github.com/kubernetes-sigs/minibroker/pkg/helm" 24 | ) 25 | 26 | type ConfigProvider interface { 27 | ConfigProvider(namespace string) (*action.Configuration, error) 28 | } 29 | 30 | type ConfigInitializer interface { 31 | ConfigInitializer( 32 | getter genericclioptions.RESTClientGetter, 33 | namespace string, 34 | helmDriver string, 35 | log action.DebugLog, 36 | ) error 37 | } 38 | 39 | type ConfigInitializerProvider interface { 40 | ConfigInitializerProvider() (*action.Configuration, helm.ConfigInitializer) 41 | } 42 | -------------------------------------------------------------------------------- /pkg/kubernetes/cluster.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubernetes 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "io/ioutil" 23 | "strings" 24 | 25 | "github.com/containers/libpod/pkg/resolvconf" 26 | ) 27 | 28 | // ClusterDomain returns the k8s cluster domain extracted from 29 | // /etc/resolv.conf. 30 | func ClusterDomain(resolvConf io.Reader) (string, error) { 31 | data, err := ioutil.ReadAll(resolvConf) 32 | if err != nil { 33 | return "", fmt.Errorf("failed to get cluster domain: %w", err) 34 | } 35 | 36 | domains := resolvconf.GetSearchDomains(data) 37 | for _, domain := range domains { 38 | if strings.HasPrefix(domain, "svc.") { 39 | return strings.TrimPrefix(domain, "svc."), nil 40 | } 41 | } 42 | 43 | err = fmt.Errorf("missing domain starting with 'svc.' in the search path") 44 | return "", fmt.Errorf("failed to get cluster domain: %w", err) 45 | } 46 | -------------------------------------------------------------------------------- /pkg/kubernetes/cluster_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubernetes_test 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | 26 | "github.com/kubernetes-sigs/minibroker/pkg/kubernetes" 27 | ) 28 | 29 | var _ = Describe("Cluster", func() { 30 | It("should fail when reading the resolv.conf reader fails", func() { 31 | var resolvConf failReader 32 | clusterDomain, err := kubernetes.ClusterDomain(&resolvConf) 33 | Expect(clusterDomain).To(BeEmpty()) 34 | Expect(err).To(MatchError("failed to get cluster domain: failed to read")) 35 | }) 36 | 37 | It("should fail when the search path is missing", func() { 38 | var resolvConf bytes.Buffer 39 | fmt.Fprintln(&resolvConf, "nameserver 1.2.3.4") 40 | fmt.Fprintln(&resolvConf, "nameserver 4.3.2.1") 41 | clusterDomain, err := kubernetes.ClusterDomain(&resolvConf) 42 | Expect(clusterDomain).To(BeEmpty()) 43 | Expect(err).To(MatchError("failed to get cluster domain: missing domain starting with 'svc.' in the search path")) 44 | }) 45 | 46 | It("should fail when the search path is missing a domain starting with svc.", func() { 47 | var resolvConf bytes.Buffer 48 | fmt.Fprintln(&resolvConf, "nameserver 1.2.3.4") 49 | fmt.Fprintln(&resolvConf, "nameserver 4.3.2.1") 50 | fmt.Fprintln(&resolvConf, "search kubecf.svc.cluster.local cluster.local") 51 | fmt.Fprintln(&resolvConf, "options ndots:5") 52 | clusterDomain, err := kubernetes.ClusterDomain(&resolvConf) 53 | Expect(clusterDomain).To(BeEmpty()) 54 | Expect(err).To(MatchError("failed to get cluster domain: missing domain starting with 'svc.' in the search path")) 55 | }) 56 | 57 | It("should succeed returning the cluster domain", func() { 58 | var resolvConf bytes.Buffer 59 | fmt.Fprintln(&resolvConf, "nameserver 1.2.3.4") 60 | fmt.Fprintln(&resolvConf, "nameserver 4.3.2.1") 61 | fmt.Fprintln(&resolvConf, "search kubecf.svc.cluster.local svc.cluster.local cluster.local") 62 | fmt.Fprintln(&resolvConf, "options ndots:5") 63 | clusterDomain, err := kubernetes.ClusterDomain(&resolvConf) 64 | Expect(clusterDomain).To(Equal("cluster.local")) 65 | Expect(err).ToNot(HaveOccurred()) 66 | }) 67 | }) 68 | 69 | type failReader struct{} 70 | 71 | func (*failReader) Read(_ []byte) (int, error) { 72 | return 0, fmt.Errorf("failed to read") 73 | } 74 | -------------------------------------------------------------------------------- /pkg/kubernetes/kubernetes_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package kubernetes_test 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestKubernetes(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Kubernetes Suite") 29 | } 30 | -------------------------------------------------------------------------------- /pkg/log/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | Package log contains definitions and implementations for dealing with logging. 19 | */ 20 | package log 21 | -------------------------------------------------------------------------------- /pkg/log/klog.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package log 18 | 19 | import klog "k8s.io/klog/v2" 20 | 21 | const klogMaxLevel = 5 22 | 23 | // Klog satisfies the Verboser interface. 24 | type Klog struct { 25 | levels []*klogLogger 26 | } 27 | 28 | // NewKlog creates a new Klog wrapped in the Verboser interface. 29 | func NewKlog() Verboser { 30 | levels := make([]*klogLogger, 0, klogMaxLevel+1) 31 | for level := 0; level <= 5; level++ { 32 | v := klog.V(klog.Level(level)) 33 | levels = append(levels, &klogLogger{v}) 34 | } 35 | return &Klog{levels} 36 | } 37 | 38 | // V returns a Logger for the provided level. 39 | func (l *Klog) V(level Level) Logger { 40 | if level > klogMaxLevel { 41 | return l.levels[klogMaxLevel] 42 | } 43 | return l.levels[level] 44 | } 45 | 46 | // klogLogger satisfies the Logger interface. 47 | type klogLogger struct { 48 | v klog.Verbose 49 | } 50 | 51 | // Enabled returns whether the Logger is enabled or not. 52 | func (l *klogLogger) Enabled() bool { 53 | return l.v.Enabled() 54 | } 55 | 56 | // Log logs a message. 57 | func (l *klogLogger) Log(format string, args ...interface{}) { 58 | l.v.Infof(format, args...) 59 | } 60 | -------------------------------------------------------------------------------- /pkg/log/log.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package log 18 | 19 | // Level represents a log level. 20 | type Level int32 21 | 22 | // Verboser wraps the V method for providing a Logger. 23 | type Verboser interface { 24 | V(Level) Logger 25 | } 26 | 27 | // Logger defines how a logger should be implemented. 28 | type Logger interface { 29 | Enabled() bool 30 | Log(format string, args ...interface{}) 31 | } 32 | -------------------------------------------------------------------------------- /pkg/log/noop.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package log 18 | 19 | // NewNoop creates a new noop wrapped in the Verboser interface. The noop verboser and logger should 20 | // be used when a logger is required, but shouldn't log anything. 21 | func NewNoop() Verboser { 22 | return &noop{&noopLogger{}} 23 | } 24 | 25 | type noop struct { 26 | log *noopLogger 27 | } 28 | 29 | func (l *noop) V(Level) Logger { 30 | return l.log 31 | } 32 | 33 | type noopLogger struct{} 34 | 35 | func (l *noopLogger) Enabled() bool { 36 | return true 37 | } 38 | 39 | func (l *noopLogger) Log(format string, args ...interface{}) { 40 | } 41 | -------------------------------------------------------------------------------- /pkg/minibroker/mariadb.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "fmt" 21 | "net/url" 22 | 23 | "github.com/pkg/errors" 24 | corev1 "k8s.io/api/core/v1" 25 | ) 26 | 27 | const ( 28 | mariadbProtocolName = "mysql" 29 | rootMariadbUsername = "root" 30 | ) 31 | 32 | type MariadbProvider struct { 33 | hostBuilder 34 | } 35 | 36 | func (p MariadbProvider) Bind( 37 | services []corev1.Service, 38 | _ *BindParams, 39 | provisionParams *ProvisionParams, 40 | chartSecrets Object, 41 | ) (Object, error) { 42 | service := services[0] 43 | if len(service.Spec.Ports) == 0 { 44 | return nil, errors.Errorf("no ports found") 45 | } 46 | svcPort := service.Spec.Ports[0] 47 | 48 | host := p.hostFromService(&service) 49 | 50 | database, err := provisionParams.DigStringOr("db.name", "") 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to get database name: %w", err) 53 | } 54 | user, err := provisionParams.DigStringOr("db.user", rootMariadbUsername) 55 | if err != nil { 56 | return nil, fmt.Errorf("failed to get username: %w", err) 57 | } 58 | 59 | var passwordKey string 60 | if user == rootMariadbUsername { 61 | passwordKey = "mariadb-root-password" 62 | } else { 63 | passwordKey = "mariadb-password" 64 | } 65 | password, err := chartSecrets.DigString(passwordKey) 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to get password: %w", err) 68 | } 69 | 70 | creds := Object{ 71 | "protocol": mariadbProtocolName, 72 | "port": svcPort.Port, 73 | "host": host, 74 | "username": user, 75 | "password": password, 76 | "database": database, 77 | "uri": (&url.URL{ 78 | Scheme: mariadbProtocolName, 79 | User: url.UserPassword(user, password), 80 | Host: fmt.Sprintf("%s:%d", host, svcPort.Port), 81 | Path: database, 82 | }).String(), 83 | } 84 | 85 | return creds, nil 86 | } 87 | -------------------------------------------------------------------------------- /pkg/minibroker/minibroker_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "testing" 21 | 22 | "helm.sh/helm/v3/pkg/chart" 23 | "helm.sh/helm/v3/pkg/repo" 24 | ) 25 | 26 | func TestHasTag(t *testing.T) { 27 | tagTests := []struct { 28 | tag string 29 | list []string 30 | expected bool 31 | }{ 32 | {"foo", []string{"foo", "bar"}, true}, 33 | {"foo", []string{"bar", "baz"}, false}, 34 | {"foo", []string{}, false}, 35 | } 36 | 37 | for _, tt := range tagTests { 38 | actual := hasTag(tt.tag, tt.list) 39 | if actual != tt.expected { 40 | t.Errorf("hasTag(%s %v): expected %t, actual %t", 41 | tt.tag, tt.list, tt.expected, actual) 42 | } 43 | } 44 | } 45 | 46 | func TestGetTagIntersection(t *testing.T) { 47 | intersectionTests := []struct { 48 | charts repo.ChartVersions 49 | expected []string 50 | }{ 51 | {nil, []string{}}, 52 | { 53 | repo.ChartVersions{ 54 | &repo.ChartVersion{ 55 | Metadata: &chart.Metadata{ 56 | Keywords: []string{}, 57 | }, 58 | }, 59 | }, 60 | []string{}, 61 | }, 62 | { 63 | repo.ChartVersions{ 64 | &repo.ChartVersion{ 65 | Metadata: &chart.Metadata{ 66 | Keywords: []string{"foo", "bar"}, 67 | }, 68 | }, 69 | }, 70 | []string{"foo", "bar"}}, 71 | { 72 | repo.ChartVersions{ 73 | &repo.ChartVersion{ 74 | Metadata: &chart.Metadata{ 75 | Keywords: []string{"foo", "bar"}, 76 | }, 77 | }, 78 | &repo.ChartVersion{ 79 | Metadata: &chart.Metadata{ 80 | Keywords: []string{"baz", "foo"}, 81 | }, 82 | }, 83 | }, 84 | []string{"foo"}}, 85 | } 86 | 87 | for _, tt := range intersectionTests { 88 | actual := getTagIntersection(tt.charts) 89 | 90 | if len(actual) != len(tt.expected) { 91 | t.Errorf("getTagIntersection(%v): expected %v, actual %v", 92 | tt.charts, tt.expected, actual) 93 | 94 | break 95 | } 96 | 97 | for index, keyword := range actual { 98 | if keyword != tt.expected[index] { 99 | t.Errorf("getTagIntersection(%v): expected %v, actual %v", 100 | tt.charts, tt.expected, actual) 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /pkg/minibroker/mongodb.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "fmt" 21 | "net/url" 22 | 23 | "github.com/pkg/errors" 24 | corev1 "k8s.io/api/core/v1" 25 | ) 26 | 27 | const ( 28 | mongodbProtocolName = "mongodb" 29 | rootMongodbUsername = "root" 30 | ) 31 | 32 | type MongodbProvider struct { 33 | hostBuilder 34 | } 35 | 36 | func (p MongodbProvider) Bind( 37 | services []corev1.Service, 38 | _ *BindParams, 39 | provisionParams *ProvisionParams, 40 | chartSecrets Object, 41 | ) (Object, error) { 42 | service := services[0] 43 | if len(service.Spec.Ports) == 0 { 44 | return nil, errors.Errorf("no ports found") 45 | } 46 | svcPort := service.Spec.Ports[0] 47 | 48 | host := p.hostFromService(&service) 49 | 50 | database, err := provisionParams.DigStringOr("mongodbDatabase", "") 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to get database name: %w", err) 53 | } 54 | user, err := provisionParams.DigStringOr("mongodbUsername", rootMongodbUsername) 55 | if err != nil { 56 | return nil, fmt.Errorf("failed to get username: %w", err) 57 | } 58 | 59 | var passwordKey string 60 | if user == rootMongodbUsername { 61 | passwordKey = "mongodb-root-password" 62 | } else { 63 | passwordKey = "mongodb-password" 64 | } 65 | password, err := chartSecrets.DigString(passwordKey) 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to get password: %w", err) 68 | } 69 | 70 | creds := Object{ 71 | "protocol": mongodbProtocolName, 72 | "port": svcPort.Port, 73 | "host": host, 74 | "username": user, 75 | "password": password, 76 | "database": database, 77 | "uri": (&url.URL{ 78 | Scheme: mongodbProtocolName, 79 | User: url.UserPassword(user, password), 80 | Host: fmt.Sprintf("%s:%d", host, svcPort.Port), 81 | Path: database, 82 | }).String(), 83 | } 84 | 85 | return creds, nil 86 | } 87 | -------------------------------------------------------------------------------- /pkg/minibroker/mysql.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "fmt" 21 | "net/url" 22 | 23 | "github.com/pkg/errors" 24 | corev1 "k8s.io/api/core/v1" 25 | ) 26 | 27 | const ( 28 | mysqlProtocolName = "mysql" 29 | rootMysqlUsername = "root" 30 | ) 31 | 32 | type MySQLProvider struct { 33 | hostBuilder 34 | } 35 | 36 | func (p MySQLProvider) Bind( 37 | services []corev1.Service, 38 | _ *BindParams, 39 | provisionParams *ProvisionParams, 40 | chartSecrets Object, 41 | ) (Object, error) { 42 | service := services[0] 43 | if len(service.Spec.Ports) == 0 { 44 | return nil, errors.Errorf("no ports found") 45 | } 46 | svcPort := service.Spec.Ports[0] 47 | 48 | host := p.hostFromService(&service) 49 | 50 | database, err := provisionParams.DigStringOr("mysqlDatabase", "") 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to get database name: %w", err) 53 | } 54 | user, err := provisionParams.DigStringOr("mysqlUser", rootMysqlUsername) 55 | if err != nil { 56 | return nil, fmt.Errorf("failed to get username: %w", err) 57 | } 58 | 59 | var passwordKey string 60 | if user == rootMysqlUsername { 61 | passwordKey = "mysql-root-password" 62 | } else { 63 | passwordKey = "mysql-password" 64 | } 65 | password, err := chartSecrets.DigString(passwordKey) 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to get password: %w", err) 68 | } 69 | 70 | creds := Object{ 71 | "protocol": mysqlProtocolName, 72 | "port": svcPort.Port, 73 | "host": host, 74 | "username": user, 75 | "password": password, 76 | "database": database, 77 | "uri": (&url.URL{ 78 | Scheme: mysqlProtocolName, 79 | User: url.UserPassword(user, password), 80 | Host: fmt.Sprintf("%s:%d", host, svcPort.Port), 81 | Path: database, 82 | }).String(), 83 | } 84 | 85 | return creds, nil 86 | } 87 | -------------------------------------------------------------------------------- /pkg/minibroker/postgres.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "fmt" 21 | "net/url" 22 | 23 | "github.com/pkg/errors" 24 | corev1 "k8s.io/api/core/v1" 25 | ) 26 | 27 | const ( 28 | postgresqlProtocolName = "postgresql" 29 | defaultPostgresqlUsername = "postgres" 30 | ) 31 | 32 | type PostgresProvider struct { 33 | hostBuilder 34 | } 35 | 36 | func (p PostgresProvider) Bind( 37 | services []corev1.Service, 38 | _ *BindParams, 39 | provisionParams *ProvisionParams, 40 | chartSecrets Object, 41 | ) (Object, error) { 42 | service := services[0] 43 | if len(service.Spec.Ports) == 0 { 44 | return nil, errors.Errorf("no ports found") 45 | } 46 | svcPort := service.Spec.Ports[0] 47 | 48 | host := p.hostFromService(&service) 49 | 50 | database, err := provisionParams.DigStringAltOr( 51 | // Some older chart versions use postgresDatabase instead of postgresqlDatabase. 52 | []string{"postgresqlDatabase", "postgresDatabase"}, 53 | "", 54 | ) 55 | if err != nil { 56 | return nil, fmt.Errorf("failed to get database name: %w", err) 57 | } 58 | user, err := provisionParams.DigStringAltOr( 59 | // Some older chart versions use postgresUsername instead of postgresqlUsername. 60 | []string{"postgresqlUsername", "postgresUsername"}, 61 | defaultPostgresqlUsername, 62 | ) 63 | if err != nil { 64 | return nil, fmt.Errorf("failed to get username: %w", err) 65 | } 66 | 67 | var passwordKey, altPasswordKey string 68 | // postgresql-postgres-password is used when postgresqlPostgresPassword is set and 69 | // postgresqlUsername is not 'postgres'. 70 | if _, ok := provisionParams.Dig("postgresqlPostgresPassword"); ok && user != defaultPostgresqlUsername { 71 | passwordKey = "postgresql-postgres-password" 72 | } else { 73 | passwordKey = "postgresql-password" 74 | // Chart versions <2.0 use postgres-password instead of postgresql-password. 75 | // See https://github.com/kubernetes-sigs/minibroker/issues/17 76 | altPasswordKey = "postgres-password" 77 | } 78 | password, err := chartSecrets.DigStringAlt([]string{passwordKey, altPasswordKey}) 79 | if err != nil { 80 | return nil, fmt.Errorf("failed to get password: %w", err) 81 | } 82 | 83 | creds := Object{ 84 | "protocol": postgresqlProtocolName, 85 | "port": svcPort.Port, 86 | "host": host, 87 | "username": user, 88 | "password": password, 89 | "database": database, 90 | "uri": (&url.URL{ 91 | Scheme: postgresqlProtocolName, 92 | User: url.UserPassword(user, password), 93 | Host: fmt.Sprintf("%s:%d", host, svcPort.Port), 94 | Path: database, 95 | }).String(), 96 | } 97 | 98 | return creds, nil 99 | } 100 | -------------------------------------------------------------------------------- /pkg/minibroker/provider.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | corev1 "k8s.io/api/core/v1" 24 | ) 25 | 26 | // Provider is the interface for the Service Provider. Its methods wrap service-specific logic. 27 | type Provider interface { 28 | Bind( 29 | service []corev1.Service, 30 | bindParams *BindParams, 31 | provisionParams *ProvisionParams, 32 | chartSecrets Object, 33 | ) (Object, error) 34 | } 35 | 36 | // Object is a wrapper around map[string]interface{} that implements methods for helping with 37 | // digging and type asserting. 38 | type Object map[string]interface{} 39 | 40 | var ( 41 | // ErrDigNotFound is the error for a key not found in the Object. 42 | ErrDigNotFound = fmt.Errorf("key not found") 43 | // ErrDigNotString is the error for a key that is not a string. 44 | ErrDigNotString = fmt.Errorf("key is not a string") 45 | ) 46 | 47 | // Dig digs the Object based on the provided key. 48 | // key must be in the format "foo.bar.baz". Each segment represents a level in the Object. 49 | func (o Object) Dig(key string) (interface{}, bool) { 50 | if key == "" { 51 | return nil, false 52 | } 53 | keyParts := strings.Split(key, ".") 54 | var part interface{} = o 55 | var ok bool 56 | for _, keyPart := range keyParts { 57 | if keyPart == "" { 58 | return nil, false 59 | } 60 | switch p := part.(type) { 61 | case map[string]interface{}: 62 | if part, ok = p[keyPart]; !ok { 63 | return nil, false 64 | } 65 | case Object: 66 | if part, ok = p[keyPart]; !ok { 67 | return nil, false 68 | } 69 | default: 70 | return nil, false 71 | } 72 | } 73 | return part, ok 74 | } 75 | 76 | // DigString wraps Object.Dig and type-asserts the found key. 77 | func (o Object) DigString(key string) (string, error) { 78 | val, ok := o.Dig(key) 79 | if !ok { 80 | return "", ErrDigNotFound 81 | } 82 | valStr, ok := val.(string) 83 | if !ok { 84 | return "", ErrDigNotString 85 | } 86 | return valStr, nil 87 | } 88 | 89 | // DigStringAlt digs for any of the given keys, returning the first found. It returns an error if 90 | // none of the alternative keys are found. 91 | func (o Object) DigStringAlt(altKeys []string) (string, error) { 92 | for _, altKey := range altKeys { 93 | valStr, err := o.DigString(altKey) 94 | if err == ErrDigNotFound { 95 | continue 96 | } 97 | if err != nil { 98 | return "", err 99 | } 100 | return valStr, nil 101 | } 102 | return "", ErrDigNotFound 103 | } 104 | 105 | // DigStringOr wraps Object.DigString and returns defaultValue if the value was not found. 106 | func (o Object) DigStringOr(key string, defaultValue string) (string, error) { 107 | str, err := o.DigString(key) 108 | if err == ErrDigNotFound { 109 | return defaultValue, nil 110 | } 111 | if err != nil { 112 | return "", err 113 | } 114 | return str, nil 115 | } 116 | 117 | // DigStringAltOr wraps Object.DigStringAlt and returns defaultValue if none of the alternative 118 | // keys are found. 119 | func (o Object) DigStringAltOr(altKeys []string, defaultValue string) (string, error) { 120 | str, err := o.DigStringAlt(altKeys) 121 | if err == ErrDigNotFound { 122 | return defaultValue, nil 123 | } 124 | if err != nil { 125 | return "", err 126 | } 127 | return str, nil 128 | } 129 | 130 | // BindParams is a specialization of Object for binding parameters, ensuring type checking. 131 | type BindParams struct { 132 | Object 133 | } 134 | 135 | // NewBindParams constructs a new BindParams. 136 | func NewBindParams(m map[string]interface{}) *BindParams { 137 | return &BindParams{Object: m} 138 | } 139 | 140 | // ProvisionParams is a specialization of Object for provisioning parameters, ensuring type 141 | // checking. 142 | type ProvisionParams struct { 143 | Object 144 | } 145 | 146 | // NewProvisionParams constructs a new ProvisionParams. 147 | func NewProvisionParams(m map[string]interface{}) *ProvisionParams { 148 | return &ProvisionParams{Object: m} 149 | } 150 | 151 | // hostBuilder provides the method for building the OSBAPI service's URI host 152 | // from a k8s service. 153 | type hostBuilder struct { 154 | clusterDomain string 155 | } 156 | 157 | // hostFromService builds the FQDN for the host using the service name and 158 | // namespace, appending the cluster domain. 159 | func (hb *hostBuilder) hostFromService(service *corev1.Service) string { 160 | return fmt.Sprintf("%s.%s.svc.%s", service.Name, service.Namespace, hb.clusterDomain) 161 | } 162 | -------------------------------------------------------------------------------- /pkg/minibroker/provider_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import "testing" 20 | 21 | func TestObjectDig(t *testing.T) { 22 | tests := []struct { 23 | obj Object 24 | key string 25 | expectedVal interface{} 26 | expectedOk bool 27 | }{ 28 | { 29 | Object{"foo": "baz"}, 30 | "bar", 31 | nil, 32 | false, 33 | }, 34 | { 35 | Object{"foo": Object{"bar": "baz"}}, 36 | "foo.foo", 37 | nil, 38 | false, 39 | }, 40 | { 41 | Object{"foo": Object{"bar": "baz"}}, 42 | "foo.bar.bar", 43 | nil, 44 | false, 45 | }, 46 | { 47 | Object{"foo": Object{"bar": "baz"}}, 48 | "", 49 | nil, 50 | false, 51 | }, 52 | { 53 | Object{"foo": Object{"": "baz"}}, 54 | "foo.", 55 | nil, 56 | false, 57 | }, 58 | { 59 | Object{"foo": Object{"bar": "baz"}}, 60 | "foo.", 61 | nil, 62 | false, 63 | }, 64 | { 65 | Object{"foo": Object{"bar": "baz"}}, 66 | "foo..bar", 67 | nil, 68 | false, 69 | }, 70 | { 71 | Object{"": Object{"bar": "baz"}}, 72 | ".bar", 73 | nil, 74 | false, 75 | }, 76 | { 77 | Object{"foo": "baz"}, 78 | "foo", 79 | "baz", 80 | true, 81 | }, 82 | { 83 | Object{"foo": Object{"bar": "baz"}}, 84 | "foo.bar", 85 | "baz", 86 | true, 87 | }, 88 | } 89 | 90 | for _, tt := range tests { 91 | val, ok := tt.obj.Dig(tt.key) 92 | if ok != tt.expectedOk { 93 | t.Errorf("Object.Dig(%s): expected ok %v, actual ok %v", tt.key, tt.expectedOk, ok) 94 | } 95 | if val != tt.expectedVal { 96 | t.Errorf("Object.Dig(%s): expected val %v, actual val %v", tt.key, tt.expectedVal, val) 97 | } 98 | } 99 | } 100 | 101 | func TestObjectDigString(t *testing.T) { 102 | tests := []struct { 103 | obj Object 104 | key string 105 | expectedVal string 106 | expectedErr error 107 | }{ 108 | { 109 | Object{"foo": "baz"}, 110 | "bar", 111 | "", 112 | ErrDigNotFound, 113 | }, 114 | { 115 | Object{"foo": 3}, 116 | "foo", 117 | "", 118 | ErrDigNotString, 119 | }, 120 | { 121 | Object{"foo": Object{"bar": "baz"}}, 122 | "foo.bar", 123 | "baz", 124 | nil, 125 | }, 126 | } 127 | 128 | for _, tt := range tests { 129 | val, err := tt.obj.DigString(tt.key) 130 | if err != tt.expectedErr { 131 | t.Errorf("Object.DigString(%s): expected err %v, actual err %v", tt.key, tt.expectedErr, err) 132 | } 133 | if val != tt.expectedVal { 134 | t.Errorf("Object.DigString(%s): expected val %v, actual val %v", tt.key, tt.expectedVal, val) 135 | } 136 | } 137 | } 138 | 139 | func TestObjectDigStringAlt(t *testing.T) { 140 | tests := []struct { 141 | obj Object 142 | altKeys []string 143 | expectedVal string 144 | expectedErr error 145 | }{ 146 | { 147 | Object{"foo": "baz"}, 148 | []string{"bar", "baz"}, 149 | "", 150 | ErrDigNotFound, 151 | }, 152 | { 153 | Object{"foo": 3}, 154 | []string{"bar", "foo"}, 155 | "", 156 | ErrDigNotString, 157 | }, 158 | { 159 | Object{"foo": Object{"bar": "baz"}}, 160 | []string{"foo", "foo.bar"}, 161 | "", 162 | ErrDigNotString, 163 | }, 164 | { 165 | Object{"foo": Object{"bar": "baz"}}, 166 | []string{"foo.foo", "foo.bar"}, 167 | "baz", 168 | nil, 169 | }, 170 | } 171 | 172 | for _, tt := range tests { 173 | val, err := tt.obj.DigStringAlt(tt.altKeys) 174 | if err != tt.expectedErr { 175 | t.Errorf("Object.DigStringAlt(%v): expected err %v, actual err %v", tt.altKeys, tt.expectedErr, err) 176 | } 177 | if val != tt.expectedVal { 178 | t.Errorf("Object.DigStringAlt(%v): expected val %v, actual val %v", tt.altKeys, tt.expectedVal, val) 179 | } 180 | } 181 | } 182 | 183 | func TestObjectDigStringOr(t *testing.T) { 184 | tests := []struct { 185 | obj Object 186 | key string 187 | defaultVal string 188 | expectedVal string 189 | expectedErr error 190 | }{ 191 | { 192 | Object{"foo": 1}, 193 | "foo", 194 | "default", 195 | "", 196 | ErrDigNotString, 197 | }, 198 | { 199 | Object{"foo": "baz"}, 200 | "bar", 201 | "default", 202 | "default", 203 | nil, 204 | }, 205 | { 206 | Object{"foo": "baz"}, 207 | "foo", 208 | "default", 209 | "baz", 210 | nil, 211 | }, 212 | } 213 | 214 | for _, tt := range tests { 215 | val, err := tt.obj.DigStringOr(tt.key, tt.defaultVal) 216 | if err != tt.expectedErr { 217 | t.Errorf("Object.DigStringOr(%s): expected err %v, actual err %v", tt.key, tt.expectedErr, err) 218 | } 219 | if val != tt.expectedVal { 220 | t.Errorf("Object.DigStringOr(%s): expected val %v, actual val %v", tt.key, tt.expectedVal, val) 221 | } 222 | } 223 | } 224 | 225 | func TestObjectDigStringAltOr(t *testing.T) { 226 | tests := []struct { 227 | obj Object 228 | altKeys []string 229 | defaultVal string 230 | expectedVal string 231 | expectedErr error 232 | }{ 233 | { 234 | Object{"foo": 1}, 235 | []string{"bar", "foo"}, 236 | "default", 237 | "", 238 | ErrDigNotString, 239 | }, 240 | { 241 | Object{"foo": "baz"}, 242 | []string{"bar", "baz"}, 243 | "default", 244 | "default", 245 | nil, 246 | }, 247 | { 248 | Object{"foo": "baz"}, 249 | []string{"bar", "foo"}, 250 | "default", 251 | "baz", 252 | nil, 253 | }, 254 | } 255 | 256 | for _, tt := range tests { 257 | val, err := tt.obj.DigStringAltOr(tt.altKeys, tt.defaultVal) 258 | if err != tt.expectedErr { 259 | t.Errorf("Object.DigStringAltOr(%v): expected err %v, actual err %v", tt.altKeys, tt.expectedErr, err) 260 | } 261 | if val != tt.expectedVal { 262 | t.Errorf("Object.DigStringAltOr(%v): expected val %v, actual val %v", tt.altKeys, tt.expectedVal, val) 263 | } 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /pkg/minibroker/rabbitmq.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "fmt" 21 | "net/url" 22 | 23 | "github.com/pkg/errors" 24 | corev1 "k8s.io/api/core/v1" 25 | ) 26 | 27 | const ( 28 | amqpProtocolName = "amqp" 29 | defaultRabbitmqUsername = "user" 30 | ) 31 | 32 | type RabbitmqProvider struct { 33 | hostBuilder 34 | } 35 | 36 | func (p RabbitmqProvider) Bind( 37 | services []corev1.Service, 38 | _ *BindParams, 39 | provisionParams *ProvisionParams, 40 | chartSecrets Object, 41 | ) (Object, error) { 42 | if len(services) == 0 { 43 | return nil, errors.Errorf("no services to process") 44 | } 45 | service := services[0] 46 | 47 | var svcPort *corev1.ServicePort 48 | for _, port := range service.Spec.Ports { 49 | if port.Name == amqpProtocolName { 50 | svcPort = &port 51 | break 52 | } 53 | } 54 | if svcPort == nil { 55 | return nil, errors.Errorf("no amqp port found") 56 | } 57 | 58 | user, err := provisionParams.DigStringOr("rabbitmq.username", defaultRabbitmqUsername) 59 | if err != nil { 60 | return nil, fmt.Errorf("failed to get username: %w", err) 61 | } 62 | 63 | password, err := chartSecrets.DigString("rabbitmq-password") 64 | if err != nil { 65 | return nil, fmt.Errorf("failed to get password: %w", err) 66 | } 67 | 68 | host := p.hostFromService(&service) 69 | creds := Object{ 70 | "protocol": amqpProtocolName, 71 | "port": svcPort.Port, 72 | "host": host, 73 | "username": user, 74 | "password": password, 75 | "uri": (&url.URL{ 76 | Scheme: amqpProtocolName, 77 | User: url.UserPassword(user, password), 78 | Host: fmt.Sprintf("%s:%d", host, svcPort.Port), 79 | }).String(), 80 | } 81 | 82 | return creds, nil 83 | } 84 | -------------------------------------------------------------------------------- /pkg/minibroker/redis.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package minibroker 18 | 19 | import ( 20 | "fmt" 21 | "net/url" 22 | 23 | "github.com/pkg/errors" 24 | corev1 "k8s.io/api/core/v1" 25 | ) 26 | 27 | const redisProtocolName = "redis" 28 | 29 | type RedisProvider struct { 30 | hostBuilder 31 | } 32 | 33 | func (p RedisProvider) Bind( 34 | services []corev1.Service, 35 | _ *BindParams, 36 | _ *ProvisionParams, 37 | chartSecrets Object, 38 | ) (Object, error) { 39 | var masterSvc *corev1.Service 40 | for _, svc := range services { 41 | if svc.Spec.Selector["role"] == "master" { 42 | masterSvc = &svc 43 | break 44 | } 45 | } 46 | if masterSvc == nil { 47 | return nil, errors.New("could not identify the master service") 48 | } 49 | 50 | if len(masterSvc.Spec.Ports) == 0 { 51 | return nil, errors.Errorf("no ports found") 52 | } 53 | svcPort := masterSvc.Spec.Ports[0] 54 | 55 | host := p.hostFromService(masterSvc) 56 | 57 | password, err := chartSecrets.DigString("redis-password") 58 | if err != nil { 59 | return nil, fmt.Errorf("failed to get password: %w", err) 60 | } 61 | 62 | creds := Object{ 63 | "protocol": redisProtocolName, 64 | "port": svcPort.Port, 65 | "host": host, 66 | "password": password, 67 | "uri": (&url.URL{ 68 | Scheme: redisProtocolName, 69 | User: url.UserPassword("", password), 70 | Host: fmt.Sprintf("%s:%d", host, svcPort.Port), 71 | }).String(), 72 | } 73 | 74 | return creds, nil 75 | } 76 | -------------------------------------------------------------------------------- /pkg/nameutil/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | /* 18 | The package nameutil contains util structures for generating names. E.g. chart release names. 19 | */ 20 | package nameutil 21 | -------------------------------------------------------------------------------- /pkg/nameutil/generator.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package nameutil 18 | 19 | import ( 20 | "crypto/rand" 21 | "encoding/binary" 22 | "fmt" 23 | "time" 24 | ) 25 | 26 | //go:generate mockgen -destination=./mocks/mock_generator.go -package=mocks github.com/kubernetes-sigs/minibroker/pkg/nameutil Generator 27 | 28 | // Generator is the interface that wraps the basic Generate method. 29 | type Generator interface { 30 | Generate(prefix string) (generated string, err error) 31 | } 32 | 33 | // NameGenerator satisfies the Generator interface for generating names. 34 | type NameGenerator struct { 35 | timeNow func() time.Time 36 | randRead func([]byte) (int, error) 37 | } 38 | 39 | // NewDefaultNameGenerator creates a new NameGenerator with the default dependencies. 40 | func NewDefaultNameGenerator() *NameGenerator { 41 | return NewNameGenerator(time.Now, rand.Read) 42 | } 43 | 44 | // NewNameGenerator creates a new NameGenerator. 45 | func NewNameGenerator( 46 | timeNow func() time.Time, 47 | randRead func([]byte) (int, error), 48 | ) *NameGenerator { 49 | return &NameGenerator{ 50 | timeNow: timeNow, 51 | randRead: randRead, 52 | } 53 | } 54 | 55 | // Generate generates a new name with a prefix based on the UnixNano UTC timestamp plus 2 random 56 | // bytes. 57 | func (ng *NameGenerator) Generate(prefix string) (string, error) { 58 | b := make([]byte, 10) 59 | binary.LittleEndian.PutUint64(b[0:], uint64(ng.timeNow().UTC().UnixNano())) 60 | if _, err := ng.randRead(b[8:]); err != nil { 61 | return "", fmt.Errorf("failed to generate a new name: %v", err) 62 | } 63 | name := fmt.Sprintf("%s%x", prefix, b) 64 | return name, nil 65 | } 66 | -------------------------------------------------------------------------------- /pkg/nameutil/generator_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package nameutil_test 18 | 19 | import ( 20 | "fmt" 21 | "time" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | 26 | "github.com/kubernetes-sigs/minibroker/pkg/nameutil" 27 | ) 28 | 29 | var _ = Describe("Generator", func() { 30 | Context("NameGenerator", func() { 31 | Describe("NewDefaultNameGenerator", func() { 32 | It("should create a new Generator", func() { 33 | var generator nameutil.Generator = nameutil.NewDefaultNameGenerator() 34 | Expect(generator).NotTo(BeNil()) 35 | }) 36 | }) 37 | 38 | Describe("Generate", func() { 39 | fakeTimeNow := func() time.Time { 40 | return time.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC) 41 | } 42 | 43 | It("should fail when rand.Read fails", func() { 44 | generator := nameutil.NewNameGenerator( 45 | fakeTimeNow, 46 | func([]byte) (int, error) { 47 | return 0, fmt.Errorf("failed rand.Read") 48 | }, 49 | ) 50 | 51 | name, err := generator.Generate("a-prefix-") 52 | Expect(name).To(Equal("")) 53 | Expect(err).To(MatchError("failed to generate a new name: failed rand.Read")) 54 | }) 55 | 56 | It("should create a new name", func() { 57 | generator := nameutil.NewNameGenerator( 58 | fakeTimeNow, 59 | func(data []byte) (int, error) { 60 | for i, _ := range data { 61 | data[i] = byte(i + 1) 62 | } 63 | return len(data), nil 64 | }, 65 | ) 66 | 67 | name, err := generator.Generate("a-prefix-") 68 | Expect(name).To(Equal("a-prefix-000064a7b3b6e00d0102")) 69 | Expect(err).NotTo(HaveOccurred()) 70 | }) 71 | }) 72 | }) 73 | }) 74 | -------------------------------------------------------------------------------- /pkg/nameutil/mocks/mock_generator.go: -------------------------------------------------------------------------------- 1 | // Code generated by MockGen. DO NOT EDIT. 2 | // Source: github.com/kubernetes-sigs/minibroker/pkg/nameutil (interfaces: Generator) 3 | 4 | // Package mocks is a generated GoMock package. 5 | package mocks 6 | 7 | import ( 8 | reflect "reflect" 9 | 10 | gomock "github.com/golang/mock/gomock" 11 | ) 12 | 13 | // MockGenerator is a mock of Generator interface. 14 | type MockGenerator struct { 15 | ctrl *gomock.Controller 16 | recorder *MockGeneratorMockRecorder 17 | } 18 | 19 | // MockGeneratorMockRecorder is the mock recorder for MockGenerator. 20 | type MockGeneratorMockRecorder struct { 21 | mock *MockGenerator 22 | } 23 | 24 | // NewMockGenerator creates a new mock instance. 25 | func NewMockGenerator(ctrl *gomock.Controller) *MockGenerator { 26 | mock := &MockGenerator{ctrl: ctrl} 27 | mock.recorder = &MockGeneratorMockRecorder{mock} 28 | return mock 29 | } 30 | 31 | // EXPECT returns an object that allows the caller to indicate expected use. 32 | func (m *MockGenerator) EXPECT() *MockGeneratorMockRecorder { 33 | return m.recorder 34 | } 35 | 36 | // Generate mocks base method. 37 | func (m *MockGenerator) Generate(arg0 string) (string, error) { 38 | m.ctrl.T.Helper() 39 | ret := m.ctrl.Call(m, "Generate", arg0) 40 | ret0, _ := ret[0].(string) 41 | ret1, _ := ret[1].(error) 42 | return ret0, ret1 43 | } 44 | 45 | // Generate indicates an expected call of Generate. 46 | func (mr *MockGeneratorMockRecorder) Generate(arg0 interface{}) *gomock.Call { 47 | mr.mock.ctrl.T.Helper() 48 | return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Generate", reflect.TypeOf((*MockGenerator)(nil).Generate), arg0) 49 | } 50 | -------------------------------------------------------------------------------- /pkg/nameutil/nameutil_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package nameutil_test 18 | 19 | import ( 20 | "testing" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | ) 25 | 26 | func TestNameutil(t *testing.T) { 27 | RegisterFailHandler(Fail) 28 | RunSpecs(t, "Nameutil Suite") 29 | } 30 | -------------------------------------------------------------------------------- /tests/integration/README.md: -------------------------------------------------------------------------------- 1 | # Integration tests 2 | 3 | This is a suite of tests to assert Minibroker integration functionality. 4 | 5 | ## Requirements 6 | 7 | You will need `ginkgo` installed. You can install it using the latest instructions from 8 | https://onsi.github.io/ginkgo/#getting-ginkgo. 9 | 10 | ## Running the test suites 11 | 12 | The tests assume the Service Catalog and Minibroker are already deployed. A namespace for deploying 13 | the service instances is also required to be created ahead of time. 14 | 15 | Environment variables: 16 | - NAMESPACE (required): the namespace used by Minibroker to provision the service instances. 17 | - TEST_BROKER_READY_TIMEOUT (optional): a timeout for waiting for the service broker to be ready. 18 | - TEST_PROVISION_TIMEOUT (optional): a timeout for waiting for the provisioning to complete. 19 | - TEST_BIND_TIMEOUT (optional): a timeout for waiting for the binding to complete. 20 | - TEST_ASSERT_TIMEOUT (optional): a timeout for waiting for the assertion to complete. 21 | 22 | An example running the tests: 23 | 24 | ``` 25 | kubectl create namespace minibroker-tests 26 | NAMESPACE=minibroker-tests ginkgo --nodes 4 --slowSpecThreshold 180 . 27 | ``` 28 | -------------------------------------------------------------------------------- /tests/integration/consumer_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package integration_test 18 | 19 | import ( 20 | "os" 21 | 22 | . "github.com/onsi/ginkgo" 23 | . "github.com/onsi/gomega" 24 | 25 | "github.com/kubernetes-sigs/minibroker/tests/integration/testutil" 26 | ) 27 | 28 | var _ = Describe("A consumer (wordpress)", func() { 29 | It("comes up and goes down", func() { 30 | releaseName := "wordpress" 31 | namespace := "minibroker-tests" 32 | 33 | pathToChart := os.Getenv("WORDPRESS_CHART") 34 | _, err := os.Stat(pathToChart) 35 | Expect(err).ToNot(HaveOccurred()) 36 | 37 | h := testutil.NewHelm(namespace) 38 | 39 | err = h.Install(GinkgoWriter, GinkgoWriter, releaseName, pathToChart) 40 | Expect(err).ToNot(HaveOccurred()) 41 | 42 | err = h.Uninstall(GinkgoWriter, GinkgoWriter, releaseName) 43 | Expect(err).ToNot(HaveOccurred()) 44 | }) 45 | }) 46 | -------------------------------------------------------------------------------- /tests/integration/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package integration contains the Minibroker integration tests. 18 | package integration 19 | -------------------------------------------------------------------------------- /tests/integration/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kubernetes-sigs/minibroker/tests/integration 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Masterminds/goutils v1.1.0 // indirect 7 | github.com/Masterminds/semver v1.5.0 // indirect 8 | github.com/Masterminds/sprig v2.22.0+incompatible 9 | github.com/huandu/xstrings v1.3.1 // indirect 10 | github.com/kubernetes-sigs/service-catalog v0.3.1 11 | github.com/mitchellh/copystructure v1.0.0 // indirect 12 | github.com/onsi/ginkgo v1.16.5 13 | github.com/onsi/gomega v1.17.0 14 | k8s.io/api v0.18.3 15 | k8s.io/apimachinery v0.18.3 16 | k8s.io/client-go v0.18.3 17 | ) 18 | -------------------------------------------------------------------------------- /tests/integration/integration_suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package integration_test 18 | 19 | import ( 20 | "os" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | ) 26 | 27 | var namespace = os.Getenv("NAMESPACE") 28 | 29 | func TestIntegration(t *testing.T) { 30 | RegisterFailHandler(Fail) 31 | RunSpecs(t, "Integration Suite") 32 | } 33 | -------------------------------------------------------------------------------- /tests/integration/resources/mariadb_client.tmpl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: mariadb-client 6 | spec: 7 | containers: 8 | - name: mariadb-client 9 | image: mariadb:{{ .DatabaseVersion }} 10 | imagePullPolicy: IfNotPresent 11 | command: {{ .Command | toJson }} 12 | env: 13 | - name: DATABASE_HOST 14 | valueFrom: 15 | secretKeyRef: 16 | name: {{ .SecretName }} 17 | key: host 18 | - name: DATABASE_PORT 19 | valueFrom: 20 | secretKeyRef: 21 | name: {{ .SecretName }} 22 | key: port 23 | - name: DATABASE_NAME 24 | valueFrom: 25 | secretKeyRef: 26 | name: {{ .SecretName }} 27 | key: database 28 | - name: DATABASE_USER 29 | valueFrom: 30 | secretKeyRef: 31 | name: {{ .SecretName }} 32 | key: username 33 | - name: DATABASE_PASSWORD 34 | valueFrom: 35 | secretKeyRef: 36 | name: {{ .SecretName }} 37 | key: password 38 | restartPolicy: Never 39 | -------------------------------------------------------------------------------- /tests/integration/resources/mongodb_client.tmpl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: mongodb-client 6 | spec: 7 | containers: 8 | - name: mongodb-client 9 | image: mongo:{{ .DatabaseVersion }} 10 | imagePullPolicy: IfNotPresent 11 | command: {{ .Command | toJson }} 12 | env: 13 | - name: DATABASE_URL 14 | valueFrom: 15 | secretKeyRef: 16 | name: {{ .SecretName }} 17 | key: uri 18 | restartPolicy: Never 19 | -------------------------------------------------------------------------------- /tests/integration/resources/mysql_client.tmpl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: mysql-client 6 | spec: 7 | containers: 8 | - name: mysql-client 9 | image: mysql:{{ .DatabaseVersion }} 10 | imagePullPolicy: IfNotPresent 11 | command: {{ .Command | toJson }} 12 | env: 13 | - name: DATABASE_HOST 14 | valueFrom: 15 | secretKeyRef: 16 | name: {{ .SecretName }} 17 | key: host 18 | - name: DATABASE_PORT 19 | valueFrom: 20 | secretKeyRef: 21 | name: {{ .SecretName }} 22 | key: port 23 | - name: DATABASE_USER 24 | valueFrom: 25 | secretKeyRef: 26 | name: {{ .SecretName }} 27 | key: username 28 | - name: DATABASE_PASSWORD 29 | valueFrom: 30 | secretKeyRef: 31 | name: {{ .SecretName }} 32 | key: password 33 | restartPolicy: Never 34 | -------------------------------------------------------------------------------- /tests/integration/resources/postgresql_client.tmpl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: postgresql-client 6 | spec: 7 | containers: 8 | - name: postgresql-client 9 | image: postgres:{{ .DatabaseVersion }} 10 | imagePullPolicy: IfNotPresent 11 | command: {{ .Command | toJson }} 12 | env: 13 | - name: PGHOST 14 | valueFrom: 15 | secretKeyRef: 16 | name: {{ .SecretName }} 17 | key: host 18 | - name: PGPORT 19 | valueFrom: 20 | secretKeyRef: 21 | name: {{ .SecretName }} 22 | key: port 23 | - name: PGUSER 24 | valueFrom: 25 | secretKeyRef: 26 | name: {{ .SecretName }} 27 | key: username 28 | - name: PGPASSWORD 29 | valueFrom: 30 | secretKeyRef: 31 | name: {{ .SecretName }} 32 | key: password 33 | restartPolicy: Never 34 | -------------------------------------------------------------------------------- /tests/integration/resources/rabbitmq_client.tmpl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: rabbitmq-client 6 | spec: 7 | containers: 8 | - name: rabbitmq-client 9 | image: toolbelt/amqp 10 | imagePullPolicy: IfNotPresent 11 | command: {{ .Command | toJson }} 12 | env: 13 | - name: DATABASE_URL 14 | valueFrom: 15 | secretKeyRef: 16 | name: {{ .SecretName }} 17 | key: uri 18 | restartPolicy: Never 19 | -------------------------------------------------------------------------------- /tests/integration/resources/redis_client.tmpl.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: redis-client 6 | spec: 7 | containers: 8 | - name: redis-client 9 | image: redis:{{ .DatabaseVersion }} 10 | imagePullPolicy: IfNotPresent 11 | command: {{ .Command | toJson }} 12 | env: 13 | - name: DATABASE_URL 14 | valueFrom: 15 | secretKeyRef: 16 | name: {{ .SecretName }} 17 | key: uri 18 | restartPolicy: Never 19 | -------------------------------------------------------------------------------- /tests/integration/testutil/helm.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testutil 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "io" 23 | "os/exec" 24 | "time" 25 | ) 26 | 27 | type Helm struct { 28 | namespace string 29 | } 30 | 31 | func NewHelm(ns string) Helm { 32 | return Helm{ 33 | namespace: ns, 34 | } 35 | } 36 | 37 | func (h Helm) Install(stdout, stderr io.Writer, name, chart string) error { 38 | cmd := exec.Command( 39 | "helm", "install", name, chart, 40 | "--wait", 41 | "--timeout", "15m", 42 | "--namespace", h.namespace, 43 | ) 44 | cmd.Stdout = stdout 45 | cmd.Stderr = stderr 46 | 47 | ctx, cancel := context.WithCancel(context.Background()) 48 | defer cancel() 49 | 50 | // Start a goroutine to print a waiting message every minute. It stops when 51 | // the 'cancel' function is called, which is after the Helm command exits 52 | // (whether it was successful or not). 53 | go func() { 54 | for i := 0; ; i++ { 55 | select { 56 | case <-ctx.Done(): 57 | return 58 | default: 59 | // Print every minute but still keep the check for readiness 60 | // every second. 61 | if i%60 == 0 { 62 | fmt.Printf("Waiting for %q to be ready...\n", name) 63 | } 64 | time.Sleep(time.Second) 65 | } 66 | } 67 | }() 68 | 69 | if err := cmd.Run(); err != nil { 70 | return fmt.Errorf("failed to install helm chart %q: %w", chart, err) 71 | } 72 | return nil 73 | } 74 | 75 | func (h Helm) Uninstall(stdout, stderr io.Writer, name string) error { 76 | cmd := exec.Command("helm", "delete", name, "--namespace", h.namespace) 77 | cmd.Stdout = stdout 78 | cmd.Stderr = stderr 79 | if err := cmd.Run(); err != nil { 80 | return fmt.Errorf("failed to uninstall helm release %q: %w", name, err) 81 | } 82 | return nil 83 | } 84 | -------------------------------------------------------------------------------- /tests/integration/testutil/testutil.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 The Kubernetes Authors. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package testutil 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | "io/ioutil" 23 | "log" 24 | "os" 25 | "text/template" 26 | "time" 27 | 28 | "github.com/Masterminds/sprig" 29 | servicecatalogv1beta1 "github.com/kubernetes-sigs/service-catalog/pkg/apis/servicecatalog/v1beta1" 30 | svcatclient "github.com/kubernetes-sigs/service-catalog/pkg/client/clientset_generated/clientset" 31 | "github.com/kubernetes-sigs/service-catalog/pkg/svcat" 32 | servicecatalog "github.com/kubernetes-sigs/service-catalog/pkg/svcat/service-catalog" 33 | "k8s.io/apimachinery/pkg/runtime" 34 | "k8s.io/client-go/kubernetes" 35 | "k8s.io/client-go/kubernetes/scheme" 36 | "k8s.io/client-go/rest" 37 | "k8s.io/client-go/tools/clientcmd" 38 | ) 39 | 40 | const defaultInterval = time.Second 41 | const defaultTimeout = time.Minute * 3 42 | 43 | // KubeClient creates a new Kubernetes client using the default kubeconfig. 44 | func KubeClient() (kubernetes.Interface, error) { 45 | config, err := restKubeConfig() 46 | if err != nil { 47 | return nil, fmt.Errorf("failed to initialize kubernetes client: %v", err) 48 | } 49 | 50 | clientset, err := kubernetes.NewForConfig(config) 51 | if err != nil { 52 | return nil, fmt.Errorf("failed to initialize kubernetes client: %v", err) 53 | } 54 | 55 | return clientset, nil 56 | } 57 | 58 | // svcatClient creates a new svcat client using the default kubeconfig. 59 | func svcatClient() (*svcatclient.Clientset, error) { 60 | config, err := restKubeConfig() 61 | if err != nil { 62 | return nil, fmt.Errorf("failed to initialize svcat client: %v", err) 63 | } 64 | 65 | clientset, err := svcatclient.NewForConfig(config) 66 | if err != nil { 67 | return nil, fmt.Errorf("failed to initialize svcat client: %v", err) 68 | } 69 | 70 | return clientset, nil 71 | } 72 | 73 | func restKubeConfig() (*rest.Config, error) { 74 | apiConfig, err := clientcmd.NewDefaultClientConfigLoadingRules().Load() 75 | if err != nil { 76 | return nil, fmt.Errorf("failed to initialize the rest config: %v", err) 77 | } 78 | 79 | overrides := clientcmd.ConfigOverrides{} 80 | config, err := clientcmd.NewDefaultClientConfig(*apiConfig, &overrides).ClientConfig() 81 | if err != nil { 82 | return nil, fmt.Errorf("failed to initialize the rest config: %v", err) 83 | } 84 | 85 | return config, nil 86 | } 87 | 88 | // Svcat wraps the svcat functionality for easier use with the integration tests. 89 | type Svcat struct { 90 | kubeClient kubernetes.Interface 91 | client *svcatclient.Clientset 92 | app *svcat.App 93 | } 94 | 95 | // NewSvcat constructs a new Svcat. 96 | func NewSvcat(kubeClient kubernetes.Interface, namespace string) (*Svcat, error) { 97 | client, err := svcatClient() 98 | if err != nil { 99 | return nil, fmt.Errorf("failed to create a new Svcat: %v", err) 100 | } 101 | app, err := svcat.NewApp(kubeClient, client, namespace) 102 | if err != nil { 103 | return nil, fmt.Errorf("failed to create a new Svcat: %v", err) 104 | } 105 | sc := &Svcat{ 106 | kubeClient: kubeClient, 107 | client: client, 108 | app: app, 109 | } 110 | return sc, nil 111 | } 112 | 113 | // WaitForBroker waits for the broker to be ready. 114 | func (sc *Svcat) WaitForBroker( 115 | name string, 116 | namespace string, 117 | timeout time.Duration, 118 | ) (servicecatalog.Broker, error) { 119 | opts := &servicecatalog.ScopeOptions{ 120 | Scope: servicecatalog.AllScope, 121 | Namespace: namespace, 122 | } 123 | broker, err := sc.app.WaitForBroker(name, opts, defaultInterval, &timeout) 124 | if err != nil { 125 | return nil, fmt.Errorf("failed to wait for broker: %v", err) 126 | } 127 | 128 | return broker, nil 129 | } 130 | 131 | // Provision asynchronously provisions an instance. 132 | func (sc *Svcat) Provision( 133 | namespace string, 134 | serviceName string, 135 | className string, 136 | planName string, 137 | params map[string]interface{}, 138 | ) (*servicecatalogv1beta1.ServiceInstance, error) { 139 | scopeOpts := servicecatalog.ScopeOptions{ 140 | Namespace: namespace, 141 | Scope: servicecatalog.AllScope, 142 | } 143 | 144 | class, err := sc.app.RetrieveClassByID(className, scopeOpts) 145 | if err != nil { 146 | return nil, fmt.Errorf("failed to provision instance: %v", err) 147 | } 148 | 149 | if class.IsClusterServiceClass() { 150 | scopeOpts.Scope = servicecatalog.ClusterScope 151 | } else { 152 | scopeOpts.Scope = servicecatalog.NamespaceScope 153 | } 154 | plan, err := sc.app.RetrievePlanByClassIDAndName(class.GetName(), planName, scopeOpts) 155 | if err != nil { 156 | return nil, fmt.Errorf("failed to provision instance: %v", err) 157 | } 158 | 159 | provisionOpts := &servicecatalog.ProvisionOptions{ 160 | Namespace: namespace, 161 | Params: params, 162 | } 163 | instance, err := sc.app.Provision(serviceName, class.GetName(), plan.GetName(), class.IsClusterServiceClass(), provisionOpts) 164 | if err != nil { 165 | return nil, fmt.Errorf("failed to provision instance: %v", err) 166 | } 167 | 168 | return instance, nil 169 | } 170 | 171 | // Deprovision asynchronously deprovisions an instance. 172 | func (sc *Svcat) Deprovision(instance *servicecatalogv1beta1.ServiceInstance) error { 173 | if err := sc.app.Deprovision(instance.Namespace, instance.Name); err != nil { 174 | return fmt.Errorf("failed to deprovision instance: %v", err) 175 | } 176 | return nil 177 | } 178 | 179 | // WaitProvisioning waits for an instance to be provisioned. 180 | func (sc *Svcat) WaitProvisioning(instance *servicecatalogv1beta1.ServiceInstance, timeout time.Duration) error { 181 | if _, err := sc.app.WaitForInstance(instance.Namespace, instance.Name, defaultInterval, &timeout); err != nil { 182 | return fmt.Errorf("failed to wait for instance to be provisioned: %v", err) 183 | } 184 | 185 | return nil 186 | } 187 | 188 | // Bind asynchronously binds an instance. 189 | func (sc *Svcat) Bind(instance *servicecatalogv1beta1.ServiceInstance) (*servicecatalogv1beta1.ServiceBinding, error) { 190 | binding, err := sc.app.Bind(instance.Namespace, "", "", instance.Name, "", nil, nil) 191 | if err != nil { 192 | return nil, fmt.Errorf("failed to bind instance: %v", err) 193 | } 194 | 195 | return binding, nil 196 | } 197 | 198 | // Unbind asynchronously unbinds an instance. 199 | func (sc *Svcat) Unbind(instance *servicecatalogv1beta1.ServiceInstance) error { 200 | if _, err := sc.app.Unbind(instance.Namespace, instance.Name); err != nil { 201 | return fmt.Errorf("failed to unbind instance: %v", err) 202 | } 203 | return nil 204 | } 205 | 206 | // WaitBinding waits for a service binding to be ready. 207 | func (sc *Svcat) WaitBinding(binding *servicecatalogv1beta1.ServiceBinding, timeout time.Duration) error { 208 | if _, err := sc.app.WaitForBinding(binding.Namespace, binding.Name, defaultInterval, &timeout); err != nil { 209 | return fmt.Errorf("failed to wait for service binding: %v", err) 210 | } 211 | 212 | return nil 213 | } 214 | 215 | // LoadKubeSpec loads and renders a Kubernetes object from a reader containing valid YAML or JSON 216 | // specs. The file can contain valid Go text/template code. 217 | func LoadKubeSpec(filepath string, values map[string]interface{}) (runtime.Object, error) { 218 | file, err := os.Open(filepath) 219 | if err != nil { 220 | return nil, fmt.Errorf("failed to load spec: %v", err) 221 | } 222 | defer file.Close() 223 | 224 | tmplData, err := ioutil.ReadAll(file) 225 | if err != nil { 226 | return nil, fmt.Errorf("failed to load spec: %v", err) 227 | } 228 | 229 | tmpl, err := template.New("").Funcs(sprig.TxtFuncMap()).Parse(string(tmplData)) 230 | if err != nil { 231 | return nil, fmt.Errorf("failed to load spec: %v", err) 232 | } 233 | 234 | var data bytes.Buffer 235 | if err := tmpl.Execute(&data, values); err != nil { 236 | return nil, fmt.Errorf("failed to load spec: %v", err) 237 | } 238 | 239 | obj, _, err := scheme.Codecs.UniversalDeserializer().Decode(data.Bytes(), nil, nil) 240 | if err != nil { 241 | return nil, fmt.Errorf("failed to load spec: %v", err) 242 | } 243 | 244 | return obj, nil 245 | } 246 | 247 | // MustTimeoutFromEnv parses the environment variable as a time.Duration, failing if it's not 248 | // parsable or returning the default timeout if the environment variable is not set. 249 | func MustTimeoutFromEnv(env string) time.Duration { 250 | if val := os.Getenv(env); val != "" { 251 | timeout, err := time.ParseDuration(val) 252 | if err != nil { 253 | log.Fatalf("failed to load timeout from environment variable %q: %v", env, err) 254 | } 255 | return timeout 256 | } 257 | return defaultTimeout 258 | } 259 | -------------------------------------------------------------------------------- /third-party/go.mod: -------------------------------------------------------------------------------- 1 | // A file to exclude the third-party directory from the top-level Go modules processing. 2 | --------------------------------------------------------------------------------