diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/.travis.yml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/.travis.yml index 8e56370c433c..2519acfd9901 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/.travis.yml +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/.travis.yml @@ -1,4 +1,11 @@ language: generic +env: + - GO_VERSION=1.9 + - GO_VERSION=rc +matrix: + allow_failures: + - env: GO_VERSION=rc + fast_finish: true sudo: required services: - docker @@ -9,10 +16,29 @@ stages: - test - name: deploy if: type != pull_request +before_install: + - | + if [[ -z "$TRAVIS_COMMIT_RANGE" ]]; then + # Builds triggered by initial commit of a new branch. + DOCS_ONLY=0 + else + DOCS_REGEX='(OWNERS|LICENSE)|(\.md$)|(^docs/)' + [[ -z "$(git diff --name-only $TRAVIS_COMMIT_RANGE | grep -vE $DOCS_REGEX)" ]] + DOCS_ONLY=$? + fi +# Test +script: + - | + if (( $DOCS_ONLY == 0 )); then + echo "Running verify-docs" + make verify-docs + else + echo "Running full build" + make verify build build-integration build-e2e test images svcat-all + fi jobs: include: - # Test - - script: make verify build build-integration build-e2e test images svcat + # Test is implicit from the build matrix # Deploy - stage: deploy deploy: diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.lock b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.lock index 125a0e54f7de..f21d14a36555 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.lock +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.lock @@ -539,7 +539,8 @@ "v2", "v2/fake" ] - revision = "31d8027f493f8f23f850415d171c7c52a972a6f2" + revision = "43fbcdcb3441d0563527208eb75062ae23280bd7" + version = "0.0.4" [[projects]] name = "github.com/prometheus/client_golang" @@ -1176,6 +1177,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "82091c32c985d8f8a15946af85f91e501fcc78424418667de9626396fd7ddc30" + inputs-digest = "5b9043b116c9435c042ac2f0719ecb99d589dc5c5cf400c15f24e9e66f651b44" solver-name = "gps-cdcl" solver-version = 1 diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.toml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.toml index b54a1475aa0f..f6d1ade97f8e 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.toml +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Gopkg.toml @@ -11,7 +11,7 @@ # Force dep to vendor the code generators, which aren't imported just used at dev time. -# Picking a subpackage with Go code won't be necessary once https://github.com/golang/dep/issues/1306 is implemented. +# Picking a subpackage with Go code won't be necessary once https://github.com/golang/dep/pull/1545 is merged. required = [ "github.com/jteeuwen/go-bindata/go-bindata", "k8s.io/code-generator/cmd/defaulter-gen", @@ -41,6 +41,10 @@ required = [ name = "github.com/golang/glog" revision = "44145f04b68cf362d9c4df2182967c2275eaefed" +[[constraint]] + name = "github.com/Azure/go-autorest" + version = "^9.1.0" + [[constraint]] name = "github.com/spf13/viper" version = "~1.0.0" @@ -65,6 +69,10 @@ required = [ name = "k8s.io/code-generator" version = "kubernetes-1.9.1" +[[constraint]] + name = "github.com/pmorie/go-open-service-broker-client" + version = "0.0.4" + [prune] non-go = true go-tests = true diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Makefile b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Makefile index 670bc56d3baf..4a50e886b3a5 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Makefile +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/Makefile @@ -32,7 +32,10 @@ SRC_DIRS = $(shell sh -c "find $(TOP_SRC_DIRS) -name \\*.go \ -exec dirname {} \\; | sort | uniq") TEST_DIRS ?= $(shell sh -c "find $(TOP_SRC_DIRS) -name \\*_test.go \ -exec dirname {} \\; | sort | uniq") +# Either the tag name, e.g. v1.2.3 or the commit hash for untagged commits, e.g. abc123 VERSION ?= $(shell git describe --always --abbrev=7 --dirty) +# Either the tag name, e.g. v1.2.3 or a combination of the closest tag combined with the commit hash, e.g. v1.2.3-2-gabc123 +TAG_VERSION ?= $(shell git describe --tags --abbrev=7 --dirty) BUILD_LDFLAGS = $(shell build/version.sh $(ROOT) $(SC_PKG)) GIT_BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD) @@ -57,10 +60,19 @@ TYPES_FILES = $(shell find pkg/apis -name types.go) GO_VERSION ?= 1.9 ALL_ARCH=amd64 arm arm64 ppc64le s390x +ALL_CLIENT_PLATFORM=darwin linux windows PLATFORM ?= linux +# This is the current platform, so that we can build a native client binary by default +CLIENT_PLATFORM?=$(shell uname -s | tr A-Z a-z) ARCH ?= amd64 +ifeq ($(PLATFORM),windows) +FILE_EXT=.exe +else +FILE_EXT= +endif + # TODO: Consider using busybox instead of debian BASEIMAGE?=gcr.io/google-containers/debian-base-$(ARCH):0.2 @@ -95,7 +107,7 @@ ifdef NO_DOCKER else # Mount .pkg as pkg so that we save our cached "go build" output files DOCKER_CMD = docker run --security-opt label:disable --rm -v $(PWD):/go/src/$(SC_PKG) \ - -v $(PWD)/.pkg:/go/pkg scbuildimage + -v $(PWD)/.pkg:/go/pkg --env AZURE_STORAGE_CONNECTION_STRING scbuildimage scBuildImageTarget = .scBuildImage endif @@ -176,8 +188,8 @@ $(BINDIR)/e2e.test: .init # Util targets ############## -.PHONY: verify verify-generated verify-client-gen -verify: .init .generate_files verify-generated verify-client-gen verify-vendor +.PHONY: verify verify-generated verify-client-gen verify-docs +verify: .init .generate_files verify-generated verify-client-gen verify-docs verify-vendor @echo Running gofmt: @$(DOCKER_CMD) gofmt -l -s $(TOP_TEST_DIRS) $(TOP_SRC_DIRS)>.out 2>&1||true @[ ! -s .out ] || \ @@ -204,13 +216,15 @@ verify: .init .generate_files verify-generated verify-client-gen verify-vendor @[ ! -s .out ] || (cat .out && rm .out && false) @rm .out @# - @echo Running href checker$(SKIP_COMMENT): - @$(DOCKER_CMD) verify-links.sh -s .pkg -t $(SKIP_HTTP) . @echo Running errexit checker: @$(DOCKER_CMD) build/verify-errexit.sh @echo Running tag verification: @$(DOCKER_CMD) build/verify-tags.sh +verify-docs: .init + @echo Running href checker$(SKIP_COMMENT): + @$(DOCKER_CMD) verify-links.sh -s .pkg -t $(SKIP_HTTP) . + verify-generated: .init .generate_files $(DOCKER_CMD) $(BUILD_DIR)/update-apiserver-gen.sh --verify-only @@ -255,8 +269,8 @@ test-integration: .init $(scBuildImageTarget) build build-integration # golang integration tests $(DOCKER_CMD) test/integration.sh $(INT_TEST_FLAGS) -clean-e2e: - rm -f $(BINDIR)/e2e.test +clean-e2e: .init $(scBuildImageTarget) + $(DOCKER_CMD) rm -f $(BINDIR)/e2e.test build-e2e: .generate_files $(BINDIR)/e2e.test @@ -265,12 +279,12 @@ test-e2e: build-e2e clean: clean-bin clean-build-image clean-generated clean-coverage -clean-bin: - rm -rf $(BINDIR) +clean-bin: .init $(scBuildImageTarget) + $(DOCKER_CMD) rm -rf $(BINDIR) rm -f .generate_exes -clean-build-image: - rm -rf .pkg +clean-build-image: .init $(scBuildImageTarget) + $(DOCKER_CMD) rm -rf .pkg rm -f .scBuildImage docker rmi -f scbuildimage > /dev/null 2>&1 || true @@ -289,11 +303,13 @@ clean-generated: git checkout -- pkg/openapi/openapi_generated.go # purge-generated removes generated files from the filesystem. -purge-generated: - find $(TOP_SRC_DIRS) -name zz_generated* -exec rm {} \; - find $(TOP_SRC_DIRS) -type d -name *_generated -exec rm -rf {} \; - rm -f pkg/openapi/openapi_generated.go +purge-generated: .init $(scBuildImageTarget) + find $(TOP_SRC_DIRS) -name zz_generated* -exec $(DOCKER_CMD) rm {} \; + find $(TOP_SRC_DIRS) -depth -type d -name *_generated \ + -exec $(DOCKER_CMD) rm -rf {} \; + $(DOCKER_CMD) rm -f pkg/openapi/openapi_generated.go echo 'package v1beta1' > pkg/apis/servicecatalog/v1beta1/types.generated.go + rm -f .generate_files clean-coverage: rm -f $(COVERAGE) @@ -363,13 +379,30 @@ release-push-%: $(MAKE) ARCH=$* build $(MAKE) ARCH=$* push -# SvCat Kubectl plugin stuff +# svcat kubectl plugin ############################ -.PHONY: $(BINDIR)/svcat -svcat: $(BINDIR)/svcat -$(BINDIR)/svcat: .init .generate_files cmd/svcat/main.go +.PHONY: $(BINDIR)/svcat/$(TAG_VERSION)/$(PLATFORM)/$(ARCH)/svcat$(FILE_EXT) +svcat: + # Compile a native binary for local dev/test + $(MAKE) svcat-for-$(CLIENT_PLATFORM) + cp $(BINDIR)/svcat/$(TAG_VERSION)/$(CLIENT_PLATFORM)/$(ARCH)/svcat$(FILE_EXT) $(BINDIR)/svcat/ + +svcat-all: $(addprefix svcat-for-,$(ALL_CLIENT_PLATFORM)) + +svcat-for-%: + $(MAKE) PLATFORM=$* VERSION=$(TAG_VERSION) svcat-xbuild + +svcat-xbuild: $(BINDIR)/svcat/$(TAG_VERSION)/$(PLATFORM)/$(ARCH)/svcat$(FILE_EXT) +$(BINDIR)/svcat/$(TAG_VERSION)/$(PLATFORM)/$(ARCH)/svcat$(FILE_EXT): .init .generate_files $(DOCKER_CMD) $(GO_BUILD) -o $@ $(SC_PKG)/cmd/svcat +svcat-publish: clean-bin svcat-all + # Download the latest client with https://download.svcat.sh/cli/latest/darwin/amd64/svcat + # Download an older client with https://download.svcat.sh/cli/VERSION/darwin/amd64/svcat + cp -R $(BINDIR)/svcat/$(TAG_VERSION) $(BINDIR)/svcat/$(MUTABLE_TAG) + # AZURE_STORAGE_CONNECTION_STRING will be used for auth in the following command + $(DOCKER_CMD) az storage blob upload-batch -d cli -s $(BINDIR)/svcat + # Dependency management via dep (https://golang.github.io/dep) .PHONY: verify-vendor test-dep verify-vendor: .init diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/build/build-image/Dockerfile b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/build/build-image/Dockerfile index 0fb0018c29bf..a01cb01acf24 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/build/build-image/Dockerfile +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/build/build-image/Dockerfile @@ -30,6 +30,12 @@ RUN go get -u github.com/golang/lint/golint RUN git clone https://github.com/duglin/vlinker.git /vlinker ENV PATH=$PATH:/vlinker/bin +# Install the azure client, used to publish svcat binaries +ENV AZCLI_VERSION=2.0.25 +RUN apt-get update && apt-get install -y python-pip && \ + rm -rf /var/lib/apt/lists/* +RUN pip install --disable-pip-version-check --no-cache-dir --upgrade cryptography azure-cli==${AZCLI_VERSION} + # Create the full dir tree that we'll mount our src into when we run the image RUN mkdir -p /go/src/github.com/kubernetes-incubator/service-catalog diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/Chart.yaml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/Chart.yaml index 0379d10c574b..0b5008c75f17 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/Chart.yaml +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/Chart.yaml @@ -1,3 +1,3 @@ name: catalog description: service-catalog API server and controller-manager helm chart -version: 0.1.8 +version: 0.1.9 diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/README.md b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/README.md index 27e75f696518..1713a81a7232 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/README.md +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/README.md @@ -40,7 +40,7 @@ chart and their default values. | Parameter | Description | Default | |-----------|-------------|---------| -| `image` | apiserver image to use | `quay.io/kubernetes-service-catalog/service-catalog:v0.1.8` | +| `image` | apiserver image to use | `quay.io/kubernetes-service-catalog/service-catalog:v0.1.9` | | `imagePullPolicy` | `imagePullPolicy` for the service catalog | `Always` | | `apiserver.tls.requestHeaderCA` | Base64-encoded CA used to validate request-header authentication, when receiving delegated authentication from an aggregator. If not set, the service catalog API server will inherit this CA from the `extension-apiserver-authentication` ConfigMap if available. | `nil` | | `apiserver.service.type` | Type of service; valid values are `LoadBalancer` and `NodePort` | `NodePort` | @@ -48,6 +48,10 @@ chart and their default values. | `apiserver.storage.type` | The storage backend to use; the only valid value is `etcd`, left for other storages support in future, e.g. `crd` | `etcd` | | `apiserver.storage.etcd.useEmbedded` | If storage type is `etcd`: Whether to embed an etcd container in the apiserver pod; THIS IS INADEQUATE FOR PRODUCTION USE! | `true` | | `apiserver.storage.etcd.servers` | If storage type is `etcd`: etcd URL(s); override this if NOT using embedded etcd | `http://localhost:2379` | +| `apiserver.storage.etcd.persistence.enabled` | Enable persistence using PVC | `false` | +| `apiserver.storage.etcd.persistence.storageClass` | PVC Storage Class | `nil` (uses alpha storage class annotation) | +| `apiserver.storage.etcd.persistence.accessMode` | PVC Access Mode | `ReadWriteOnce` | +| `apiserver.storage.etcd.persistence.size` | PVC Storage Request | `4Gi` | | `apiserver.verbosity` | Log level; valid values are in the range 0 - 10 | `10` | | `apiserver.auth.enabled` | Enable authentication and authorization | `true` | | `controllerManager.verbosity` | Log level; valid values are in the range 0 - 10 | `10` | diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/templates/apiserver-deployment.yaml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/templates/apiserver-deployment.yaml index 290472b74902..29ac303eb4ac 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/templates/apiserver-deployment.yaml +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/templates/apiserver-deployment.yaml @@ -147,7 +147,12 @@ spec: - key: requestheader-ca.crt path: requestheader-ca.crt {{- end }} - {{- if eq .Values.apiserver.storage.type "etcd" }} + {{- if and (eq .Values.apiserver.storage.type "etcd") .Values.apiserver.storage.etcd.useEmbedded }} - name: etcd-data-dir + {{- if .Values.apiserver.storage.etcd.persistence.enabled }} + persistentVolumeClaim: + claimName: {{ .Values.apiserver.storage.etcd.persistence.existingClaim | default (printf "%s-%s" (include "fullname" .) "etcd") }} + {{- else }} emptyDir: {} {{- end }} + {{- end }} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/templates/etcd-pvc.yaml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/templates/etcd-pvc.yaml new file mode 100644 index 000000000000..8d6a8c74c47c --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/templates/etcd-pvc.yaml @@ -0,0 +1,24 @@ +{{- if and (eq .Values.apiserver.storage.type "etcd") .Values.apiserver.storage.etcd.useEmbedded .Values.apiserver.storage.etcd.persistence.enabled (not .Values.apiserver.storage.etcd.persistence.existingClaim) }} +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ template "fullname" . }}-etcd + labels: + app: {{ template "fullname" . }}-etcd + chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + release: "{{ .Release.Name }}" + heritage: "{{ .Release.Service }}" +spec: + accessModes: + - {{ .Values.apiserver.storage.etcd.persistence.accessMode | quote }} + resources: + requests: + storage: {{ .Values.apiserver.storage.etcd.persistence.size | quote }} +{{- if .Values.apiserver.storage.etcd.persistence.storageClass }} +{{- if (eq "-" .Values.apiserver.storage.etcd.persistence.storageClass) }} + storageClassName: "" +{{- else }} + storageClassName: "{{ .Values.apiserver.storage.etcd.persistence.storageClass }}" +{{- end }} +{{- end }} +{{- end }} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/values.yaml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/values.yaml index 101327014323..98f38052f74a 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/values.yaml +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/catalog/values.yaml @@ -1,6 +1,6 @@ # Default values for Service Catalog # service-catalog image to use -image: quay.io/kubernetes-service-catalog/service-catalog:v0.1.8 +image: quay.io/kubernetes-service-catalog/service-catalog:v0.1.9 # imagePullPolicy for the service-catalog; valid values are "IfNotPresent", # "Never", and "Always" imagePullPolicy: Always @@ -56,6 +56,18 @@ apiserver: useEmbedded: true # etcd URL(s); override this if NOT using embedded etcd servers: http://localhost:2379 + # etcd persistence options IF using embedded etcd + persistence: + enabled: false + ## If defined, storageClassName: + ## If set to "-", storageClassName: "", which disables dynamic provisioning + ## If undefined (the default) or set to null, no storageClassName spec is + ## set, choosing the default provisioner. (gp2 on AWS, standard on + ## GKE, AWS & OpenStack) + ## + # storageClass: "-" + accessMode: ReadWriteOnce + size: 4Gi # Log level; valid values are in the range 0 - 10 verbosity: 10 auth: diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/README.md b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/README.md index c89bb0a3dfee..087c4118d5f4 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/README.md +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/README.md @@ -34,7 +34,7 @@ Service Broker | Parameter | Description | Default | |-----------|-------------|---------| -| `image` | Image to use | `quay.io/kubernetes-service-catalog/user-broker:v0.1.8` | +| `image` | Image to use | `quay.io/kubernetes-service-catalog/user-broker:v0.1.9` | | `imagePullPolicy` | `imagePullPolicy` for the ups-broker | `Always` | Specify each parameter using the `--set key=value[,key=value]` argument to diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/values.yaml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/values.yaml index 04406ac32a10..b559ad2b46c9 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/values.yaml +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/charts/ups-broker/values.yaml @@ -1,6 +1,6 @@ # Default values for User-Provided Service Broker # Image to use -image: quay.io/kubernetes-service-catalog/user-broker:v0.1.8 +image: quay.io/kubernetes-service-catalog/user-broker:v0.1.9 # ImagePullPolicy; valid values are "IfNotPresent", "Never", and "Always" imagePullPolicy: Always # Certificate details to use for TLS. Leave blank to not use TLS diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/README.md b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/README.md index 674bec4f4ac8..b1c7a7167109 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/README.md +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/README.md @@ -24,18 +24,26 @@ In order to use svcat, you will need: Follow the appropriate instructions for your shell to download svcat. The binary can be used by itself, or as kubectl plugin. -## Bash +## MacOS ``` -curl -sLO https://servicecatalogcli.blob.core.windows.net/cli/latest/$(uname -s)/$(uname -m)/svcat +curl -sLO https://download.svcat.sh/cli/latest/darwin/amd64/svcat chmod +x ./svcat mv ./svcat /usr/local/bin/ svcat --version ``` -## PowerShell +## Linux +``` +curl -sLO https://download.svcat.sh/cli/latest/linux/amd64/svcat +chmod +x ./svcat +mv ./svcat /usr/local/bin/ +svcat --version +``` + +## Windows ``` -iwr 'https://servicecatalogcli.blob.core.windows.net/cli/latest/Windows/x86_64/svcat.exe' -UseBasicParsing -OutFile svcat.exe +iwr 'https://download.svcat.sh/cli/latest/windows/amd64/svcat.exe' -UseBasicParsing -OutFile svcat.exe mkdir -f ~\bin $env:PATH += ";${pwd}\bin" svcat --version @@ -46,9 +54,9 @@ You will need to find a permanent location for it and add it to your PATH. ## Manual 1. Download the appropriate binary for your operating system: - * macOS: https://servicecatalogcli.blob.core.windows.net/cli/latest/Darwin/x86_64/svcat - * Windows: https://servicecatalogcli.blob.core.windows.net/cli/latest/Windows/x86_64/svcat.exe - * Linux: https://servicecatalogcli.blob.core.windows.net/cli/latest/Linux/x86_64/svcat + * macOS: https://download.svcat.sh/cli/latest/darwin/amd64/svcat + * Windows: https://download.svcat.sh/cli/latest/windows/amd64/svcat.exe + * Linux: https://download.svcat.sh/cli/latest/linux/amd64/svcat 1. Make the binary executable. 1. Move the binary to a directory on your PATH. diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/main.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/main.go index 0aecd2888e66..a70daac33404 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/main.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/main.go @@ -27,6 +27,7 @@ import ( "github.com/kubernetes-incubator/service-catalog/cmd/svcat/instance" "github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan" "github.com/kubernetes-incubator/service-catalog/cmd/svcat/plugin" + "github.com/kubernetes-incubator/service-catalog/pkg" "github.com/kubernetes-incubator/service-catalog/pkg/svcat" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -108,11 +109,7 @@ func buildRootCommand() *cobra.Command { } func printVersion(cxt *command.Context) { - if commit == "" { // commit is empty for Homebrew builds - fmt.Fprintf(cxt.Output, "svcat %s\n", version) - } else { - fmt.Fprintf(cxt.Output, "svcat %s (%s)\n", version, commit) - } + fmt.Fprintf(cxt.Output, "svcat %s\n", pkg.VERSION) } func newSyncCmd(cxt *command.Context) *cobra.Command { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/output/plan.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/output/plan.go index c9a19c57c70e..34dcc7e0541c 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/output/plan.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/output/plan.go @@ -120,3 +120,25 @@ func WritePlanDetails(w io.Writer, plan *v1beta1.ClusterServicePlan, class *v1be t.Render() } + +// WritePlanSchemas prints the schemas for a single plan. +func WritePlanSchemas(w io.Writer, plan *v1beta1.ClusterServicePlan) { + instanceCreateSchema := plan.Spec.ServiceInstanceCreateParameterSchema + instanceUpdateSchema := plan.Spec.ServiceInstanceUpdateParameterSchema + bindingCreateSchema := plan.Spec.ServiceBindingCreateParameterSchema + + if instanceCreateSchema != nil { + fmt.Fprintln(w, "\nInstance Create Parameter Schema:") + writeYAML(w, instanceCreateSchema, 2) + } + + if instanceUpdateSchema != nil { + fmt.Fprintln(w, "\nInstance Update Parameter Schema:") + writeYAML(w, instanceUpdateSchema, 2) + } + + if bindingCreateSchema != nil { + fmt.Fprintln(w, "\nBinding Create Parameter Schema:") + writeYAML(w, bindingCreateSchema, 2) + } +} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/output/yaml.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/output/yaml.go new file mode 100644 index 000000000000..acf4c365e897 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/output/yaml.go @@ -0,0 +1,43 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package output + +import ( + "fmt" + "io" + "strings" + + "github.com/ghodss/yaml" +) + +// writeYAML writes the given obj to the given Writer in YAML format, indented +// n spaces +func writeYAML(w io.Writer, obj interface{}, n int) { + yBytes, err := yaml.Marshal(obj) + if err != nil { + fmt.Fprintf(w, "err marshaling yaml: %v\n", err) + return + } + y := string(yBytes) + if n > 0 { + indent := strings.Repeat(" ", n) + y = indent + strings.Replace(y, "\n", "\n"+indent, -1) + y = strings.TrimRight(y, " ") + } + + fmt.Fprint(w, y) +} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/describe_cmd.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/describe_cmd.go index 7ca881ab1222..5d7100aed8e5 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/describe_cmd.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/describe_cmd.go @@ -18,6 +18,7 @@ package plan import ( "fmt" + "strings" "github.com/kubernetes-incubator/service-catalog/cmd/svcat/command" "github.com/kubernetes-incubator/service-catalog/cmd/svcat/output" @@ -29,6 +30,7 @@ type describeCmd struct { *command.Context traverse bool lookupByUUID bool + showSchemas bool uuid string name string } @@ -61,6 +63,13 @@ func NewDescribeCmd(cxt *command.Context) *cobra.Command { false, "Whether or not to get the class by UUID (the default is by name)", ) + cmd.Flags().BoolVarP( + &describeCmd.showSchemas, + "show-schemas", + "", + true, + "Whether or not to show instance and binding parameter schemas", + ) return cmd } @@ -87,6 +96,12 @@ func (c *describeCmd) describe() error { var err error if c.lookupByUUID { plan, err = c.App.RetrievePlanByID(c.uuid) + } else if strings.Contains(c.name, "/") { + names := strings.Split(c.name, "/") + if len(names) != 2 { + return fmt.Errorf("failed to parse class/plan name combination '%s'", c.name) + } + plan, err = c.App.RetrievePlanByClassAndPlanNames(names[0], names[1]) } else { plan, err = c.App.RetrievePlanByName(c.name) } @@ -117,5 +132,9 @@ func (c *describeCmd) describe() error { output.WriteParentBroker(c.Output, broker) } + if c.showSchemas { + output.WritePlanSchemas(c.Output, plan) + } + return nil } diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/get_cmd.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/get_cmd.go index 2a78994b647c..7a0c81dce935 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/get_cmd.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/plan/get_cmd.go @@ -18,6 +18,7 @@ package plan import ( "fmt" + "strings" "github.com/kubernetes-incubator/service-catalog/cmd/svcat/command" "github.com/kubernetes-incubator/service-catalog/cmd/svcat/output" @@ -98,6 +99,12 @@ func (c *getCmd) get() error { var err error if c.lookupByUUID { plan, err = c.App.RetrievePlanByID(c.uuid) + } else if strings.Contains(c.name, "/") { + names := strings.Split(c.name, "/") + if len(names) != 2 { + return fmt.Errorf("failed to parse class/plan name combination '%s'", c.name) + } + plan, err = c.App.RetrievePlanByClassAndPlanNames(names[0], names[1]) } else { plan, err = c.App.RetrievePlanByName(c.name) } diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/svcat_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/svcat_test.go index 571990d97b9d..d899028fe377 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/svcat_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/svcat_test.go @@ -84,8 +84,12 @@ func TestCommandOutput(t *testing.T) { {name: "list all plans", cmd: "get plans", golden: "output/get-plans.txt"}, {name: "get plan by name", cmd: "get plan default", golden: "output/get-plan.txt"}, {name: "get plan by uuid", cmd: "get plan --uuid 86064792-7ea2-467b-af93-ac9694d96d52", golden: "output/get-plan.txt"}, + {name: "get plan by class/plan name combo", cmd: "get plan user-provided-service/default", golden: "output/get-plan.txt"}, {name: "describe plan by name", cmd: "describe plan default", golden: "output/describe-plan.txt"}, {name: "describe plan by uuid", cmd: "describe plan --uuid 86064792-7ea2-467b-af93-ac9694d96d52", golden: "output/describe-plan.txt"}, + {name: "describe plan by class/plan name combo", cmd: "describe plan user-provided-service/default", golden: "output/describe-plan.txt"}, + {name: "describe plan with schemas", cmd: "describe plan premium", golden: "output/describe-plan-with-schemas.txt"}, + {name: "describe plan without schemas", cmd: "describe plan premium --show-schemas=false", golden: "output/describe-plan-without-schemas.txt"}, {name: "list all instances", cmd: "get instances -n test-ns", golden: "output/get-instances.txt"}, {name: "get instance", cmd: "get instance ups-instance -n test-ns", golden: "output/get-instance.txt"}, diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/output/describe-plan-with-schemas.txt b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/output/describe-plan-with-schemas.txt new file mode 100644 index 000000000000..4b2caf1d7b0d --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/output/describe-plan-with-schemas.txt @@ -0,0 +1,27 @@ + Name: premium + Description: Premium plan + UUID: cc0d7529-18e8-416d-8946-6f7456acd589 + Status: Active + Free: false + Class: user-provided-service + +Instances: +No instances defined + +Instance Create Parameter Schema: + properties: + testInstanceProperty: + description: A test instance property. + type: string + required: + - testInstanceProperty + type: object + +Binding Create Parameter Schema: + properties: + testBindingProperty: + description: A test binding property. + type: string + required: + - testBindingProperty + type: object diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/output/describe-plan-without-schemas.txt b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/output/describe-plan-without-schemas.txt new file mode 100644 index 000000000000..cee74b483935 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/output/describe-plan-without-schemas.txt @@ -0,0 +1,9 @@ + Name: premium + Description: Premium plan + UUID: cc0d7529-18e8-416d-8946-6f7456acd589 + Status: Active + Free: false + Class: user-provided-service + +Instances: +No instances defined diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/plugin.yaml b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/plugin.yaml index 2db5dcc51d16..592b840ab7dd 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/plugin.yaml +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/plugin.yaml @@ -58,6 +58,8 @@ tree: shortDesc: Show details of a specific plan command: ./svcat describe plan flags: + - name: show-schemas + desc: Whether or not to show instance and binding parameter schemas - name: traverse shorthand: t desc: Whether or not to traverse from plan -> class -> broker diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans.json b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans.json index bae430c575d4..f05c7dfa1964 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans.json +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans.json @@ -44,7 +44,31 @@ "free": false, "clusterServiceClassRef": { "name": "4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468" - } + }, + "instanceCreateParameterSchema": { + "properties": { + "testInstanceProperty": { + "description": "A test instance property.", + "type": "string" + } + }, + "required": [ + "testInstanceProperty" + ], + "type": "object" + }, + "serviceBindingCreateParameterSchema": { + "properties": { + "testBindingProperty": { + "description": "A test binding property.", + "type": "string" + } + }, + "required": [ + "testBindingProperty" + ], + "type": "object" + } }, "status": { "removedFromBrokerCatalog": false diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.clusterServiceClassRef.name=4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468,spec.externalName=default.json b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.clusterServiceClassRef.name=4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468,spec.externalName=default.json new file mode 100644 index 000000000000..ef3cc0076be5 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.clusterServiceClassRef.name=4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468,spec.externalName=default.json @@ -0,0 +1,32 @@ +{ + "kind": "ClusterServicePlanList", + "apiVersion": "servicecatalog.k8s.io/v1beta1", + "metadata": { + "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceplans", + "resourceVersion": "114" + }, + "items": [ + { + "metadata": { + "name": "86064792-7ea2-467b-af93-ac9694d96d52", + "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/clusterserviceplans/86064792-7ea2-467b-af93-ac9694d96d52", + "uid": "7b3d0190-f711-11e7-aa44-0242ac110005", + "resourceVersion": "4", + "creationTimestamp": "2018-01-11T20:53:31Z" + }, + "spec": { + "clusterServiceBrokerName": "ups-broker", + "externalName": "default", + "externalID": "86064792-7ea2-467b-af93-ac9694d96d52", + "description": "Sample plan description", + "free": true, + "clusterServiceClassRef": { + "name": "4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468" + } + }, + "status": { + "removedFromBrokerCatalog": false + } + } + ] +} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.clusterServiceClassRef.name=4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468.json b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.clusterServiceClassRef.name=4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468.json index 5ae270a47221..a892e22d9e58 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.clusterServiceClassRef.name=4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468.json +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.clusterServiceClassRef.name=4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468.json @@ -44,7 +44,31 @@ "free": false, "clusterServiceClassRef": { "name": "4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468" - } + }, + "instanceCreateParameterSchema": { + "properties": { + "testInstanceProperty": { + "description": "A test instance property.", + "type": "string" + } + }, + "required": [ + "testInstanceProperty" + ], + "type": "object" + }, + "serviceBindingCreateParameterSchema": { + "properties": { + "testBindingProperty": { + "description": "A test binding property.", + "type": "string" + } + }, + "required": [ + "testBindingProperty" + ], + "type": "object" + } }, "status": { "removedFromBrokerCatalog": false diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.externalName=premium.json b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.externalName=premium.json index de96ac9aed1e..2ab8bda92a3f 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.externalName=premium.json +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/clusterserviceplans?fieldSelector=spec.externalName=premium.json @@ -22,7 +22,31 @@ "free": false, "clusterServiceClassRef": { "name": "4f6e6cf6-ffdd-425f-a2c7-3c9258ad2468" - } + }, + "instanceCreateParameterSchema": { + "properties": { + "testInstanceProperty": { + "description": "A test instance property.", + "type": "string" + } + }, + "required": [ + "testInstanceProperty" + ], + "type": "object" + }, + "serviceBindingCreateParameterSchema": { + "properties": { + "testBindingProperty": { + "description": "A test binding property.", + "type": "string" + } + }, + "required": [ + "testBindingProperty" + ], + "type": "object" + } }, "status": { "removedFromBrokerCatalog": false diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/serviceinstances?fieldSelector=spec.clusterServicePlanRef.name=cc0d7529-18e8-416d-8946-6f7456acd589.json b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/serviceinstances?fieldSelector=spec.clusterServicePlanRef.name=cc0d7529-18e8-416d-8946-6f7456acd589.json new file mode 100644 index 000000000000..2cf2e02eab73 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/cmd/svcat/testdata/responses/serviceinstances?fieldSelector=spec.clusterServicePlanRef.name=cc0d7529-18e8-416d-8946-6f7456acd589.json @@ -0,0 +1,9 @@ +{ + "kind": "ServiceInstanceList", + "apiVersion": "servicecatalog.k8s.io/v1beta1", + "metadata": { + "selfLink": "/apis/servicecatalog.k8s.io/v1beta1/serviceinstances", + "resourceVersion": "109" + }, + "items": [] +} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/hack/start-server.sh b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/hack/start-server.sh index 49f9d91f08f0..095e28ffaad1 100755 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/hack/start-server.sh +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/hack/start-server.sh @@ -52,12 +52,12 @@ count=0 D_HOST=${DOCKER_HOST:-localhost} D_HOST=${D_HOST#*//} # remove leading proto:// D_HOST=${D_HOST%:*} # remove trailing port # -while ! curl --cacert ${ROOT}/.var/run/kubernetes-service-catalog/apiserver.crt https://${D_HOST}:${PORT} > /dev/null 2>&1 ; do +while ! wget --ca-certificate ${ROOT}/.var/run/kubernetes-service-catalog/apiserver.crt https://${D_HOST}:${PORT} > /dev/null 2>&1 ; do sleep 1 (( count++ )) || true if [ "${count}" == "30" ]; then echo "Timed-out waiting for API Server" - (set -x ; curl --cacert ${ROOT}/.var/run/kubernetes-service-catalog/apiserver.crt https://${D_HOST}:${PORT}) + (set -x ; wget --ca-certificate ${ROOT}/.var/run/kubernetes-service-catalog/apiserver.crt https://${D_HOST}:${PORT}) (set -x ; docker ps) (set -x ; docker logs apiserver) exit 1 diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/pkg/brokerapi/openservicebroker/open_service_broker_client.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/pkg/brokerapi/openservicebroker/open_service_broker_client.go index 7c59453ba0fb..b5c74a6714a5 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/pkg/brokerapi/openservicebroker/open_service_broker_client.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/pkg/brokerapi/openservicebroker/open_service_broker_client.go @@ -118,7 +118,6 @@ func (c *openServiceBrokerClient) GetCatalog() (*brokerapi.Catalog, error) { return nil, err } - req.SetBasicAuth(c.username, c.password) resp, err := c.Do(req) if err != nil { glog.Errorf("Failed to fetch catalog %q from %s: response: %v error: %#v", c.name, catalogURL, resp, err) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/travis/deploy.sh b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/travis/deploy.sh index c3729c8420dd..48590950959a 100755 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/travis/deploy.sh +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/contrib/travis/deploy.sh @@ -24,7 +24,7 @@ docker login -u "${QUAY_USERNAME}" -p "${QUAY_PASSWORD}" quay.io if [[ "${TRAVIS_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+[a-z]*(-(r|R)(c|C)[0-9]+)*$ ]]; then echo "Pushing images with tags '${TRAVIS_TAG}' and 'latest'." - VERSION="${TRAVIS_TAG}" MUTABLE_TAG="latest" make release-push + VERSION="${TRAVIS_TAG}" MUTABLE_TAG="latest" make release-push svcat-publish elif [[ "${TRAVIS_BRANCH}" == "master" ]]; then echo "Pushing images with default tags (git sha and 'canary')." make push diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/docs/devguide.md b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/docs/devguide.md index 54e834cf0925..3ec54ea4c565 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/docs/devguide.md +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/docs/devguide.md @@ -8,6 +8,7 @@ Table of Contents - [Building](#building) - [Testing](#testing) - [Advanced Build Steps](#advanced-build-steps) +- [Dependency Management](#dependency-management) - [Deploying to Kubernetes](#deploying-to-kubernetes) - [Demo walkthrough](#demo-walkthrough) @@ -146,7 +147,7 @@ git remote -v ## Building First `cd` to the root of the cloned repository tree. -To build the service-catalog: +To build the service-catalog server components: $ make build @@ -155,6 +156,10 @@ is done within a Docker container-- meaning you do not need to have all of the necessary tooling installed on your host (such as a golang compiler or dep). Building outside the container is possible, but not officially supported. +To build the service-catalog client, `svcat`: + + $ make svcat + Note, this will do the basic build of the service catalog. There are more more [advanced build steps](#advanced-build-steps) below as well. @@ -262,6 +267,29 @@ The images are tagged with the current Git commit SHA: $ docker images +### svcat targets +These are targets for the service-catalog client, `svcat`: + +* `make svcat-all` builds all supported client platforms (darwin, linux, windows). +* `make svcat-for-X` builds a specific platform. +* `make svcat` builds for the current dev's platform. +* `make svcat-publish` compiles everything and uploads the binaries. + +The same tags are used for both client and server. The cli uses the format that +always includes a tag, so that it's clear which release you are "closest" to, +e.g. v1.2.3 for official releases and v1.2.3-2-gabc123 for untagged commits. + +### Deploying Releases + +* Merge to master - A docker image for the server is pushed to [quay.io/kubernetes-service-catalog/service-catalog](http://quay.io/kubernetes-service-catalog/service-catalog), + tagged with the abbreviated commit hash. Nothing is deployed for the client, `svcat`. +* Tag a commit on master with vX.Y.Z - A docker image for the server is pushed, + tagged with the version, e.g. vX.Y.Z. The client binaries are published to + https://download.svcat.sh/cli/latest/OS/ARCH/svcat and https://download.svcat.sh/cli/VERSION/OS/ARCH/svcat. + +The idea behind "latest" link is that we can provide a permanent link to the most recent stable release of `svcat`. +If someone wants to install a unreleased version, they must build it locally. + ---- ## Deploying to Kubernetes @@ -297,6 +325,41 @@ If you choose third party resources storage, the helm chart will not launch an etcd server, but will instead instruct the API server to store all resources in the Kubernetes cluster as third party resources. +## Dependency Management +We use [dep](https://golang.github.io/dep) to manage our dependencies. We commit the resulting +vendor directory to ensure repeatable builds and isolation from upstream source disruptions. +Because vendor is committed, you do not need to interact with dep unless you are +changing dependencies. + +* Gopkg.toml - the dep manifest, this is intended to be hand-edited and contains a set of +constraints and other rules for dep to apply when selecting appropriate versions of dependencies. +* Gopkg.lock - the dep lockfile, do not edit because it is a generated file. +* vendor/ - the source of all of our dependencies. Commit changes to this directory in a +separate commit from any other changes (including to the Gopkg files) so that it's easier to +review your pull request. + +If you use VS Code, we recommend installing the [dep extension](https://marketplace.visualstudio.com/items?itemName=carolynvs.dep). +It provides snippets and improved highlighting that makes it easier to work with dep. + +### Selecting the version for a dependency +* Use released versions of a dependency, for example v1.2.3. +* Use the master branch when a dependency does not tag releases, or we require an unreleased change. +* Include an explanatory comment with a link to any relevant issues anytime a dependency is + pinned to a specific revision in Gopkg.toml. + +### Add a new dependency +1. Run `dep ensure -add github.com/example/project/pkg/foo`. This adds a constraint to Gopkg.toml, +and downloads the dependency to vendor/. +1. Import the package in the code and use it. +1. Run `dep ensure -v` to sync Gopkg.lock and vendor/ with your changes. + +### Change the version of a dependency +1. Edit Gopkg.toml and update the version for the project. If the project is not in +Gopkg.toml already, add a constraint for it and set the version. +1. Run `dep ensure -v` to sync Gopkg.lock and vendor/ with the updated version. + +[Watch a screencast](https://youtu.be/yvL66s_hQ94) + ## Demo walkthrough Check out the [introduction](./introduction.md) to get started with diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding.go index 360bdd618e4b..93ee862b5ee2 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding.go @@ -91,6 +91,10 @@ func validateServiceBindingSpec(spec *sc.ServiceBindingSpec, fldPath *field.Path allErrs = append(allErrs, field.Invalid(fldPath.Child("secretName"), spec.SecretName, msg)) } + if spec.ParametersFrom != nil { + allErrs = append(allErrs, validateParametersFromSource(spec.ParametersFrom, fldPath)...) + } + return allErrs } diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding_test.go index 8638e0756023..c23c6311d419 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/binding_test.go @@ -118,6 +118,50 @@ func TestValidateServiceBinding(t *testing.T) { }(), valid: false, }, + { + name: "valid parametersFrom", + binding: func() *servicecatalog.ServiceBinding { + b := validServiceBinding() + b.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{ + {SecretKeyRef: &servicecatalog.SecretKeyReference{Name: "test-key-name", Key: "test-key"}}} + return b + }(), + valid: true, + }, + { + name: "missing key reference in parametersFrom", + binding: func() *servicecatalog.ServiceBinding { + b := validServiceBinding() + b.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{{SecretKeyRef: nil}} + return b + }(), + valid: false, + }, + { + name: "key name is missing in parametersFrom", + binding: func() *servicecatalog.ServiceBinding { + b := validServiceBinding() + b.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{ + {SecretKeyRef: &servicecatalog.SecretKeyReference{Name: "", Key: "test-key"}}} + return b + }(), + valid: false, + }, + { + name: "key is missing in parametersFrom", + binding: func() *servicecatalog.ServiceBinding { + b := validServiceBinding() + b.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{ + {SecretKeyRef: &servicecatalog.SecretKeyReference{Name: "test-key-name", Key: ""}}} + return b + }(), + valid: false, + }, + { name: "valid with in-progress bind", binding: validServiceBindingWithInProgressBind(), diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance.go index 40413f8e9411..88a0bb44d39c 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance.go @@ -88,18 +88,7 @@ func validateServiceInstanceSpec(spec *sc.ServiceInstanceSpec, fldPath *field.Pa allErrs = append(allErrs, validatePlanReference(&spec.PlanReference, fldPath)...) if spec.ParametersFrom != nil { - for _, paramsFrom := range spec.ParametersFrom { - if paramsFrom.SecretKeyRef != nil { - if paramsFrom.SecretKeyRef.Name == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("parametersFrom.secretKeyRef.name"), "name is required")) - } - if paramsFrom.SecretKeyRef.Key == "" { - allErrs = append(allErrs, field.Required(fldPath.Child("parametersFrom.secretKeyRef.key"), "key is required")) - } - } else { - allErrs = append(allErrs, field.Required(fldPath.Child("parametersFrom"), "source must not be empty if present")) - } - } + allErrs = append(allErrs, validateParametersFromSource(spec.ParametersFrom, fldPath)...) } if spec.Parameters != nil { if len(spec.Parameters.Raw) == 0 { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance_test.go index b3d37cf41e60..7dd80571c6bb 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/instance_test.go @@ -159,6 +159,49 @@ func TestValidateServiceInstance(t *testing.T) { }(), valid: false, }, + { + name: "valid parametersFrom", + instance: func() *servicecatalog.ServiceInstance { + i := validServiceInstance() + i.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{ + {SecretKeyRef: &servicecatalog.SecretKeyReference{Name: "test-key-name", Key: "test-key"}}} + return i + }(), + valid: true, + }, + { + name: "missing key reference in parametersFrom", + instance: func() *servicecatalog.ServiceInstance { + i := validServiceInstance() + i.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{{SecretKeyRef: nil}} + return i + }(), + valid: false, + }, + { + name: "key name is missing in parametersFrom", + instance: func() *servicecatalog.ServiceInstance { + i := validServiceInstance() + i.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{ + {SecretKeyRef: &servicecatalog.SecretKeyReference{Name: "", Key: "test-key"}}} + return i + }(), + valid: false, + }, + { + name: "key is missing in parametersFrom", + instance: func() *servicecatalog.ServiceInstance { + i := validServiceInstance() + i.Spec.ParametersFrom = + []servicecatalog.ParametersFromSource{ + {SecretKeyRef: &servicecatalog.SecretKeyReference{Name: "test-key-name", Key: ""}}} + return i + }(), + valid: false, + }, { name: "valid with in-progress provision", instance: validServiceInstanceWithInProgressProvision(), diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass.go index ca358a7612de..21185c73935e 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass.go @@ -26,8 +26,10 @@ import ( sc "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog" ) -// validateServiceClassName is the validation function for ServiceClass names. -var validateServiceClassName = apivalidation.NameIsDNSSubdomain +const serviceClassNameFmt string = `[-a-zA-Z0-9]+` +const serviceClassNameMaxLength int = 63 + +var serviceClassNameRegexp = regexp.MustCompile("^" + serviceClassNameFmt + "$") const guidFmt string = "[a-zA-Z0-9]([-a-zA-Z0-9.]*[a-zA-Z0-9])?" const guidMaxLength int = 63 @@ -36,6 +38,19 @@ const guidMaxLength int = 63 // DNS1123 labels that allows uppercase characters. var guidRegexp = regexp.MustCompile("^" + guidFmt + "$") +// validateServiceClassName is the validation function for Service names. +func validateServiceClassName(value string, prefix bool) []string { + var errs []string + if len(value) > serviceClassNameMaxLength { + errs = append(errs, utilvalidation.MaxLenError(serviceClassNameMaxLength)) + } + if !serviceClassNameRegexp.MatchString(value) { + errs = append(errs, utilvalidation.RegexError(serviceClassNameFmt, "service-name-40d-0983-1b89")) + } + + return errs +} + // validateExternalID is the validation function for External IDs that // have been passed in. External IDs used to be OpenServiceBrokerAPI // GUIDs, so we will retain that form until there is another provider diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass_test.go index 2ddd145912b3..17dacf5aa9f7 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceclass_test.go @@ -113,6 +113,15 @@ func TestValidateClusterServiceClass(t *testing.T) { }(), valid: false, }, + { + name: "invalid serviceClass - period in externalName", + serviceClass: func() *servicecatalog.ClusterServiceClass { + s := validClusterServiceClass() + s.Spec.ExternalName = "abc.com" + return s + }(), + valid: false, + }, { name: "invalid serviceClass - missing externalName", serviceClass: func() *servicecatalog.ClusterServiceClass { @@ -122,6 +131,24 @@ func TestValidateClusterServiceClass(t *testing.T) { }(), valid: false, }, + { + name: "invalid serviceClass - valid but weird externalName1", + serviceClass: func() *servicecatalog.ClusterServiceClass { + s := validClusterServiceClass() + s.Spec.ExternalName = "-" + return s + }(), + valid: true, + }, + { + name: "invalid serviceClass - valid but weird externalName2", + serviceClass: func() *servicecatalog.ClusterServiceClass { + s := validClusterServiceClass() + s.Spec.ExternalName = "0" + return s + }(), + valid: true, + }, } for _, tc := range cases { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan.go index e7b832ac1317..7acdb267b2ce 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan.go @@ -26,7 +26,7 @@ import ( sc "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog" ) -const servicePlanNameFmt string = `[-a-z0-9]+` +const servicePlanNameFmt string = `[-a-zA-Z0-9]+` const servicePlanNameMaxLength int = 63 var servicePlanNameRegexp = regexp.MustCompile("^" + servicePlanNameFmt + "$") diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan_test.go index 33fd5562676a..d391c23b1e85 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/serviceplan_test.go @@ -65,7 +65,7 @@ func TestValidateClusterServicePlan(t *testing.T) { name: "bad name", servicePlan: func() *servicecatalog.ClusterServicePlan { s := validClusterServicePlan() - s.Name = "X" + s.Name = "#" return s }(), valid: false, @@ -74,11 +74,29 @@ func TestValidateClusterServicePlan(t *testing.T) { name: "bad externalName", servicePlan: func() *servicecatalog.ClusterServicePlan { s := validClusterServicePlan() - s.Spec.ExternalName = "X" + s.Spec.ExternalName = "#" return s }(), valid: false, }, + { + name: "mixed case Name", + servicePlan: func() *servicecatalog.ClusterServicePlan { + s := validClusterServicePlan() + s.Name = "abcXYZ" + return s + }(), + valid: true, + }, + { + name: "mixed case externalName", + servicePlan: func() *servicecatalog.ClusterServicePlan { + s := validClusterServicePlan() + s.Spec.ExternalName = "abcXYZ" + return s + }(), + valid: true, + }, { name: "missing clusterServiceBrokerName", servicePlan: func() *servicecatalog.ClusterServicePlan { @@ -107,6 +125,9 @@ func TestValidateClusterServicePlan(t *testing.T) { valid: false, }, { + // Note this is NOT due to the spec, this is due to + // a Kubernetes limitation. So, technically this restriction + // could cause a valid Broker to not work against Kube. name: "external id too long", servicePlan: func() *servicecatalog.ClusterServicePlan { s := validClusterServicePlan() diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/validation.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/validation.go index c4202462bba0..3b90185feb9e 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/validation.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/validation/validation.go @@ -17,6 +17,8 @@ limitations under the License. package validation import ( + sc "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog" + "k8s.io/apimachinery/pkg/util/validation/field" "regexp" ) @@ -25,3 +27,22 @@ var hexademicalStringRegexp = regexp.MustCompile("^[[:xdigit:]]*$") func stringIsHexadecimal(s string) bool { return hexademicalStringRegexp.MatchString(s) } + +func validateParametersFromSource(parametersFrom []sc.ParametersFromSource, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for _, paramsFrom := range parametersFrom { + if paramsFrom.SecretKeyRef != nil { + if paramsFrom.SecretKeyRef.Name == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("parametersFrom.secretKeyRef.name"), "name is required")) + } + if paramsFrom.SecretKeyRef.Key == "" { + allErrs = append(allErrs, field.Required(fldPath.Child("parametersFrom.secretKeyRef.key"), "key is required")) + } + } else { + allErrs = append(allErrs, field.Required(fldPath.Child("parametersFrom"), "source must not be empty if present")) + } + } + + return allErrs +} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/settings/v1alpha1/types.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/settings/v1alpha1/types.go index fe9096ab01ed..44789b8e70f8 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/settings/v1alpha1/types.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/apis/settings/v1alpha1/types.go @@ -65,7 +65,7 @@ type PodPresetSpec struct { type PodPresetList struct { metav1.TypeMeta `json:",inline"` // +optional - metav1.ListMeta `json:"metadata, omitempty"` + metav1.ListMeta `json:"metadata,omitempty"` Items []PodPreset `json:"items"` } diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller.go index 99e4a5e65a52..db1a0607f9a6 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller.go @@ -495,7 +495,7 @@ func convertCatalog(in *osb.CatalogResponse) ([]*v1beta1.ClusterServiceClass, [] } if utilfeature.DefaultFeatureGate.Enabled(scfeatures.AsyncBindingOperations) { - serviceClasses[i].Spec.BindingRetrievable = svc.BindingRetrievable + serviceClasses[i].Spec.BindingRetrievable = svc.BindingsRetrievable } if svc.Metadata != nil { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_binding.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_binding.go index 5906a8ea487e..f21656e91575 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_binding.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_binding.go @@ -672,6 +672,17 @@ func clearServiceBindingCurrentOperation(toUpdate *v1beta1.ServiceBinding) { toUpdate.Status.OrphanMitigationInProgress = false } +// rollbackBindingReconciledGenerationOnDeletion resets the ReconciledGeneration +// if a deletion was performed while an async bind is running. +// TODO: rework saving off current generation as the start of the async +// operation, see PR 1708/Issue 1587. +func rollbackBindingReconciledGenerationOnDeletion(binding *v1beta1.ServiceBinding, currentReconciledGeneration int64) { + if binding.DeletionTimestamp != nil { + glog.V(4).Infof("Not updating ReconciledGeneration after async operation because there is a deletion pending.") + binding.Status.ReconciledGeneration = currentReconciledGeneration + } +} + func (c *controller) requeueServiceBindingForPoll(key string) error { c.bindingQueue.Add(key) @@ -1129,7 +1140,9 @@ func (c *controller) processServiceBindingOperationError(binding *v1beta1.Servic // injected in the cluster. func (c *controller) processBindSuccess(binding *v1beta1.ServiceBinding) error { setServiceBindingCondition(binding, v1beta1.ServiceBindingConditionReady, v1beta1.ConditionTrue, successInjectedBindResultReason, successInjectedBindResultMessage) + currentReconciledGeneration := binding.Status.ReconciledGeneration clearServiceBindingCurrentOperation(binding) + rollbackBindingReconciledGenerationOnDeletion(binding, currentReconciledGeneration) if _, err := c.updateServiceBindingStatus(binding); err != nil { return err @@ -1142,6 +1155,7 @@ func (c *controller) processBindSuccess(binding *v1beta1.ServiceBinding) error { // processBindFailure handles the logging and updating of a ServiceBinding that // hit a terminal failure during bind reconciliation. func (c *controller) processBindFailure(binding *v1beta1.ServiceBinding, readyCond, failedCond *v1beta1.ServiceBindingCondition, shouldMitigateOrphan bool) error { + currentReconciledGeneration := binding.Status.ReconciledGeneration if readyCond != nil { c.recorder.Event(binding, corev1.EventTypeWarning, readyCond.Reason, readyCond.Message) setServiceBindingCondition(binding, readyCond.Type, readyCond.Status, readyCond.Reason, readyCond.Message) @@ -1161,6 +1175,7 @@ func (c *controller) processBindFailure(binding *v1beta1.ServiceBinding, readyCo binding.Status.OperationStartTime = nil } else { clearServiceBindingCurrentOperation(binding) + rollbackBindingReconciledGenerationOnDeletion(binding, currentReconciledGeneration) } if _, err := c.updateServiceBindingStatus(binding); err != nil { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker.go index dd24ffa3c880..158567bafce9 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker.go @@ -540,11 +540,27 @@ func (c *controller) reconcileClusterServiceClassFromClusterServiceBrokerCatalog toUpdate.Spec.ExternalName = serviceClass.Spec.ExternalName toUpdate.Spec.ExternalMetadata = serviceClass.Spec.ExternalMetadata - if _, err := c.serviceCatalogClient.ClusterServiceClasses().Update(toUpdate); err != nil { + updatedServiceClass, err := c.serviceCatalogClient.ClusterServiceClasses().Update(toUpdate) + if err != nil { glog.Error(pcb.Messagef("Error updating %s: %v", pretty.ClusterServiceClassName(serviceClass), err)) return err } + if updatedServiceClass.Status.RemovedFromBrokerCatalog { + glog.V(4).Info(pcb.Messagef("Resetting RemovedFromBrokerCatalog status on %s", pretty.ClusterServiceClassName(serviceClass))) + updatedServiceClass.Status.RemovedFromBrokerCatalog = false + _, err := c.serviceCatalogClient.ClusterServiceClasses().UpdateStatus(updatedServiceClass) + if err != nil { + s := fmt.Sprintf("Error updating status of %s: %v", pretty.ClusterServiceClassName(updatedServiceClass), err) + glog.Warning(pcb.Message(s)) + c.recorder.Eventf(broker, corev1.EventTypeWarning, errorSyncingCatalogReason, s) + if err := c.updateClusterServiceBrokerCondition(broker, v1beta1.ServiceBrokerConditionReady, v1beta1.ConditionFalse, errorSyncingCatalogReason, errorSyncingCatalogMessage+s); err != nil { + return err + } + return err + } + } + return nil } diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker_test.go index 0c0af7188591..188454a4c563 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_broker_test.go @@ -318,6 +318,62 @@ func TestReconcileClusterServiceBrokerRemovedClusterServiceClass(t *testing.T) { assertNumberOfActions(t, kubeActions, 0) } +// TestReconcileClusterServiceBrokerRemovedAndRestoredClusterServiceClass +// validates where Service Catalog has a class that is marked as +// RemovedFromBrokerCatalog but then the ServiceBroker adds the class back into +// its getCatalog response. This should result in the class's status being +// updated resetting the RemovedFromBrokerCatalog to false. +func TestReconcileClusterServiceBrokerRemovedAndRestoredClusterServiceClass(t *testing.T) { + fakeKubeClient, fakeCatalogClient, fakeClusterServiceBrokerClient, testController, sharedInformers := newTestController(t, getTestCatalogConfig()) + + testClusterServiceClass := getTestClusterServiceClass() + testClusterServicePlan := getTestClusterServicePlan() + testClusterServicePlanNonbindable := getTestClusterServicePlanNonbindable() + testClusterServiceClass.Status.RemovedFromBrokerCatalog = true + sharedInformers.ClusterServiceClasses().Informer().GetStore().Add(testClusterServiceClass) + + fakeCatalogClient.AddReactor("list", "clusterserviceclasses", func(action clientgotesting.Action) (bool, runtime.Object, error) { + return true, &v1beta1.ClusterServiceClassList{ + Items: []v1beta1.ClusterServiceClass{ + *testClusterServiceClass, + }, + }, nil + }) + + fakeCatalogClient.AddReactor("update", "clusterserviceclasses", func(action clientgotesting.Action) (bool, runtime.Object, error) { + return true, testClusterServiceClass, nil + }) + + if err := testController.reconcileClusterServiceBroker(getTestClusterServiceBroker()); err != nil { + t.Fatalf("This should not fail: %v", err) + } + + brokerActions := fakeClusterServiceBrokerClient.Actions() + assertNumberOfClusterServiceBrokerActions(t, brokerActions, 1) + assertGetCatalog(t, brokerActions[0]) + + listRestrictions := clientgotesting.ListRestrictions{ + Labels: labels.Everything(), + Fields: fields.OneTermEqualSelector("spec.clusterServiceBrokerName", "test-broker"), + } + + actions := fakeCatalogClient.Actions() + assertNumberOfActions(t, actions, 7) + assertList(t, actions[0], &v1beta1.ClusterServiceClass{}, listRestrictions) + assertList(t, actions[1], &v1beta1.ClusterServicePlan{}, listRestrictions) + assertUpdate(t, actions[2], testClusterServiceClass) + class := assertUpdateStatus(t, actions[3], testClusterServiceClass) + assertRemovedFromBrokerCatalogFalse(t, class) + assertCreate(t, actions[4], testClusterServicePlan) + assertCreate(t, actions[5], testClusterServicePlanNonbindable) + updatedClusterServiceBroker := assertUpdateStatus(t, actions[6], getTestClusterServiceBroker()) + assertClusterServiceBrokerReadyTrue(t, updatedClusterServiceBroker) + + // verify no kube resources created + kubeActions := fakeKubeClient.Actions() + assertNumberOfActions(t, kubeActions, 0) +} + func TestReconcileClusterServiceBrokerRemovedClusterServicePlan(t *testing.T) { fakeKubeClient, fakeCatalogClient, fakeClusterServiceBrokerClient, testController, sharedInformers := newTestController(t, getTestCatalogConfig()) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_test.go index 38a788b74b2b..206a59369e90 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/controller/controller_test.go @@ -1542,7 +1542,7 @@ func newTestController(t *testing.T, config fakeosb.FakeClientConfiguration) ( // create a fake kube client fakeKubeClient := &clientgofake.Clientset{} // create a fake sc client - fakeCatalogClient := &fake.Clientset{&servicecatalogclientset.Clientset{}} + fakeCatalogClient := &fake.Clientset{Clientset: &servicecatalogclientset.Clientset{}} fakeOSBClient := fakeosb.NewFakeClient(config) // error should always be nil brokerClFunc := fakeosb.ReturnFakeClientFunc(fakeOSBClient) @@ -1818,6 +1818,25 @@ func testActionFor(t *testing.T, name string, f failfFunc, action clientgotestin return fakeRtObject, true } +func assertRemovedFromBrokerCatalogFalse(t *testing.T, obj runtime.Object) { + assertRemovedFromBrokerCatalog(t, obj, false) +} + +func assertRemovedFromBrokerCatalogTrue(t *testing.T, obj runtime.Object) { + assertRemovedFromBrokerCatalog(t, obj, true) +} + +func assertRemovedFromBrokerCatalog(t *testing.T, obj runtime.Object, condition bool) { + clusterServiceClass, ok := obj.(*v1beta1.ClusterServiceClass) + if !ok { + fatalf(t, "Couldn't convert object %+v into a *v1beta1.ClusterServiceClass", obj) + } + + if clusterServiceClass.Status.RemovedFromBrokerCatalog != condition { + fatalf(t, "ClusterServiceClass.RemovedFromBrokerCatalog!=%v", condition) + } +} + func assertClusterServiceBrokerReadyTrue(t *testing.T, obj runtime.Object) { assertClusterServiceBrokerCondition(t, obj, v1beta1.ServiceBrokerConditionReady, v1beta1.ConditionTrue) } diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/hyperkube/hyperkube.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/hyperkube/hyperkube.go index d0757d1d8b44..0cbbb04b8ebd 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/hyperkube/hyperkube.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/hyperkube/hyperkube.go @@ -25,6 +25,9 @@ import ( "os" "path" + "github.com/golang/glog" + "github.com/kubernetes-incubator/service-catalog/pkg/version" + "github.com/spf13/pflag" utiltemplate "github.com/kubernetes-incubator/service-catalog/pkg/kubernetes/pkg/util/template" @@ -182,7 +185,7 @@ func (hk *HyperKube) Run(args []string, stopCh <-chan struct{}) error { logs.InitLogs() defer logs.FlushLogs() - + glog.Infof("Service Catalog version %s (built %s)", version.Get().String(), version.Get().BuildDate) if !s.RespectsStopCh { // For commands that do not respect the stopCh, we run them in a go // routine and leave them running when stopCh is closed. diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/metrics/metrics.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/metrics/metrics.go index 340910b031ce..711cefebfe57 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/metrics/metrics.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/metrics/metrics.go @@ -74,11 +74,11 @@ var ( ) ) -func register() { +func register(registry *prometheus.Registry) { registerMetrics.Do(func() { - prometheus.MustRegister(BrokerServiceClassCount) - prometheus.MustRegister(BrokerServicePlanCount) - prometheus.MustRegister(OSBRequestCount) + registry.MustRegister(BrokerServiceClassCount) + registry.MustRegister(BrokerServicePlanCount) + registry.MustRegister(OSBRequestCount) }) } @@ -86,7 +86,8 @@ func register() { // objects with Prometheus and installs the Prometheus http handler at the // default context. func RegisterMetricsAndInstallHandler(m *http.ServeMux) { - register() - m.Handle("/metrics", promhttp.Handler()) - glog.V(4).Info("Registered /metrics with promhttp") + registry := prometheus.NewRegistry() + register(registry) + m.Handle("/metrics", promhttp.HandlerFor(registry, promhttp.HandlerOpts{ErrorHandling: promhttp.ContinueOnError})) + glog.V(4).Info("Registered /metrics with prometheus") } diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/dep.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/dep.go new file mode 100644 index 000000000000..ad8aeb25e8a0 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/dep.go @@ -0,0 +1,8 @@ +package svcat + +import ( + // This workaround a gap in dep where we have no control over the version of + // our transitive dependencies. + // Once https://github.com/golang/dep/pull/1489 is merged, we can remove this file. + _ "github.com/Azure/go-autorest/autorest" +) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/binding.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/binding.go index 727bc714ecf2..d7fbac448a4c 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/binding.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/binding.go @@ -99,11 +99,9 @@ func (sdk *SDK) Bind(namespace, bindingName, instanceName, secretName string, // Unbind deletes all bindings associated to an instance. func (sdk *SDK) Unbind(ns, instanceName string) error { - instance := &v1beta1.ServiceInstance{ - ObjectMeta: v1.ObjectMeta{ - Namespace: ns, - Name: instanceName, - }, + instance, err := sdk.RetrieveInstance(ns, instanceName) + if err != nil { + return err } bindings, err := sdk.RetrieveBindingsByInstance(instance) if err != nil { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/binding_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/binding_test.go new file mode 100644 index 000000000000..2df3c4b980cc --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/binding_test.go @@ -0,0 +1,235 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package servicecatalog_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Binding", func() { + var ( + sdk *SDK + svcCatClient *fake.Clientset + sb *v1beta1.ServiceBinding + sb2 *v1beta1.ServiceBinding + ) + + BeforeEach(func() { + sb = &v1beta1.ServiceBinding{ObjectMeta: metav1.ObjectMeta{Name: "foobar", Namespace: "foobar_namespace"}} + sb2 = &v1beta1.ServiceBinding{ObjectMeta: metav1.ObjectMeta{Name: "barbaz", Namespace: "foobar_namespace"}} + svcCatClient = fake.NewSimpleClientset(sb, sb2) + sdk = &SDK{ + ServiceCatalogClient: svcCatClient, + } + }) + + Describe("RetrieveBinding", func() { + It("Calls the generated v1beta1 Get method with the passed in binding and namespace", func() { + binding, err := sdk.RetrieveBinding(sb.Namespace, sb.Name) + Expect(err).NotTo(HaveOccurred()) + Expect(binding).To(Equal(sb)) + + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "servicebindings")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(sb.Name)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(sb.Namespace)) + }) + It("Bubbles up errors", func() { + fakeName := "not_a_real_binding" + + _, err := sdk.RetrieveBinding(sb.Namespace, fakeName) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "servicebindings")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(fakeName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(sb.Namespace)) + }) + }) + + Describe("RetrieveBindings", func() { + It("Calls the generated v1beta1 List method with the specified namespace", func() { + bindings, err := sdk.RetrieveBindings(sb.Namespace) + + Expect(err).NotTo(HaveOccurred()) + Expect(bindings.Items).Should(ConsistOf(*sb, *sb2)) + Expect(svcCatClient.Actions()[0].Matches("list", "servicebindings")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "servicebindings", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + bindings, err := sdk.RetrieveBindings(sb.Namespace) + + Expect(bindings).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "servicebindings")).To(BeTrue()) + }) + }) + + Describe("RetrieveBindingsByInstance", func() { + It("Calls the generated v1beta1 List method on the provided instance's namespace", func() { + si := &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: "apple_instance", Namespace: sb.Namespace}} + sb.Spec.ServiceInstanceRef.Name = si.Name + svcCatClient = fake.NewSimpleClientset(sb, sb2) + sdk = &SDK{ + ServiceCatalogClient: svcCatClient, + } + + bindings, err := sdk.RetrieveBindingsByInstance(si) + Expect(err).NotTo(HaveOccurred()) + + Expect(bindings).To(ConsistOf(*sb)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("list", "servicebindings")).To(BeTrue()) + Expect(actions[0].(testing.ListActionImpl).Namespace).To(Equal(si.Namespace)) + }) + + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "servicebindings", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + si := &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: "apple_instance", Namespace: "not_real_namespace"}} + bindings, err := sdk.RetrieveBindingsByInstance(si) + + Expect(bindings).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "servicebindings")).To(BeTrue()) + }) + }) + + Describe("Bind", func() { + It("Calls the generated v1beta1 method to create a binding", func() { + bindingNamespace := "banana_namespace" + bindingName := "banana_binding" + instanceName := "banana_instance" + secret := "banana_secret" + binding, err := sdk.Bind(bindingNamespace, bindingName, instanceName, secret, map[string]string{}, map[string]string{}) + + Expect(err).NotTo(HaveOccurred()) + Expect(binding).NotTo(BeNil()) + Expect(binding.ObjectMeta.Namespace).To(Equal(bindingNamespace)) + Expect(binding.ObjectMeta.Name).To(Equal(bindingName)) + Expect(binding.Spec.ServiceInstanceRef.Name).To(Equal(instanceName)) + Expect(binding.Spec.SecretName).To(Equal(secret)) + Expect(svcCatClient.Actions()[0].Matches("create", "servicebindings")).To(BeTrue()) + }) + + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("create", "servicebindings", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + bindingNamespace := "banana_namespace" + bindingName := "banana_binding" + instanceName := "banana_instance" + binding, err := sdk.Bind(bindingNamespace, bindingName, instanceName, "banana_secret", map[string]string{}, map[string]string{}) + + Expect(binding).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("create", "servicebindings")).To(BeTrue()) + }) + }) + + Describe("Unbind", func() { + It("Calls the generated v1beta1 method to delete a binding", func() { + instanceNamespace := sb.Namespace + instanceName := "apple_instance" + si := &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: instanceName, Namespace: instanceNamespace}} + sb.Spec.ServiceInstanceRef.Name = si.Name + linkedClient := fake.NewSimpleClientset(sb, sb2, si) + sdk = &SDK{ + ServiceCatalogClient: linkedClient, + } + + err := sdk.Unbind(instanceNamespace, instanceName) + + Expect(err).NotTo(HaveOccurred()) + Expect(linkedClient.Actions()[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(linkedClient.Actions()[1].Matches("list", "servicebindings")).To(BeTrue()) + Expect(linkedClient.Actions()[2].Matches("delete", "servicebindings")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + instanceNamespace := sb.Namespace + instanceName := "apple_instance" + errorMessage := "error deleting binding" + si := &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: instanceName, Namespace: instanceNamespace}} + sb.Spec.ServiceInstanceRef.Name = si.Name + badClient := &fake.Clientset{} + badClient.AddReactor("get", "serviceinstances", func(action testing.Action) (bool, runtime.Object, error) { + return true, si, nil + }) + badClient.AddReactor("list", "servicebindings", func(action testing.Action) (bool, runtime.Object, error) { + return true, &v1beta1.ServiceBindingList{Items: []v1beta1.ServiceBinding{*sb}}, nil + }) + badClient.AddReactor("delete", "servicebindings", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk = &SDK{ + ServiceCatalogClient: badClient, + } + + err := sdk.Unbind(instanceNamespace, instanceName) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(badClient.Actions()[1].Matches("list", "servicebindings")).To(BeTrue()) + Expect(badClient.Actions()[2].Matches("delete", "servicebindings")).To(BeTrue()) + }) + It("Checks to see if the binding's instance exists before attempting to delete the binding", func() { + instanceNamespace := sb.Namespace + instanceName := "apple_instance" + si := &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: instanceName, Namespace: instanceNamespace}} + sb.Spec.ServiceInstanceRef.Name = si.Name + noInstanceClient := fake.NewSimpleClientset(sb, sb2) + sdk = &SDK{ + ServiceCatalogClient: noInstanceClient, + } + + err := sdk.Unbind(instanceNamespace, instanceName) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring("unable to get instance")) + Expect(noInstanceClient.Actions()[0].Matches("get", "serviceinstances")).To(BeTrue()) + }) + }) +}) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/broker_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/broker_test.go new file mode 100644 index 000000000000..e07c46cc06de --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/broker_test.go @@ -0,0 +1,135 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package servicecatalog_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Broker", func() { + var ( + sdk *SDK + svcCatClient *fake.Clientset + sb *v1beta1.ClusterServiceBroker + sb2 *v1beta1.ClusterServiceBroker + ) + + BeforeEach(func() { + sb = &v1beta1.ClusterServiceBroker{ObjectMeta: metav1.ObjectMeta{Name: "foobar"}} + sb2 = &v1beta1.ClusterServiceBroker{ObjectMeta: metav1.ObjectMeta{Name: "barbaz"}} + svcCatClient = fake.NewSimpleClientset(sb, sb2) + sdk = &SDK{ + ServiceCatalogClient: svcCatClient, + } + }) + + Describe("RetrieveBrokers", func() { + It("Calls the generated v1beta1 List method", func() { + brokers, err := sdk.RetrieveBrokers() + + Expect(err).NotTo(HaveOccurred()) + Expect(brokers).Should(ConsistOf(*sb, *sb2)) + Expect(svcCatClient.Actions()[0].Matches("list", "clusterservicebrokers")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "clusterservicebrokers", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + _, err := sdk.RetrieveBrokers() + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "clusterservicebrokers")).To(BeTrue()) + }) + }) + Describe("RetrieveBroker", func() { + It("Calls the generated v1beta1 List method with the passed in broker", func() { + broker, err := sdk.RetrieveBroker(sb.Name) + + Expect(err).NotTo(HaveOccurred()) + Expect(broker).To(Equal(sb)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(sb.Name)) + }) + It("Bubbles up errors", func() { + brokerName := "banana" + + broker, err := sdk.RetrieveBroker(brokerName) + + Expect(broker).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(brokerName)) + }) + }) + Describe("RetrieveBrokerByClass", func() { + It("Calls the generated v1beta1 List method with the passed in class's parent broker", func() { + sc := &v1beta1.ClusterServiceClass{Spec: v1beta1.ClusterServiceClassSpec{ClusterServiceBrokerName: sb.Name}} + broker, err := sdk.RetrieveBrokerByClass(sc) + + Expect(broker).NotTo(BeNil()) + Expect(err).NotTo(HaveOccurred()) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(sb.Name)) + }) + + It("Bubbles up errors", func() { + brokerName := "banana" + sc := &v1beta1.ClusterServiceClass{Spec: v1beta1.ClusterServiceClassSpec{ClusterServiceBrokerName: brokerName}} + broker, err := sdk.RetrieveBrokerByClass(sc) + + Expect(broker).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(brokerName)) + }) + }) + Describe("Sync", func() { + It("Useds the generated b1beta1 Retrieve method to get the broker, and then updates it with a new RelistRequests", func() { + err := sdk.Sync(sb.Name, 3) + Expect(err).NotTo(HaveOccurred()) + + actions := svcCatClient.Actions() + Expect(len(actions) >= 2).To(BeTrue()) + Expect(actions[0].Matches("get", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(sb.Name)) + + Expect(actions[1].Matches("update", "clusterservicebrokers")).To(BeTrue()) + Expect(actions[1].(testing.UpdateActionImpl).Object.(*v1beta1.ClusterServiceBroker).Spec.RelistRequests).Should(BeNumerically(">", 0)) + }) + }) +}) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/class_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/class_test.go new file mode 100644 index 000000000000..9ead4863dba2 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/class_test.go @@ -0,0 +1,210 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package servicecatalog_test + +import ( + "errors" + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Class", func() { + var ( + sdk *SDK + svcCatClient *fake.Clientset + sc *v1beta1.ClusterServiceClass + sc2 *v1beta1.ClusterServiceClass + ) + + BeforeEach(func() { + sc = &v1beta1.ClusterServiceClass{ObjectMeta: metav1.ObjectMeta{Name: "foobar"}} + sc2 = &v1beta1.ClusterServiceClass{ObjectMeta: metav1.ObjectMeta{Name: "barbaz"}} + svcCatClient = fake.NewSimpleClientset(sc, sc2) + sdk = &SDK{ + ServiceCatalogClient: svcCatClient, + } + }) + + Describe("RetrieveClasses", func() { + It("Calls the generated v1beta1 List method", func() { + classes, err := sdk.RetrieveClasses() + + Expect(err).NotTo(HaveOccurred()) + Expect(classes).Should(ConsistOf(*sc, *sc2)) + Expect(svcCatClient.Actions()[0].Matches("list", "clusterserviceclasses")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk = &SDK{ + ServiceCatalogClient: badClient, + } + + _, err := sdk.RetrieveClasses() + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "clusterserviceclasses")).To(BeTrue()) + }) + }) + Describe("RetrieveClassByName", func() { + It("Calls the generated v1beta1 List method with the passed in class", func() { + className := sc.Name + realClient := &fake.Clientset{} + realClient.AddReactor("list", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, &v1beta1.ClusterServiceClassList{Items: []v1beta1.ClusterServiceClass{*sc}}, nil + }) + sdk = &SDK{ + ServiceCatalogClient: realClient, + } + class, err := sdk.RetrieveClassByName(className) + + Expect(err).NotTo(HaveOccurred()) + Expect(class).To(Equal(sc)) + actions := realClient.Actions() + Expect(actions[0].Matches("list", "clusterserviceclasses")).To(BeTrue()) + requirements := actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Requirements() + Expect(requirements).ShouldNot(BeEmpty()) + Expect(requirements[0].Field).To(Equal("spec.externalName")) + Expect(requirements[0].Value).To(Equal(className)) + }) + It("Bubbles up errors", func() { + className := "notreal_class" + emptyClient := &fake.Clientset{} + emptyClient.AddReactor("list", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, &v1beta1.ClusterServiceClassList{Items: []v1beta1.ClusterServiceClass{}}, nil + }) + sdk = &SDK{ + ServiceCatalogClient: emptyClient, + } + class, err := sdk.RetrieveClassByName(className) + + Expect(class).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := emptyClient.Actions() + Expect(actions[0].Matches("list", "clusterserviceclasses")).To(BeTrue()) + requirements := actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Requirements() + Expect(requirements).ShouldNot(BeEmpty()) + Expect(requirements[0].Field).To(Equal("spec.externalName")) + Expect(requirements[0].Value).To(Equal(className)) + }) + }) + Describe("RetrieveClassByID", func() { + It("Calls the generated v1beta1 get method", func() { + classID := fmt.Sprintf("%v", sc.UID) + realClient := &fake.Clientset{} + realClient.AddReactor("get", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, sc, nil + }) + sdk = &SDK{ + ServiceCatalogClient: realClient, + } + class, err := sdk.RetrieveClassByID(classID) + Expect(err).NotTo(HaveOccurred()) + Expect(fmt.Sprintf("%v", class.UID)).To(Equal(classID)) + actions := realClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceclasses")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + errorMessage := "not found" + emptyClient := &fake.Clientset{} + emptyClient.AddReactor("get", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, errors.New(errorMessage) + }) + sdk = &SDK{ + ServiceCatalogClient: emptyClient, + } + class, err := sdk.RetrieveClassByID("not_real") + + Expect(class).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := emptyClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceclasses")).To(BeTrue()) + }) + }) + Describe("RetrieveClassByPlan", func() { + It("Calls the generated v1beta1 get method with the plan's parent service class's name", func() { + classPlan := &v1beta1.ClusterServicePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_plan", + }, + Spec: v1beta1.ClusterServicePlanSpec{ + ClusterServiceClassRef: v1beta1.ClusterObjectReference{ + Name: sc.Name, + }, + }, + } + realClient := &fake.Clientset{} + realClient.AddReactor("get", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, sc, nil + }) + sdk = &SDK{ + ServiceCatalogClient: realClient, + } + class, err := sdk.RetrieveClassByPlan(classPlan) + Expect(err).NotTo(HaveOccurred()) + Expect(class).To(Equal(sc)) + actions := realClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceclasses")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(sc.Name)) + }) + It("Bubbles up errors", func() { + fakeClassName := "not_real" + errorMessage := "not found" + + classPlan := &v1beta1.ClusterServicePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_plan", + }, + Spec: v1beta1.ClusterServicePlanSpec{ + ClusterServiceClassRef: v1beta1.ClusterObjectReference{ + Name: fakeClassName, + }, + }, + } + badClient := &fake.Clientset{} + badClient.AddReactor("get", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, errors.New(errorMessage) + }) + sdk = &SDK{ + ServiceCatalogClient: badClient, + } + class, err := sdk.RetrieveClassByPlan(classPlan) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + Expect(class).To(BeNil()) + actions := badClient.Actions() + Expect(actions[0].Matches("get", "clusterserviceclasses")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(fakeClassName)) + }) + }) +}) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/instance_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/instance_test.go new file mode 100644 index 000000000000..a84399bcdb26 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/instance_test.go @@ -0,0 +1,487 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package servicecatalog_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Instances", func() { + var ( + sdk *SDK + svcCatClient *fake.Clientset + si *v1beta1.ServiceInstance + si2 *v1beta1.ServiceInstance + ) + + BeforeEach(func() { + si = &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: "foobar", Namespace: "foobar_namespace"}} + si2 = &v1beta1.ServiceInstance{ObjectMeta: metav1.ObjectMeta{Name: "barbaz", Namespace: "foobar_namespace"}} + svcCatClient = fake.NewSimpleClientset(si, si2) + sdk = &SDK{ + ServiceCatalogClient: svcCatClient, + } + }) + + Describe("RetrieveInstancees", func() { + It("Calls the generated v1beta1 List method with the specified namespace", func() { + namespace := si.Namespace + + instances, err := sdk.RetrieveInstances(namespace) + + Expect(err).NotTo(HaveOccurred()) + Expect(instances.Items).Should(ConsistOf(*si, *si2)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("list", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.ListActionImpl).Namespace).To(Equal(namespace)) + }) + It("Bubbles up errors", func() { + namespace := si.Namespace + badClient := &fake.Clientset{} + errorMessage := "error retrieving list" + badClient.AddReactor("list", "serviceinstances", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + _, err := sdk.RetrieveInstances(namespace) + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "serviceinstances")).To(BeTrue()) + }) + }) + Describe("RetrieveInstance", func() { + It("Calls the generated v1beta1 Get method with the passed in instance", func() { + instanceName := si.Name + namespace := si.Namespace + + instance, err := sdk.RetrieveInstance(namespace, instanceName) + Expect(err).NotTo(HaveOccurred()) + Expect(instance).To(Equal(si)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(instanceName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + It("Bubbles up errors", func() { + instanceName := "not_real" + namespace := "foobar_namespace" + + _, err := sdk.RetrieveInstance(namespace, instanceName) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("not found")) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(instanceName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + }) + Describe("RetrieveInstanceByBinding", func() { + It("Calls the generated v1beta1 Get method with the binding's namespace and the binding's instance's name", func() { + instanceName := si.Name + namespace := si.Namespace + sb := &v1beta1.ServiceBinding{ObjectMeta: metav1.ObjectMeta{Name: "banana_binding", Namespace: namespace}} + sb.Spec.ServiceInstanceRef.Name = instanceName + instance, err := sdk.RetrieveInstanceByBinding(sb) + + Expect(err).NotTo(HaveOccurred()) + Expect(instance).NotTo(BeNil()) + Expect(instance).To(Equal(si)) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(instanceName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + It("Bubbles up errors", func() { + namespace := si.Namespace + instanceName := "not_real_instance" + sb := &v1beta1.ServiceBinding{ObjectMeta: metav1.ObjectMeta{Name: "banana_binding", Namespace: namespace}} + sb.Spec.ServiceInstanceRef.Name = instanceName + badClient := &fake.Clientset{} + errorMessage := "no instance found" + badClient.AddReactor("get", "serviceinstances", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + instance, err := sdk.RetrieveInstanceByBinding(sb) + Expect(instance).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + actions := badClient.Actions() + Expect(actions[0].Matches("get", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(instanceName)) + Expect(actions[0].(testing.GetActionImpl).Namespace).To(Equal(namespace)) + }) + }) + Describe("RetrieveInstancesByPlan", func() { + It("Calls the generated v1beta1 List method with a ListOption containing the passed in plan", func() { + plan := &v1beta1.ClusterServicePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_plan", + }, + Spec: v1beta1.ClusterServicePlanSpec{}, + } + si = &v1beta1.ServiceInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: "foobar_namespace", + }, + Spec: v1beta1.ServiceInstanceSpec{ + ClusterServicePlanRef: &v1beta1.ClusterObjectReference{ + Name: plan.Name, + }, + }, + } + linkedClient := fake.NewSimpleClientset(si, si2) + sdk.ServiceCatalogClient = linkedClient + + _, err := sdk.RetrieveInstancesByPlan(plan) + Expect(err).NotTo(HaveOccurred()) + actions := linkedClient.Actions() + Expect(actions[0].Matches("list", "serviceinstances")).To(BeTrue()) + opts := fields.Set{"spec.clusterServicePlanRef.name": plan.Name} + Expect(actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Matches(opts)).To(BeTrue()) + }) + It("Bubbles up errors", func() { + badClient := &fake.Clientset{} + errorMessage := "no instances found" + plan := &v1beta1.ClusterServicePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_plan", + }, + Spec: v1beta1.ClusterServicePlanSpec{}, + } + badClient.AddReactor("list", "serviceinstances", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + instances, err := sdk.RetrieveInstancesByPlan(plan) + Expect(instances).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + actions := badClient.Actions() + Expect(actions[0].Matches("list", "serviceinstances")).To(BeTrue()) + opts := fields.Set{"spec.clusterServicePlanRef.name": plan.Name} + Expect(actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Matches(opts)).To(BeTrue()) + }) + }) + Describe("InstanceParentHierarchy", func() { + It("calls the v1beta1 generated Get function repeatedly to build the heirarchy of the passed in service isntance", func() { + broker := &v1beta1.ClusterServiceBroker{ObjectMeta: metav1.ObjectMeta{Name: "foobar_broker"}} + class := &v1beta1.ClusterServiceClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_class", + }, + Spec: v1beta1.ClusterServiceClassSpec{ + ClusterServiceBrokerName: broker.Name, + }, + } + plan := &v1beta1.ClusterServicePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_plan", + }, + Spec: v1beta1.ClusterServicePlanSpec{}, + } + si = &v1beta1.ServiceInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: "foobar_namespace", + }, + Spec: v1beta1.ServiceInstanceSpec{ + ClusterServicePlanRef: &v1beta1.ClusterObjectReference{ + Name: plan.Name, + }, + ClusterServiceClassRef: &v1beta1.ClusterObjectReference{ + Name: class.Name, + }, + }, + } + linkedClient := fake.NewSimpleClientset(si, si2, class, plan, broker) + sdk.ServiceCatalogClient = linkedClient + retClass, retPlan, retBroker, err := sdk.InstanceParentHierarchy(si) + Expect(err).NotTo(HaveOccurred()) + Expect(retClass.Name).To(Equal(class.Name)) + Expect(retPlan.Name).To(Equal(plan.Name)) + Expect(retBroker.Name).To(Equal(broker.Name)) + actions := linkedClient.Actions() + getClass := testing.GetActionImpl{ + ActionImpl: testing.ActionImpl{ + Verb: "get", + Resource: schema.GroupVersionResource{ + Group: "servicecatalog.k8s.io", + Version: "v1beta1", + Resource: "clusterserviceclasses", + }, + }, + Name: class.Name, + } + getPlan := testing.GetActionImpl{ + ActionImpl: testing.ActionImpl{ + Verb: "get", + Resource: schema.GroupVersionResource{ + Group: "servicecatalog.k8s.io", + Version: "v1beta1", + Resource: "clusterserviceplans", + }, + }, + Name: plan.Name, + } + getBroker := testing.GetActionImpl{ + ActionImpl: testing.ActionImpl{ + Verb: "get", + Resource: schema.GroupVersionResource{ + Group: "servicecatalog.k8s.io", + Version: "v1beta1", + Resource: "clusterservicebrokers", + }, + }, + Name: broker.Name, + } + Expect(actions).Should(ContainElement(getClass)) + Expect(actions).Should(ContainElement(getPlan)) + Expect(actions).Should(ContainElement(getBroker)) + }) + It("Bubbles up errors", func() { + si = &v1beta1.ServiceInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: "foobar_namespace", + }, + Spec: v1beta1.ServiceInstanceSpec{ + ClusterServicePlanRef: &v1beta1.ClusterObjectReference{ + Name: "not_real_plan", + }, + ClusterServiceClassRef: &v1beta1.ClusterObjectReference{ + Name: "not_real_class", + }, + }, + } + badClient := &fake.Clientset{} + errorMessage := "error retrieving thing" + badClient.AddReactor("get", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + badClient.AddReactor("get", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + a, b, c, err := sdk.InstanceParentHierarchy(si) + Expect(a).To(BeNil()) + Expect(b).To(BeNil()) + Expect(c).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + }) + }) + Describe("InstanceToServiceClassAndPlan", func() { + It("Calls the generated v1beta methods with the names of the class and plan from the passed in instance", func() { + class := &v1beta1.ClusterServiceClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_class", + }, + } + plan := &v1beta1.ClusterServicePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar_plan", + }, + Spec: v1beta1.ClusterServicePlanSpec{}, + } + si = &v1beta1.ServiceInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: "foobar_namespace", + }, + Spec: v1beta1.ServiceInstanceSpec{ + ClusterServicePlanRef: &v1beta1.ClusterObjectReference{ + Name: plan.Name, + }, + ClusterServiceClassRef: &v1beta1.ClusterObjectReference{ + Name: class.Name, + }, + }, + } + linkedClient := fake.NewSimpleClientset(si, si2, class, plan) + sdk.ServiceCatalogClient = linkedClient + + retClass, retPlan, err := sdk.InstanceToServiceClassAndPlan(si) + Expect(err).NotTo(HaveOccurred()) + Expect(retClass).To(Equal(class)) + Expect(retPlan).To(Equal(plan)) + actions := linkedClient.Actions() + getClass := testing.GetActionImpl{ + ActionImpl: testing.ActionImpl{ + Verb: "get", + Resource: schema.GroupVersionResource{ + Group: "servicecatalog.k8s.io", + Version: "v1beta1", + Resource: "clusterserviceclasses", + }, + }, + Name: class.Name, + } + getPlan := testing.GetActionImpl{ + ActionImpl: testing.ActionImpl{ + Verb: "get", + Resource: schema.GroupVersionResource{ + Group: "servicecatalog.k8s.io", + Version: "v1beta1", + Resource: "clusterserviceplans", + }, + }, + Name: plan.Name, + } + Expect(actions).Should(ContainElement(getClass)) + Expect(actions).Should(ContainElement(getPlan)) + }) + It("Bubbles up errors", func() { + si = &v1beta1.ServiceInstance{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foobar", + Namespace: "foobar_namespace", + }, + Spec: v1beta1.ServiceInstanceSpec{ + ClusterServicePlanRef: &v1beta1.ClusterObjectReference{ + Name: "not_real_plan", + }, + ClusterServiceClassRef: &v1beta1.ClusterObjectReference{ + Name: "not_real_class", + }, + }, + } + badClient := &fake.Clientset{} + errorMessage := "error retrieving thing" + badClient.AddReactor("get", "clusterserviceclasses", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + badClient.AddReactor("get", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + a, b, err := sdk.InstanceToServiceClassAndPlan(si) + Expect(a).To(BeNil()) + Expect(b).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + }) + }) + Describe("Provision", func() { + It("Calls the v1beta1 Create method with the passed in arguements", func() { + namespace := "cherry_namespace" + instanceName := "cherry" + className := "cherry_class" + planName := "cherry_plan" + params := make(map[string]string) + params["foo"] = "bar" + secrets := make(map[string]string) + secrets["username"] = "admin" + secrets["password"] = "abc123" + + service, err := sdk.Provision(namespace, instanceName, className, planName, params, secrets) + + Expect(err).NotTo(HaveOccurred()) + Expect(service.Namespace).To(Equal(namespace)) + Expect(service.Name).To(Equal(instanceName)) + Expect(service.Spec.PlanReference.ClusterServiceClassExternalName).To(Equal(className)) + Expect(service.Spec.PlanReference.ClusterServicePlanExternalName).To(Equal(planName)) + + actions := svcCatClient.Actions() + Expect(actions[0].Matches("create", "serviceinstances")).To(BeTrue()) + objectFromRequest := actions[0].(testing.CreateActionImpl).Object.(*v1beta1.ServiceInstance) + Expect(objectFromRequest.ObjectMeta.Name).To(Equal(instanceName)) + Expect(objectFromRequest.ObjectMeta.Namespace).To(Equal(namespace)) + Expect(objectFromRequest.Spec.PlanReference.ClusterServiceClassExternalName).To(Equal(className)) + Expect(objectFromRequest.Spec.PlanReference.ClusterServicePlanExternalName).To(Equal(planName)) + Expect(objectFromRequest.Spec.Parameters.Raw).To(Equal([]byte("{\"foo\":\"bar\"}"))) + param := v1beta1.ParametersFromSource{ + SecretKeyRef: &v1beta1.SecretKeyReference{ + Name: "username", + Key: "admin", + }, + } + param2 := v1beta1.ParametersFromSource{ + SecretKeyRef: &v1beta1.SecretKeyReference{ + Name: "password", + Key: "abc123", + }, + } + Expect(objectFromRequest.Spec.ParametersFrom).Should(ConsistOf(param, param2)) + }) + It("Bubbles up errors", func() { + errorMessage := "error retrieving list" + namespace := "cherry_namespace" + instanceName := "cherry" + className := "cherry_class" + planName := "cherry_plan" + params := make(map[string]string) + params["foo"] = "bar" + secrets := make(map[string]string) + secrets["username"] = "admin" + secrets["password"] = "abc123" + badClient := &fake.Clientset{} + badClient.AddReactor("create", "serviceinstances", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + service, err := sdk.Provision(namespace, instanceName, className, planName, params, secrets) + Expect(service).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + }) + }) + Describe("Deprovision", func() { + It("Calls the v1beta1 Delete method wiht the passed in service instance name", func() { + err := sdk.Deprovision(si.Namespace, si.Name) + Expect(err).NotTo(HaveOccurred()) + actions := svcCatClient.Actions() + Expect(actions[0].Matches("delete", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.DeleteActionImpl).Name).To(Equal(si.Name)) + }) + }) + It("Bubbles up errors", func() { + errorMessage := "instance not found" + badClient := &fake.Clientset{} + badClient.AddReactor("delete", "serviceinstances", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + err := sdk.Deprovision(si.Namespace, si.Name) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).To(ContainSubstring(errorMessage)) + actions := badClient.Actions() + Expect(actions[0].Matches("delete", "serviceinstances")).To(BeTrue()) + Expect(actions[0].(testing.DeleteActionImpl).Name).To(Equal(si.Name)) + }) +}) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/plan.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/plan.go index 42e7a27f86d6..4e1989440956 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/plan.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/plan.go @@ -82,3 +82,31 @@ func (sdk *SDK) RetrievePlansByClass(class *v1beta1.ClusterServiceClass, return plans.Items, nil } + +// RetrievePlanByClassAndPlanNames gets a plan by its class/plan name combination. +func (sdk *SDK) RetrievePlanByClassAndPlanNames(className, planName string, +) (*v1beta1.ClusterServicePlan, error) { + class, err := sdk.RetrieveClassByName(className) + if err != nil { + return nil, err + } + + planOpts := v1.ListOptions{ + FieldSelector: fields.AndSelectors( + fields.OneTermEqualSelector(FieldServiceClassRef, class.Name), + fields.OneTermEqualSelector(FieldExternalPlanName, planName), + ).String(), + } + searchResults, err := sdk.ServiceCatalog().ClusterServicePlans().List(planOpts) + if err != nil { + return nil, fmt.Errorf("unable to search plans by class/plan name '%s/%s' (%s)", className, planName, err) + } + if len(searchResults.Items) == 0 { + return nil, fmt.Errorf("plan not found '%s/%s'", className, planName) + } + if len(searchResults.Items) > 1 { + // Note: Should never occur, as class/plan name combo must be unique + return nil, fmt.Errorf("more than one matching plan found for '%s/%s'", className, planName) + } + return &searchResults.Items[0], nil +} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/plan_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/plan_test.go new file mode 100644 index 000000000000..1d9c96876f18 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/plan_test.go @@ -0,0 +1,200 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package servicecatalog_test + +import ( + "fmt" + + "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" + "github.com/kubernetes-incubator/service-catalog/pkg/client/clientset_generated/clientset/fake" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/testing" + + . "github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Plan", func() { + var ( + sdk *SDK + svcCatClient *fake.Clientset + sp *v1beta1.ClusterServicePlan + sp2 *v1beta1.ClusterServicePlan + ) + + BeforeEach(func() { + sp = &v1beta1.ClusterServicePlan{ObjectMeta: metav1.ObjectMeta{Name: "foobar"}} + sp2 = &v1beta1.ClusterServicePlan{ObjectMeta: metav1.ObjectMeta{Name: "barbaz"}} + svcCatClient = fake.NewSimpleClientset(sp, sp2) + sdk = &SDK{ + ServiceCatalogClient: svcCatClient, + } + }) + + Describe("RetrivePlans", func() { + It("Calls the generated v1beta1 List method", func() { + plans, err := sdk.RetrievePlans() + + Expect(err).NotTo(HaveOccurred()) + Expect(plans).Should(ConsistOf(*sp, *sp2)) + Expect(svcCatClient.Actions()[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + }) + It("Bubbles up errors", func() { + errorMessage := "error retrieving list" + badClient := &fake.Clientset{} + badClient.AddReactor("list", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + _, err := sdk.RetrievePlans() + + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + Expect(badClient.Actions()[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + }) + }) + Describe("RetrievePlanByName", func() { + It("Calls the generated v1beta1 List method with the passed in plan name", func() { + planName := sp.Name + singleClient := &fake.Clientset{} + singleClient.AddReactor("list", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, &v1beta1.ClusterServicePlanList{Items: []v1beta1.ClusterServicePlan{*sp}}, nil + }) + sdk.ServiceCatalogClient = singleClient + + plan, err := sdk.RetrievePlanByName(planName) + + Expect(err).NotTo(HaveOccurred()) + Expect(plan.Name).To(Equal(planName)) + actions := singleClient.Actions() + Expect(len(actions)).To(Equal(1)) + Expect(actions[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + opts := fields.Set{"spec.externalName": planName} + Expect(actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Matches(opts)).To(BeTrue()) + }) + It("Bubbles up errors", func() { + planName := "not_real" + errorMessage := "plan not found" + badClient := &fake.Clientset{} + badClient.AddReactor("list", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + plan, err := sdk.RetrievePlanByName(planName) + + Expect(plan).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + actions := badClient.Actions() + Expect(len(actions)).To(Equal(1)) + Expect(actions[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + opts := fields.Set{"spec.externalName": planName} + Expect(actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Matches(opts)).To(BeTrue()) + }) + }) + Describe("RetrievePlanByID", func() { + It("Calls the generated v1beta1 get method with the passed in uuid", func() { + planID := sp.Name + _, err := sdk.RetrievePlanByID(planID) + Expect(err).NotTo(HaveOccurred()) + actions := svcCatClient.Actions() + Expect(len(actions)).To(Equal(1)) + Expect(actions[0].Matches("get", "clusterserviceplans")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(planID)) + }) + It("Bubbles up errors", func() { + planID := "not_real" + errorMessage := "plan not found" + badClient := &fake.Clientset{} + badClient.AddReactor("get", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + plan, err := sdk.RetrievePlanByID(planID) + + Expect(plan).To(BeNil()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring(errorMessage)) + actions := badClient.Actions() + Expect(len(actions)).To(Equal(1)) + Expect(actions[0].Matches("get", "clusterserviceplans")).To(BeTrue()) + Expect(actions[0].(testing.GetActionImpl).Name).To(Equal(planID)) + }) + }) + Describe("RetrievePlansByClass", func() { + It("Calls the generated v1beta1 List method with an opts containing the passed in class' name", func() { + class := &v1beta1.ClusterServiceClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "durian_class", + }, + Spec: v1beta1.ClusterServiceClassSpec{}, + } + plan := &v1beta1.ClusterServicePlan{ + ObjectMeta: metav1.ObjectMeta{ + Name: "durian", + }, + Spec: v1beta1.ClusterServicePlanSpec{ + ClusterServiceClassRef: v1beta1.ClusterObjectReference{ + Name: class.Name, + }, + }, + } + linkedClient := fake.NewSimpleClientset(class, plan) + linkedClient.AddReactor("list", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, &v1beta1.ClusterServicePlanList{Items: []v1beta1.ClusterServicePlan{*plan}}, nil + }) + sdk.ServiceCatalogClient = linkedClient + retPlans, err := sdk.RetrievePlansByClass(class) + Expect(retPlans).To(ConsistOf(*plan)) + Expect(err).NotTo(HaveOccurred()) + actions := linkedClient.Actions() + Expect(len(actions)).To(Equal(1)) + Expect(actions[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + opts := fields.Set{"spec.clusterServiceClassRef.name": class.Name} + Expect(actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Matches(opts)).To(BeTrue()) + }) + It("Bubbles up errors", func() { + errorMessage := "no plans found" + class := &v1beta1.ClusterServiceClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: "durian_class", + }, + Spec: v1beta1.ClusterServiceClassSpec{}, + } + badClient := &fake.Clientset{} + badClient.AddReactor("list", "clusterserviceplans", func(action testing.Action) (bool, runtime.Object, error) { + return true, nil, fmt.Errorf(errorMessage) + }) + sdk.ServiceCatalogClient = badClient + + plans, err := sdk.RetrievePlansByClass(class) + Expect(plans).To(BeNil()) + Expect(err).To(HaveOccurred()) + actions := badClient.Actions() + Expect(len(actions)).To(Equal(1)) + Expect(actions[0].Matches("list", "clusterserviceplans")).To(BeTrue()) + opts := fields.Set{"spec.clusterServiceClassRef.name": class.Name} + Expect(actions[0].(testing.ListActionImpl).GetListRestrictions().Fields.Matches(opts)).To(BeTrue()) + }) + }) +}) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/sdk.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/sdk.go index 3a559cabb04e..0640f59d16f2 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/sdk.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/sdk.go @@ -23,7 +23,7 @@ import ( // SDK wrapper around the generated Go client for the Kubernetes Service Catalog type SDK struct { - ServiceCatalogClient *clientset.Clientset + ServiceCatalogClient clientset.Interface } // ServiceCatalog is the underlying generated Service Catalog versioned interface diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/service_catalog_suite_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/service_catalog_suite_test.go new file mode 100644 index 000000000000..743d001ab7ff --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/pkg/svcat/service-catalog/service_catalog_suite_test.go @@ -0,0 +1,29 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package servicecatalog_test + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "testing" +) + +func TestServiceCatalog(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "ServiceCatalog Suite") +} diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/broker.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/broker.go index 504bfd03aeae..ad7968d201a4 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/broker.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/broker.go @@ -69,7 +69,7 @@ var _ = framework.ServiceCatalogDescribe("ClusterServiceBroker", func() { It("should become ready", func() { By("Creating a Broker") - url := "http://test-broker." + f.Namespace.Name + ".svc.cluster.local" + url := "http://" + brokerName + "." + f.Namespace.Name + ".svc.cluster.local" broker, err := f.ServiceCatalogClientSet.ServicecatalogV1beta1().ClusterServiceBrokers().Create(newTestBroker(brokerName, url)) Expect(err).NotTo(HaveOccurred()) By("Waiting for Broker to be ready") diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/util.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/util.go index 27d2d1748ad9..f361464ac65b 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/util.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/util.go @@ -38,6 +38,7 @@ func NewUPSBrokerPod(name string) *corev1.Pod { Args: []string{ "--port", "8080", + "-alsologtostderr", }, Ports: []corev1.ContainerPort{ { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/walkthrough.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/walkthrough.go index f8d9777a8e7f..0cc18758ab0e 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/walkthrough.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/e2e/walkthrough.go @@ -17,9 +17,12 @@ limitations under the License. package e2e import ( + "bytes" + v1beta1 "github.com/kubernetes-incubator/service-catalog/pkg/apis/servicecatalog/v1beta1" "github.com/kubernetes-incubator/service-catalog/test/e2e/framework" "github.com/kubernetes-incubator/service-catalog/test/util" + "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" . "github.com/onsi/ginkgo" @@ -51,9 +54,19 @@ var _ = framework.ServiceCatalogDescribe("walkthrough", func() { }) AfterEach(func() { + rc, err := f.KubeClientSet.CoreV1().Pods(f.Namespace.Name).GetLogs(upsbrokername, &v1.PodLogOptions{}).Stream() + defer rc.Close() + if err != nil { + framework.Logf("Error getting logs for pod %s: %v", upsbrokername, err) + } else { + buf := new(bytes.Buffer) + buf.ReadFrom(rc) + framework.Logf("Pod %s has the following logs:\n%sEnd %s logs", upsbrokername, buf.String(), upsbrokername) + } + // Delete ups-broker pod and service By("Deleting the ups-broker pod") - err := f.KubeClientSet.CoreV1().Pods(f.Namespace.Name).Delete(upsbrokername, nil) + err = f.KubeClientSet.CoreV1().Pods(f.Namespace.Name).Delete(upsbrokername, nil) Expect(err).NotTo(HaveOccurred()) By("Deleting the ups-broker service") diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_binding_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_binding_test.go index ec7aad57363e..b7b1eb84c7ab 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_binding_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_binding_test.go @@ -481,7 +481,7 @@ func TestDeleteServiceBindingFailureRetry(t *testing.T) { return &osb.UnbindResponse{}, nil } return nil, osb.HTTPStatusCodeError{ - StatusCode: 500, + StatusCode: 500, Description: strPtr("test error unbinding"), } }) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_test.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_test.go index 7fe502283d8a..d1823183a72b 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_test.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/integration/controller_test.go @@ -528,6 +528,85 @@ func TestServiceInstanceDeleteWithAsyncProvisionInProgress(t *testing.T) { } } +// TestServiceBindingDeleteWithAsyncBindInProgress tests that you can delete a +// binding during an async bind operation. Verify the binding is deleted when +// the bind operation completes regardless of success or failure. +func TestServiceBindingDeleteWithAsyncBindInProgress(t *testing.T) { + cases := []struct { + name string + bindSucceeds bool + }{ + { + name: "bind succeeds", + bindSucceeds: true, + }, + { + name: "bind fails", + bindSucceeds: false, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + + // Enable the AsyncBindingOperations feature + utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%v=true", scfeatures.AsyncBindingOperations)) + defer utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("%v=false", scfeatures.AsyncBindingOperations)) + + var done int32 = 0 + ct := controllerTest{ + t: t, + broker: getTestBroker(), + instance: getTestInstance(), + binding: getTestBinding(), + skipVerifyingBindingSuccess: true, + setup: func(ct *controllerTest) { + ct.osbClient.BindReaction.(*fakeosb.BindReaction).Response.Async = true + ct.osbClient.PollBindingLastOperationReaction = fakeosb.DynamicPollBindingLastOperationReaction( + func(_ *osb.BindingLastOperationRequest) (*osb.LastOperationResponse, error) { + state := osb.StateInProgress + d := atomic.LoadInt32(&done) + if d > 0 { + if tc.bindSucceeds { + state = osb.StateSucceeded + } else { + state = osb.StateFailed + } + } + return &osb.LastOperationResponse{State: state}, nil + }) + }, + } + ct.run(func(ct *controllerTest) { + if _, err := util.WaitForBindingCondition(ct.client, ct.binding.Namespace, ct.binding.Name, + v1beta1.ServiceBindingCondition{ + Type: v1beta1.ServiceBindingConditionReady, + Status: v1beta1.ConditionFalse, + Reason: "Binding", + }); err != nil { + t.Fatalf("error waiting for binding to be created asynchronously: %v", err) + } + + if err := ct.client.ServiceBindings(ct.binding.Namespace).Delete(ct.binding.Name, &metav1.DeleteOptions{}); err != nil { + t.Fatalf("failed to delete binding: %v", err) + } + + // notify the thread handling DynamicPollLastOperationReaction that it can end the async op + atomic.StoreInt32(&done, 1) + + if err := util.WaitForBindingToNotExist(ct.client, ct.binding.Namespace, ct.binding.Name); err != nil { + t.Fatalf("error waiting for binding to not exist: %v", err) + } + + // We deleted the binding above, clear it so test cleanup doesn't fail + ct.binding = nil + }) + }) + } +} + func getUpdateInstanceResponseByPollCountReactions(numOfResponses int, stateProgressions []fakeosb.UpdateInstanceReaction) fakeosb.DynamicUpdateInstanceReaction { numberOfPolls := 0 numberOfStates := len(stateProgressions) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/test-dep.sh b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/test-dep.sh index 8ff529366854..21f7596f1535 100755 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/test-dep.sh +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/test-dep.sh @@ -21,7 +21,7 @@ result=0 function cleanup() { popd - rm -r contrib/examples/consumer/vendor + git clean ./contrib/examples/consumer/ -xdf if [[ "${result:-}" != "0" ]]; then echo "A downstream consumer of our client library cannot use dep to vendor Service Catalog. You may need to add a constraint to Gopkg.toml to address." @@ -32,7 +32,9 @@ function cleanup() { pushd contrib/examples/consumer trap "cleanup" EXIT -dep ensure +echo "Running dep ensure..." +dep ensure -v +echo "Compiling the downstream consumer using the resolved dependencies..." go build . echo "Verified that our Gopkg.toml is sufficient for a downstream consumer of our client library." diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/util/util.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/util/util.go index 10a22128428d..3f838395b118 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/util/util.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/test/util/util.go @@ -34,7 +34,8 @@ import ( // WaitForBrokerCondition waits for the status of the named broker to contain // a condition whose type and status matches the supplied one. func WaitForBrokerCondition(client v1beta1servicecatalog.ServicecatalogV1beta1Interface, name string, condition v1beta1.ServiceBrokerCondition) error { - return wait.PollImmediate(500*time.Millisecond, wait.ForeverTestTimeout, + // GetCatalog default timeout time is 60 seconds, so the wait here must be at least that (previously set to 30 seconds) + return wait.PollImmediate(500*time.Millisecond, 3*time.Minute, func() (bool, error) { glog.V(5).Infof("Waiting for broker %v condition %#v", name, condition) broker, err := client.ClusterServiceBrokers().Get(name, metav1.GetOptions{}) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/bind.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/bind.go index 96b73c330c3d..574c738015a1 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/bind.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/bind.go @@ -47,7 +47,7 @@ func (c *client) Bind(r *BindRequest) (*BindResponse, error) { params := map[string]string{} if r.AcceptsIncomplete { - params[asyncQueryParamKey] = "true" + params[AcceptsIncomplete] = "true" } requestBody := &bindRequestBody{ diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/client.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/client.go index 310d134d1108..829e906fb20c 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/client.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/client.go @@ -30,7 +30,6 @@ const ( lastOperationURLFmt = "%s/v2/service_instances/%s/last_operation" bindingLastOperationURLFmt = "%s/v2/service_instances/%s/service_bindings/%s/last_operation" bindingURLFmt = "%s/v2/service_instances/%s/service_bindings/%s" - asyncQueryParamKey = "accepts_incomplete" ) // NewClient is a CreateFunc for creating a new functional Client and @@ -64,6 +63,7 @@ func NewClient(config *ClientConfiguration) (Client, error) { URL: strings.TrimRight(config.URL, "/"), APIVersion: config.APIVersion, EnableAlphaFeatures: config.EnableAlphaFeatures, + Verbose: config.Verbose, httpClient: httpClient, } c.doRequestFunc = c.doRequest @@ -204,16 +204,26 @@ func (c *client) unmarshalResponse(response *http.Response, obj interface{}) err // response. func (c *client) handleFailureResponse(response *http.Response) error { glog.Info("handling failure responses") - brokerResponse := &failureResponseBody{} - if err := c.unmarshalResponse(response, brokerResponse); err != nil { - return HTTPStatusCodeError{StatusCode: response.StatusCode, ResponseError: err} + + httpErr := HTTPStatusCodeError{ + StatusCode: response.StatusCode, + } + + brokerResponse := make(map[string]interface{}) + if err := c.unmarshalResponse(response, &brokerResponse); err != nil { + httpErr.ResponseError = err + return httpErr } - return HTTPStatusCodeError{ - StatusCode: response.StatusCode, - ErrorMessage: brokerResponse.Err, - Description: brokerResponse.Description, + if errorMessage, ok := brokerResponse["error"].(string); ok { + httpErr.ErrorMessage = &errorMessage } + + if description, ok := brokerResponse["description"].(string); ok { + httpErr.Description = &description + } + + return httpErr } func buildOriginatingIdentityHeaderValue(i *OriginatingIdentity) (string, error) { diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/constants.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/constants.go new file mode 100644 index 000000000000..9f35e4ab3c75 --- /dev/null +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/constants.go @@ -0,0 +1,25 @@ +package v2 + +const ( + // AcceptsIncomplete is the name of a query parameter that indicates that + // the client allows a request to complete asynchronously. + AcceptsIncomplete = "accepts_incomplete" + + // VarKeyInstanceID is the name to use for a mux var representing an + // instance ID. + VarKeyInstanceID = "instance_id" + + // VarKeyBindingID is the name to use for a mux var representing a binding + // ID. + VarKeyBindingID = "binding_id" + + // VarKeyServiceID is the name to use for a mux var representing a service ID. + VarKeyServiceID = "service_id" + + // VarKeyPlanID is the name to use for a mux var representing a plan ID. + VarKeyPlanID = "plan_id" + + // VarKeyOperation is the name to use for a mux var representing an + // operation. + VarKeyOperation = "operation" +) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/deprovision_instance.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/deprovision_instance.go index 1dc128980c78..730149ba398b 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/deprovision_instance.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/deprovision_instance.go @@ -13,11 +13,11 @@ func (c *client) DeprovisionInstance(r *DeprovisionRequest) (*DeprovisionRespons fullURL := fmt.Sprintf(serviceInstanceURLFmt, c.URL, r.InstanceID) params := map[string]string{ - serviceIDKey: string(r.ServiceID), - planIDKey: string(r.PlanID), + VarKeyServiceID: string(r.ServiceID), + VarKeyPlanID: string(r.PlanID), } if r.AcceptsIncomplete { - params[asyncQueryParamKey] = "true" + params[AcceptsIncomplete] = "true" } response, err := c.prepareAndDo(http.MethodDelete, fullURL, params, nil, r.OriginatingIdentity) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/interface.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/interface.go index f1ae38b60bb3..168689091407 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/interface.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/interface.go @@ -64,6 +64,8 @@ type ClientConfiguration struct { // CAData holds PEM-encoded bytes (typically read from a root certificates bundle). // This CA certificate will be added to any specified in TLSConfig.RootCAs. CAData []byte + // Verbose is whether the client will log to glog. + Verbose bool } // DefaultClientConfiguration returns a default ClientConfiguration: diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_binding_last_operation.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_binding_last_operation.go index 8726acbc42ac..dfd39a4a2e39 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_binding_last_operation.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_binding_last_operation.go @@ -20,15 +20,15 @@ func (c *client) PollBindingLastOperation(r *BindingLastOperationRequest) (*Last params := map[string]string{} if r.ServiceID != nil { - params[serviceIDKey] = *r.ServiceID + params[VarKeyServiceID] = *r.ServiceID } if r.PlanID != nil { - params[planIDKey] = *r.PlanID + params[VarKeyPlanID] = *r.PlanID } if r.OperationKey != nil { op := *r.OperationKey opStr := string(op) - params[operationKey] = opStr + params[VarKeyOperation] = opStr } response, err := c.prepareAndDo(http.MethodGet, fullURL, params, nil /* request body */, r.OriginatingIdentity) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_last_operation.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_last_operation.go index 78468b4c738d..27e19ffab3b6 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_last_operation.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/poll_last_operation.go @@ -5,12 +5,6 @@ import ( "net/http" ) -const ( - serviceIDKey = "service_id" - planIDKey = "plan_id" - operationKey = "operation" -) - func (c *client) PollLastOperation(r *LastOperationRequest) (*LastOperationResponse, error) { if err := validateLastOperationRequest(r); err != nil { return nil, err @@ -20,15 +14,15 @@ func (c *client) PollLastOperation(r *LastOperationRequest) (*LastOperationRespo params := map[string]string{} if r.ServiceID != nil { - params[serviceIDKey] = *r.ServiceID + params[VarKeyServiceID] = *r.ServiceID } if r.PlanID != nil { - params[planIDKey] = *r.PlanID + params[VarKeyPlanID] = *r.PlanID } if r.OperationKey != nil { op := *r.OperationKey opStr := string(op) - params[operationKey] = opStr + params[VarKeyOperation] = opStr } response, err := c.prepareAndDo(http.MethodGet, fullURL, params, nil /* request body */, r.OriginatingIdentity) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/provision_instance.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/provision_instance.go index 1f2ca532648a..9f528af2a4dc 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/provision_instance.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/provision_instance.go @@ -32,7 +32,7 @@ func (c *client) ProvisionInstance(r *ProvisionRequest) (*ProvisionResponse, err params := map[string]string{} if r.AcceptsIncomplete { - params[asyncQueryParamKey] = "true" + params[AcceptsIncomplete] = "true" } requestBody := &provisionRequestBody{ diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/types.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/types.go index 777ae7335ba5..7087fe2a8476 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/types.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/types.go @@ -27,15 +27,15 @@ type Service struct { // Bindable represents whether a service is bindable. May be overridden // on a per-plan basis by the Plan.Bindable field. Bindable bool `json:"bindable"` - // BindingRetrievable is ALPHA and may change or disappear at any time. - // BindingRetrievable will only be provided if alpha features are + // BindingsRetrievable is ALPHA and may change or disappear at any time. + // BindingsRetrievable will only be provided if alpha features are // enabled. // - // BindingRetrievable represents whether fetching a service binding via + // BindingsRetrievable represents whether fetching a service binding via // a GET on the binding resource's endpoint // (/v2/service_instances/instance-id/service_bindings/binding-id) is // supported for all plans. - BindingRetrievable bool `json:"binding_retrievable,omitempty"` + BindingsRetrievable bool `json:"bindings_retrievable,omitempty"` // PlanUpdatable represents whether instances of this service may be // updated to a different plan. The serialized form 'plan_updateable' is // a mistake that has become written into the API for backward @@ -217,6 +217,8 @@ type UpdateInstanceRequest struct { // unset, indicates that the client does not wish to update the parameters // for an instance. Parameters map[string]interface{} `json:"parameters,omitempty"` + // Previous values contains information about the service instance prior to the update. + PreviousValues *PreviousValues `json:"previous_values,omitempty"` // Context requires a client API version >= 2.12. // // Context is platform-specific contextual information under which the @@ -224,17 +226,23 @@ type UpdateInstanceRequest struct { Context map[string]interface{} `json:"context,omitempty"` // OriginatingIdentity is the identity on the platform of the user making this request. OriginatingIdentity *OriginatingIdentity `json:"originatingIdentity,omitempty"` +} - // The OSB API also has a field called `previous_values` that contains: - // OrgID - // SpaceID - // ServiceID - // PlanID - // - // ...but those fields seem to be a relic of some API design mistakes in - // the past. As such, this client library does not currently support - // them. I will happily change this if someone can present a specific - // example of a broker that requires these fields to be sent. +// PreviousValues represents information about the service instance prior to the update. +type PreviousValues struct { + // ID of the plan prior to the update. If present, MUST be a non-empty string. + PlanID string `json:"plan_id,omitempty"` + // Deprecated; determined to be unnecessary as the value is immutable. ID of the service + // for the service instance. If present, MUST be a non-empty string. + ServiceID string `json:"service_id,omitempty"` + // Deprecated; Organization for the service instance MUST be provided by platforms in the + // top-level field context. ID of the organization specified for the service instance. + // If present, MUST be a non-empty string. + OrgID string `json:"organization_id,omitempty"` + // Deprecated; Space for the service instance MUST be provided by platforms in the top-level + // field context. ID of the space specified for the service instance. If present, MUST be + // a non-empty string. + SpaceID string `json:"space_id,omitempty"` } // UpdateInstanceResponse represents a broker's response to an update instance diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/unbind.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/unbind.go index d6a2bc1e5d08..30a0998b162a 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/unbind.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/unbind.go @@ -26,10 +26,10 @@ func (c *client) Unbind(r *UnbindRequest) (*UnbindResponse, error) { fullURL := fmt.Sprintf(bindingURLFmt, c.URL, r.InstanceID, r.BindingID) params := map[string]string{} - params[serviceIDKey] = r.ServiceID - params[planIDKey] = r.PlanID + params[VarKeyServiceID] = r.ServiceID + params[VarKeyPlanID] = r.PlanID if r.AcceptsIncomplete { - params[asyncQueryParamKey] = "true" + params[AcceptsIncomplete] = "true" } response, err := c.prepareAndDo(http.MethodDelete, fullURL, params, nil, r.OriginatingIdentity) diff --git a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/update_instance.go b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/update_instance.go index f947b06c3371..8b8943c24896 100644 --- a/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/update_instance.go +++ b/cmd/service-catalog/go/src/github.com/kubernetes-incubator/service-catalog/vendor/github.com/pmorie/go-open-service-broker-client/v2/update_instance.go @@ -8,13 +8,11 @@ import ( // internal message body types type updateInstanceRequestBody struct { - ServiceID string `json:"service_id"` - PlanID *string `json:"plan_id,omitempty"` - Parameters map[string]interface{} `json:"parameters,omitempty"` - Context map[string]interface{} `json:"context,omitempty"` - - // Note: this client does not currently support the 'previous_values' - // field of this request body. + ServiceID string `json:"service_id"` + PlanID *string `json:"plan_id,omitempty"` + Parameters map[string]interface{} `json:"parameters,omitempty"` + Context map[string]interface{} `json:"context,omitempty"` + PreviousValues *PreviousValues `json:"previous_values,omitempty"` } func (c *client) UpdateInstance(r *UpdateInstanceRequest) (*UpdateInstanceResponse, error) { @@ -25,13 +23,14 @@ func (c *client) UpdateInstance(r *UpdateInstanceRequest) (*UpdateInstanceRespon fullURL := fmt.Sprintf(serviceInstanceURLFmt, c.URL, r.InstanceID) params := map[string]string{} if r.AcceptsIncomplete { - params[asyncQueryParamKey] = "true" + params[AcceptsIncomplete] = "true" } requestBody := &updateInstanceRequestBody{ - ServiceID: r.ServiceID, - PlanID: r.PlanID, - Parameters: r.Parameters, + ServiceID: r.ServiceID, + PlanID: r.PlanID, + Parameters: r.Parameters, + PreviousValues: r.PreviousValues, } if c.APIVersion.AtLeast(Version2_12()) { @@ -42,7 +41,6 @@ func (c *client) UpdateInstance(r *UpdateInstanceRequest) (*UpdateInstanceRespon if err != nil { return nil, err } - switch response.StatusCode { case http.StatusOK: if err := c.unmarshalResponse(response, &struct{}{}); err != nil {