From 228c63dda009a1d497b3bb568e2329842d25cfb8 Mon Sep 17 00:00:00 2001 From: William Mortl <32373900+WilliamMortlMicrosoft@users.noreply.github.com> Date: Wed, 28 Aug 2019 14:33:27 -0600 Subject: [PATCH] create/update/delete firewall rules for SQL servers (#148) * need to handled unexpected error types...like validation.error (#111) * refactor tests (#90) * improve tests with parallel execution and rm sleep * fix the tests to run on kindcluster * Updates to KV controller from Ace (#80) (#112) * feat: implement keyvault controller * Ace's KV changes with updates * Added an event for the final successful provisioning * Updated changes based on the PR comments * removing unwanted file * making resource group name the one in the keyvault yaml Co-authored-by: Ace Eldeib * Test update (#115) * this needs to exist in the reconciler in order to use controllerutil createorupdate * Feat/add consumer group kind (#117) * add consumer group kind * update tests for consumer group * fix isbeingdeleted * Updates to README - steps for onboarding (#114) * cluster additions * updated docs * Update azure-pipelines.yaml (#119) * Update azure-pipelines.yaml * fix tests (#140) * revert back // +kubebuilder:subresource:status changes - fix broken tests * Devcontainer to Help Onboard New People (#142) * add dev conatiner - wip * DevContainer up and running. * Removed `sleep 80` and replaced with `kubectl wait`. * Run `make set-kindcluster` from docker-compose. * Set timeout on wait. * Added `install-test-dependency` to makefile and dockerfile. * Update README - Create SP with contribution rights. * Updated README with details on using devcontainer. * Stuff that wanted me to commit. * Reverted changes made to `docker-build` in Makefile. * pass future where possible instead of bool (#121) * first commit on Amanda's branch * first * before properties * test not tested * test works * unit tests work, needs firewall rules * addresses feedback * erin's feedback * janani's change, pass future * async works much better now * janani feedback * screwed up interface prototype * firewall settings and unit tests * firewall rule deletion --- .devcontainer/Dockerfile | 91 ++++ .devcontainer/devcontainer.json | 24 + .devcontainer/docker-compose.yml | 24 + .gitignore | 1 - Makefile | 82 ++-- PROJECT | 3 + README.md | 156 +++++-- api/v1/consumergroup_types.go | 90 ++++ api/v1/consumergroup_types_test.go | 76 +++ api/v1/eventhub_types.go | 1 - api/v1/zz_generated.deepcopy.go | 89 ++++ .../azure.microsoft.com_consumergroups.yaml | 432 ++++++++++++++++++ .../bases/azure.microsoft.com_eventhubs.yaml | 2 - config/crd/kustomization.yaml | 12 +- .../cainjection_in_consumergroups.yaml | 8 + .../patches/webhook_in_consumergroups.yaml | 17 + config/default/manager_image_patch.yaml | 2 +- config/rbac/role.yaml | 67 +-- config/samples/azure_v1_consumergroup.yaml | 9 + controllers/consumergroup_controller.go | 184 ++++++++ .../consumergroup_controller_finalizer.go | 57 +++ controllers/consumergroup_controller_test.go | 100 ++++ controllers/eventhub_controller.go | 10 +- controllers/eventhub_controller_test.go | 6 +- controllers/suite_test.go | 12 + devops/azure-pipelines.yaml | 16 +- go.mod | 5 +- go.sum | 34 ++ main.go | 9 + pkg/errhelp/errors.go | 4 - .../eventhubs/consumergroup.go | 65 +++ .../eventhubs/consumergroup_test.go | 90 ++++ .../sqlclient/endtoend_test.go | 129 ++++-- .../sqlclient/resourceclient.go | 17 +- .../sqlclient/sqlclient_godsk.go | 195 ++++++-- .../sqlclient/sqlproperties.go | 44 +- 36 files changed, 1900 insertions(+), 263 deletions(-) create mode 100644 .devcontainer/Dockerfile create mode 100644 .devcontainer/devcontainer.json create mode 100644 .devcontainer/docker-compose.yml create mode 100644 api/v1/consumergroup_types.go create mode 100644 api/v1/consumergroup_types_test.go create mode 100644 config/crd/bases/azure.microsoft.com_consumergroups.yaml create mode 100644 config/crd/patches/cainjection_in_consumergroups.yaml create mode 100644 config/crd/patches/webhook_in_consumergroups.yaml create mode 100644 config/samples/azure_v1_consumergroup.yaml create mode 100644 controllers/consumergroup_controller.go create mode 100644 controllers/consumergroup_controller_finalizer.go create mode 100644 controllers/consumergroup_controller_test.go create mode 100644 pkg/resourcemanager/eventhubs/consumergroup.go create mode 100644 pkg/resourcemanager/eventhubs/consumergroup_test.go diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 00000000000..925ea3756c3 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,91 @@ +#------------------------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information. +#------------------------------------------------------------------------------------------------------------- + +FROM golang:1.12.5 + +# Avoid warnings by switching to noninteractive +ENV DEBIAN_FRONTEND=noninteractive + +# Configure apt, install packages and tools +RUN apt-get update \ + && apt-get -y install --no-install-recommends apt-utils 2>&1 \ + # + # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed + && apt-get -y install git procps lsb-release \ + # + # Install gocode-gomod + && go get -x -d github.com/stamblerre/gocode 2>&1 \ + && go build -o gocode-gomod github.com/stamblerre/gocode \ + && mv gocode-gomod $GOPATH/bin/ \ + # + # Install Go tools + && go get -u -v \ + github.com/mdempsky/gocode \ + github.com/uudashr/gopkgs/cmd/gopkgs \ + github.com/ramya-rao-a/go-outline \ + github.com/acroca/go-symbols \ + github.com/godoctor/godoctor \ + golang.org/x/tools/cmd/guru \ + golang.org/x/tools/cmd/gorename \ + github.com/rogpeppe/godef \ + github.com/zmb3/gogetdoc \ + github.com/haya14busa/goplay/cmd/goplay \ + github.com/sqs/goreturns \ + github.com/josharian/impl \ + github.com/davidrjenni/reftools/cmd/fillstruct \ + github.com/fatih/gomodifytags \ + github.com/cweill/gotests/... \ + golang.org/x/tools/cmd/goimports \ + golang.org/x/lint/golint \ + golang.org/x/tools/cmd/gopls \ + github.com/alecthomas/gometalinter \ + honnef.co/go/tools/... \ + github.com/golangci/golangci-lint/cmd/golangci-lint \ + github.com/mgechev/revive \ + github.com/derekparker/delve/cmd/dlv 2>&1 \ + # + # Clean up + && apt-get autoremove -y \ + && apt-get clean -y \ + && rm -rf /var/lib/apt/lists/* + +RUN apt-get update \ + # + # Install Docker CE CLI + && apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common lsb-release \ + && curl -fsSL https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/gpg | apt-key add - 2>/dev/null \ + && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/$(lsb_release -is | tr '[:upper:]' '[:lower:]') $(lsb_release -cs) stable" \ + && apt-get update \ + && apt-get install -y docker-ce-cli \ + # + # Install kubectl + && curl -sSL -o /usr/local/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl \ + && chmod +x /usr/local/bin/kubectl \ + # + # Install Helm + && curl -s https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash - + +# Verify git, process tools installed +RUN apt-get -y install git procps wget nano zsh inotify-tools jq +RUN wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh -O - | zsh || true + +ENV PATH="/usr/local/kubebuilder/bin:${PATH}" + +ENV GO111MODULE=on + +# Set the default shell to bash instead of sh +ENV AZURE_CLIENT_ID="" +ENV AZURE_CLIENT_SECRET="" +ENV AZURE_SUBSCRIPTION_ID="" +ENV AZURE_TENANT_ID="" +ENV KUBECONFIG="/root/.kube/kind-config-kind" + +COPY ./Makefile ./ +RUN make install-kind +RUN make install-kubebuilder +RUN make install-kustomize +RUN make install-test-dependency + +ENV SHELL /bin/bash diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000000..2ff8884aa04 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +// If you want to run as a non-root user in the container, see .devcontainer/docker-compose.yml. +{ + "name": "Go", + "dockerComposeFile": "docker-compose.yml", + "service": "docker-in-docker", + "workspaceFolder": "/workspace", + "shutdownAction": "stopCompose", + "extensions": [ + "ms-azuretools.vscode-docker", + "ms-vscode.go" + ], + "settings": { + "terminal.integrated.shell.linux": "zsh", + "go.gopath": "/go", + "go.inferGopath": true, + "go.useLanguageServer": true, + "go.toolsEnvVars": { + "GO111MODULE": "on" + }, + "remote.extensionKind": { + "ms-azuretools.vscode-docker": "workspace" + } + } +} \ No newline at end of file diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml new file mode 100644 index 00000000000..d9c884e4c7d --- /dev/null +++ b/.devcontainer/docker-compose.yml @@ -0,0 +1,24 @@ +version: '3' +services: + docker-in-docker: + build: + context: ../ + dockerfile: .devcontainer/Dockerfile + network_mode: "host" + ports: + - 5002:5001 + volumes: + # Update this to wherever you want VS Code to mount the folder of your project + - ..:/workspace + + # This lets you avoid setting up Git again in the container + - ~/.gitconfig:/root/.gitconfig + - ~/.ssh:/root/.ssh:ro # does not work on Windows! Will need to generate in container :( + # Forwarding the socket is optional, but lets docker work inside the container if you install the Docker CLI. + # See the docker-in-docker-compose definition for details on how to install it. + - /var/run/docker.sock:/var/run/docker.sock + + # Overrides default command so things don't shut down after the process ends - useful for debugging + command: bash -c "cd /workspace/ && make set-kindcluster && sleep infinity" + env_file: + - .env \ No newline at end of file diff --git a/.gitignore b/.gitignore index 4fa06d60840..12c5e121e94 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ testlogs.txt .env __debug_bin .vscode -.devcontainer .DS_Store cover-existing.html coverage-existing.txt diff --git a/Makefile b/Makefile index a7425b076e5..eb5dbb1af75 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,3 @@ - # Image URL to use all building/pushing image targets IMG ?= controller:latest # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) @@ -74,22 +73,7 @@ else CONTROLLER_GEN=$(shell which controller-gen) endif -set-kindcluster: -ifeq (,$(shell which kind)) - @echo "installing kind" - GO111MODULE="on" go get sigs.k8s.io/kind@v0.4.0 -else - @echo "kind has been installed" -endif - #KUBECONFIG=$(shell kind get kubeconfig-path --name="kind") - #$(shell export KUBECONFIG="$(kind get kubeconfig-path --name="kind")") -ifeq ($(shell kind get kubeconfig-path --name="kind"),$(KUBECONFIG)) - @echo "kubeconfig-path points to kind path" -else - @echo "please run below command in your shell and then re-run make set-kindcluster" - @echo "\e[31mexport KUBECONFIG=$(shell kind get kubeconfig-path --name="kind")\e[0m" - @exit 111 -endif +create-kindcluster: ifeq (,$(shell kind get clusters)) @echo "no kind cluster" else @@ -98,10 +82,21 @@ else endif @echo "creating kind cluster" kind create cluster - export KUBECONFIG="$(kind get kubeconfig-path --name="kind")" + +set-kindcluster: install-kind +ifeq (${shell kind get kubeconfig-path --name="kind"},${KUBECONFIG}) + @echo "kubeconfig-path points to kind path" +else + @echo "please run below command in your shell and then re-run make set-kindcluster" + @echo "\e[31mexport KUBECONFIG=$(shell kind get kubeconfig-path --name="kind")\e[0m" + @exit 111 +endif + make create-kindcluster + @echo "getting value of KUBECONFIG" - kind get kubeconfig-path --name="kind" @echo ${KUBECONFIG} + @echo "getting value of kind kubeconfig-path" + kubectl cluster-info kubectl create namespace azureoperator-system kubectl --namespace azureoperator-system \ @@ -111,33 +106,60 @@ endif --from-literal=AZURE_SUBSCRIPTION_ID=${AZURE_SUBSCRIPTION_ID} \ --from-literal=AZURE_TENANT_ID=${AZURE_TENANT_ID} - kubectl create namespace cert-manager - kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true - kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.9.0/cert-manager.yaml + make install-cert-manager #create image and load it into cluster IMG="docker.io/controllertest:1" make docker-build kind load docker-image docker.io/controllertest:1 --loglevel "trace" make install kubectl get namespaces - @echo "sleep 80 seconds to get the cert pods running" - sleep 80 - @echo "end of sleep" kubectl get pods --namespace cert-manager + @echo "Waiting for cert-manager to be ready" + kubectl wait pod -n cert-manager --for condition=ready --timeout=60s --all @echo "all the pods should be running" make deploy + sed -i'' -e 's@image: .*@image: '"IMAGE_URL"'@' ./config/default/manager_image_patch.yaml + +install-kind: +ifeq (,$(shell which kind)) + @echo "installing kind" + GO111MODULE="on" go get sigs.k8s.io/kind@v0.4.0 +else + @echo "kind has been installed" +endif + +install-kubebuilder: +ifeq (,$(shell which kubebuilder)) + @echo "installing kubebuilder" + # download kubebuilder and extract it to tmp + curl -sL https://go.kubebuilder.io/dl/2.0.0-rc.0/$(shell go env GOOS)/$(shell go env GOARCH) | tar -xz -C /tmp/ + # move to a long-term location and put it on your path + # (you'll need to set the KUBEBUILDER_ASSETS env var if you put it somewhere else) + mv /tmp/kubebuilder_2.0.0-rc.0_$(shell go env GOOS)_$(shell go env GOARCH) /usr/local/kubebuilder + export PATH=$PATH:/usr/local/kubebuilder/bin +else + @echo "kubebuilder has been installed" +endif install-kustomize: ifeq (,$(shell which kustomize)) @echo "installing kustomize" # download kustomize - sudo mkdir -p /usr/local/kustomize/ - sudo curl -o /usr/local/kubebuilder/bin/kustomize -sL "https://go.kubebuilder.io/kustomize/$(shell go env GOOS)/$(shell go env GOARCH)" + curl -o /usr/local/kubebuilder/bin/kustomize -sL "https://go.kubebuilder.io/kustomize/$(shell go env GOOS)/$(shell go env GOARCH)" # set permission - sudo chmod a+x /usr/local/kubebuilder/bin/kustomize - # export path + chmod a+x /usr/local/kubebuilder/bin/kustomize $(shell which kustomize) - else @echo "kustomize has been installed" endif + +install-cert-manager: + kubectl create namespace cert-manager + kubectl label namespace cert-manager certmanager.k8s.io/disable-validation=true + kubectl apply -f https://github.com/jetstack/cert-manager/releases/download/v0.9.0/cert-manager.yaml + +install-test-dependency: + go get -u github.com/jstemmer/go-junit-report \ + && go get github.com/axw/gocov/gocov \ + && go get github.com/AlekSi/gocov-xml \ + && go get golang.org/x/tools/cmd/cover \ No newline at end of file diff --git a/PROJECT b/PROJECT index 270d2537d7a..96cf4e0276e 100644 --- a/PROJECT +++ b/PROJECT @@ -23,3 +23,6 @@ resources: - group: azure version: v1 kind: KeyVault +- group: azure + version: v1 + kind: ConsumerGroup diff --git a/README.md b/README.md index c46c7036142..316878e9c00 100644 --- a/README.md +++ b/README.md @@ -9,19 +9,31 @@ Kubernetes offers the facility of extending it's API through the concept of 'Operators' ([Introducing Operators: Putting Operational Knowledge into Software](https://coreos.com/blog/introducing-operators.html)). This repository contains the resources and code to provision a Resource group and Azure Event Hub using Kubernetes operator. The Azure Operator comprises of: -- The golang application is a Kubernetes controller that watches Customer Resource Definitions (CRDs) that define a Resource Group and Event Hub + +- The golang application is a Kubernetes controller that watches Customer Resource Definitions (CRDs) that define a Resource Group and Event Hub The project was built using 1. [Kubebuilder](https://book.kubebuilder.io/) +## Building and Running from Source + ### Prerequisites And Assumptions 1. You have GoLang installed. -2. You have the kubectl command line (kubectl CLI) installed. -3. You have acess to a Kubernetes cluster. It can be a local hosted Cluster like [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/), [Kind](https://github.com/kubernetes-sigs/kind) or, Docker for desktop installed localy with RBAC enabled. if you opt for Azure Kubernetes Service ([AKS](https://azure.microsoft.com/en-au/services/kubernetes-service/)), you can use: `az aks get-credentials --resource-group $RG_NAME --name $Cluster_NAME` -Kubectl: Client version 1.14 Server Version 1.12 -4. [kustomize](https://github.com/kubernetes-sigs/kustomize) is also needed +2. [Docker](https://docs.docker.com/install/) is installed and running. +3. You have the kubectl command line (kubectl CLI) installed. +4. You have access to a Kubernetes cluster. + - It can be a local hosted Cluster like + [Minikube](https://kubernetes.io/docs/tasks/tools/install-minikube/), + [Kind](https://github.com/kubernetes-sigs/kind) or Docker for desktop installed locally with RBAC enabled. + - If you opt for Azure Kubernetes Service ([AKS](https://azure.microsoft.com/en-au/services/kubernetes-service/)), you can use: + `az aks get-credentials --resource-group $RG_NAME --name $Cluster_NAME` + - Kubectl: Client version 1.14 Server Version 1.12 + + **Note:** it is recommended to use [Kind](https://github.com/kubernetes-sigs/kind) as it is needed for testing Webhooks. +5. Install [Kubebuilder](https://book.kubebuilder.io/), following the linked installation instructions. +6. [Kustomize](https://github.com/kubernetes-sigs/kustomize) is also needed. This must be installed via `make install-kustomize` (see section below). Basic commands to check your cluster @@ -32,54 +44,114 @@ Basic commands to check your cluster kubectl get pods -n kube-system ``` -5. [Cert Manager](https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html) -```shell -kubectl get secret webhook-server-cert -n azureoperator-system -o yaml >certs.txt -``` -you can use `https://inbrowser.tools/` and exctarct ca.crt,tls.crt and tls.key +### Quick Start + +If you're using VSCode with [Remote - Containers](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) extensions installed, you quickly have you're environment set up and ready to go with everything you need to get started. + +1. Open this project in VSCode. +2. Inside `.devcontainer`, create a file called `.env` and using the following template, copy your Service Principal's details. + + ```txt + AZURE_CLIENT_ID= + + AZURE_CLIENT_SECRET= + + AZURE_SUBSCRIPTION_ID= -### Run Souce Code + AZURE_TENANT_ID= + ``` -1. Clone the repo +3. Open the Command Pallet (`Command+Shift+P` on MacOS or `CTRL+Shift+P` on Windows), type `Remote-Containers: Open Folder in Container...` and hit enter. +4. VSCode will relaunch and start building our development container. This will install all the necessary dependencies required for you to begin developing. +5. Once the container has finished building, you can now start testing your Azure Service Operator within your own local kubernetes environment. -2. Install the azure_v1_eventhub CRD in the configured Kubernetes cluster folder ~/.kube/config, -run `kubectl apply -f config/crd/bases` or `make install` +**Note**: after the DevContainer has finished building, the kind cluster will initialising and installing the Azure Service Operator in the background. This will take some time before it is available. + +To see when the kind cluster is ready, use `docker ps -a` to list your running containers, look for `IMAGE` with the name `azure-service-operator_devcontainer_docker-in-docker...`. Using that image's `CONTAINER ID`, use `docker logs -f CONTAINER ID` to view the logs from the container setting up your cluster. + +### Getting started + +1. Clone the repository from the following folder `/src/github.com/Azure`. + +2. Make sure the environment variable `GO111MODULE=on` is set. 3. Update the values in `azure_v1_eventhub.yaml` to reflect the resource group and event hub you want to provision -4. you need kind to test webhooks +4. Install [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/) -```shell - GO111MODULE="on" go get sigs.k8s.io/kind@v0.4.0 && kind create cluster - kind create cluster - export KUBECONFIG="$(kind get kubeconfig-path --name="kind")" - kubectl cluster-info - IMG="docker.io/yourimage:tag" make docker-build - kind load docker-image docker.io/yourimage:tag --loglevel "trace" - make deploy -``` + ```shell + GO111MODULE="on" go get sigs.k8s.io/kind@v0.4.0 && kind create cluster + kind create cluster + export KUBECONFIG="$(kind get kubeconfig-path --name="kind")" + kubectl cluster-info + IMG="docker.io/yourimage:tag" make docker-build + kind load docker-image docker.io/yourimage:tag --loglevel "trace" + make deploy + ``` -4. Set the environment variables AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, AZURE_SUBSCRIPTION_ID, REQUEUE_AFTER -If you are running it on Windows the environment variables should not have quotes. It should be set in this way: -SET AZURE_TENANT_ID=11xxxx-xxx-xxx-xxx-xxxxx -and the VSCode should be run from the same session/command window +5. Create a Service Principal + If you don't have a Service Principal create one from the Azure CLI: -5. Set the azureoperatorsettings secrete + ```bash + az ad sp create-for-rbac --role Contributor + ``` -```shell -Create the azureoperator-system namespace -kubectl --namespace azureoperator-system \ - create secret generic azureoperatorsettings \ - --from-literal=AZURE_CLIENT_ID="xxxx" \ - --from-literal=AZURE_CLIENT_SECRET="xxxxx" \ - --from-literal=AZURE_SUBSCRIPTION_ID="xxxx" \ - --from-literal=AZURE_TENANT_ID="xxxxx" -``` -### How to extend the operator and build your own images + Then make sure this service principal has rights assigned to provision resources on your Azure account. + +6. Set the environment variables `AZURE_TENANT_ID`, `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_SUBSCRIPTION_ID`, `REQUEUE_AFTER`. + + If you are running it on Windows the environment variables should not have quotes. + + It should be set in this way: + `SET AZURE_TENANT_ID=11xxxx-xxx-xxx-xxx-xxxxx` + and the VSCode should be run from the same session/command window + +7. Set up the Cluster + + If you are using Kind: + + ```shell + make set-kindcluster + ``` + + If you are not using Kind, it's a manual process, as follows: + + a. Create the namespace + + ```shell + kubectl create namespace azureoperator-system + ``` + + b. Set the azureoperatorsettings secret + + ```shell + kubectl --namespace azureoperator-system \ + create secret generic azureoperatorsettings \ + --from-literal=AZURE_CLIENT_ID="$AZURE_CLIENT_ID" \ + --from-literal=AZURE_CLIENT_SECRET="$AZURE_CLIENT_SECRET" \ + --from-literal=AZURE_SUBSCRIPTION_ID="$AZURE_SUBSCRIPTION_ID" \ + --from-literal=AZURE_TENANT_ID="$AZURE_TENANT_ID" + ``` + + c. [Cert Manager](https://docs.cert-manager.io/en/latest/getting-started/install/kubernetes.html) + + ```shell + kubectl get secret webhook-server-cert -n azureoperator-system -o yaml > certs.txt + ``` + + you can use `https://inbrowser.tools/` and extract `ca.crt`, `tls.crt` and `tls.key` + +8. Install [kustomize](https://github.com/kubernetes-sigs/kustomize) using `make install-kustomize`. + +9. Install the azure_v1_eventhub CRD in the configured Kubernetes cluster folder ~/.kube/config, + + run `kubectl apply -f config/crd/bases` or `make install` + +## How to extend the operator and build your own images -#### Updating the Azure operator: +### Updating the Azure operator -This Repo is generated by [Kubebuilder](https://book.kubebuilder.io/). +This repository is generated by [Kubebuilder](https://book.kubebuilder.io/). To Extend the operator `github.com/Azure/azure-service-operator`: @@ -93,7 +165,7 @@ To Extend the operator `github.com/Azure/azure-service-operator`: 8. Build `make build` 9. Deploy `make deploy` -# Contributing +## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us diff --git a/api/v1/consumergroup_types.go b/api/v1/consumergroup_types.go new file mode 100644 index 00000000000..d7dd1c5de99 --- /dev/null +++ b/api/v1/consumergroup_types.go @@ -0,0 +1,90 @@ +/* + +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 v1 + +import ( + helpers "github.com/Azure/azure-service-operator/pkg/helpers" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// ConsumerGroupSpec defines the desired state of ConsumerGroup +type ConsumerGroupSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + ResourceGroupName string `json:"resourcegroup,omitempty"` + NamespaceName string `json:"namespace,omitempty"` + EventhubName string `json:"eventhub,omitempty"` +} + +// ConsumerGroupStatus defines the observed state of ConsumerGroup +type ConsumerGroupStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + Provisioning bool `json:"provisioning,omitempty"` + Provisioned bool `json:"provisioned,omitempty"` +} + +// +kubebuilder:object:root=true + +// ConsumerGroup is the Schema for the consumergroups API +type ConsumerGroup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ConsumerGroupSpec `json:"spec,omitempty"` + Status ConsumerGroupStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// ConsumerGroupList contains a list of ConsumerGroup +type ConsumerGroupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []ConsumerGroup `json:"items"` +} + +func init() { + SchemeBuilder.Register(&ConsumerGroup{}, &ConsumerGroupList{}) +} + +func init() { + SchemeBuilder.Register(&ConsumerGroup{}, &ConsumerGroupList{}) +} + +func (consumerGroup *ConsumerGroup) IsBeingDeleted() bool { + return !consumerGroup.ObjectMeta.DeletionTimestamp.IsZero() +} + +func (consumerGroup *ConsumerGroup) IsSubmitted() bool { + return consumerGroup.Status.Provisioning || consumerGroup.Status.Provisioned + +} + +func (consumerGroup *ConsumerGroup) HasFinalizer(finalizerName string) bool { + return helpers.ContainsString(consumerGroup.ObjectMeta.Finalizers, finalizerName) +} + +func (consumerGroup *ConsumerGroup) AddFinalizer(finalizerName string) { + consumerGroup.ObjectMeta.Finalizers = append(consumerGroup.ObjectMeta.Finalizers, finalizerName) +} + +func (consumerGroup *ConsumerGroup) RemoveFinalizer(finalizerName string) { + consumerGroup.ObjectMeta.Finalizers = helpers.RemoveString(consumerGroup.ObjectMeta.Finalizers, finalizerName) +} diff --git a/api/v1/consumergroup_types_test.go b/api/v1/consumergroup_types_test.go new file mode 100644 index 00000000000..6d326e40322 --- /dev/null +++ b/api/v1/consumergroup_types_test.go @@ -0,0 +1,76 @@ +/* + +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 v1 + +import ( + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "golang.org/x/net/context" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +// These tests are written in BDD-style using Ginkgo framework. Refer to +// http://onsi.github.io/ginkgo to learn more. + +var _ = Describe("ConsumerGroup", func() { + var ( + key types.NamespacedName + created, fetched *ConsumerGroup + ) + + BeforeEach(func() { + // Add any setup steps that needs to be executed before each test + }) + + AfterEach(func() { + // Add any teardown steps that needs to be executed after each test + }) + + // Add Tests for OpenAPI validation (or additonal CRD features) specified in + // your API definition. + // Avoid adding tests for vanilla CRUD operations because they would + // test Kubernetes API server, which isn't the goal here. + Context("Create API", func() { + + It("should create an object successfully", func() { + + key = types.NamespacedName{ + Name: "foo", + Namespace: "default", + } + created = &ConsumerGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: "foo", + Namespace: "default", + }} + + By("creating an API obj") + Expect(k8sClient.Create(context.TODO(), created)).To(Succeed()) + + fetched = &ConsumerGroup{} + Expect(k8sClient.Get(context.TODO(), key, fetched)).To(Succeed()) + Expect(fetched).To(Equal(created)) + + By("deleting the created object") + Expect(k8sClient.Delete(context.TODO(), created)).To(Succeed()) + Expect(k8sClient.Get(context.TODO(), key, created)).ToNot(Succeed()) + }) + + }) + +}) diff --git a/api/v1/eventhub_types.go b/api/v1/eventhub_types.go index b3bdfb850ad..9b6f071a43c 100644 --- a/api/v1/eventhub_types.go +++ b/api/v1/eventhub_types.go @@ -72,7 +72,6 @@ type EventhubProperties struct { } // +kubebuilder:object:root=true -// +kubebuilder:subresource:status // Eventhub is the Schema for the eventhubs API type Eventhub struct { metav1.TypeMeta `json:",inline"` diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index b5e4dabaf70..a1c583ca105 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -23,6 +23,95 @@ import ( "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsumerGroup) DeepCopyInto(out *ConsumerGroup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerGroup. +func (in *ConsumerGroup) DeepCopy() *ConsumerGroup { + if in == nil { + return nil + } + out := new(ConsumerGroup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConsumerGroup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsumerGroupList) DeepCopyInto(out *ConsumerGroupList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]ConsumerGroup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerGroupList. +func (in *ConsumerGroupList) DeepCopy() *ConsumerGroupList { + if in == nil { + return nil + } + out := new(ConsumerGroupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ConsumerGroupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsumerGroupSpec) DeepCopyInto(out *ConsumerGroupSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerGroupSpec. +func (in *ConsumerGroupSpec) DeepCopy() *ConsumerGroupSpec { + if in == nil { + return nil + } + out := new(ConsumerGroupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsumerGroupStatus) DeepCopyInto(out *ConsumerGroupStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsumerGroupStatus. +func (in *ConsumerGroupStatus) DeepCopy() *ConsumerGroupStatus { + if in == nil { + return nil + } + out := new(ConsumerGroupStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Eventhub) DeepCopyInto(out *Eventhub) { *out = *in diff --git a/config/crd/bases/azure.microsoft.com_consumergroups.yaml b/config/crd/bases/azure.microsoft.com_consumergroups.yaml new file mode 100644 index 00000000000..8d00e1f0cd1 --- /dev/null +++ b/config/crd/bases/azure.microsoft.com_consumergroups.yaml @@ -0,0 +1,432 @@ + +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + creationTimestamp: null + name: consumergroups.azure.microsoft.com +spec: + group: azure.microsoft.com + names: + kind: ConsumerGroup + plural: consumergroups + scope: "" + validation: + openAPIV3Schema: + description: ConsumerGroup is the Schema for the consumergroups API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + metadata: + description: ObjectMeta is metadata that all persisted resources must have, + which includes all objects users must create. + properties: + annotations: + additionalProperties: + type: string + description: 'Annotations is an unstructured key value map stored with + a resource that may be set by external tools to store and retrieve + arbitrary metadata. They are not queryable and should be preserved + when modifying objects. More info: http://kubernetes.io/docs/user-guide/annotations' + type: object + clusterName: + description: The name of the cluster which the object belongs to. This + is used to distinguish resources with same name and namespace in different + clusters. This field is not set anywhere right now and apiserver is + going to ignore it if set in create or update request. + type: string + creationTimestamp: + description: "CreationTimestamp is a timestamp representing the server + time when this object was created. It is not guaranteed to be set + in happens-before order across separate operations. Clients may not + set this value. It is represented in RFC3339 form and is in UTC. \n + Populated by the system. Read-only. Null for lists. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + format: date-time + type: string + deletionGracePeriodSeconds: + description: Number of seconds allowed for this object to gracefully + terminate before it will be removed from the system. Only set when + deletionTimestamp is also set. May only be shortened. Read-only. + format: int64 + type: integer + deletionTimestamp: + description: "DeletionTimestamp is RFC 3339 date and time at which this + resource will be deleted. This field is set by the server when a graceful + deletion is requested by the user, and is not directly settable by + a client. The resource is expected to be deleted (no longer visible + from resource lists, and not reachable by name) after the time in + this field, once the finalizers list is empty. As long as the finalizers + list contains items, deletion is blocked. Once the deletionTimestamp + is set, this value may not be unset or be set further into the future, + although it may be shortened or the resource may be deleted prior + to this time. For example, a user may request that a pod is deleted + in 30 seconds. The Kubelet will react by sending a graceful termination + signal to the containers in the pod. After that 30 seconds, the Kubelet + will send a hard termination signal (SIGKILL) to the container and + after cleanup, remove the pod from the API. In the presence of network + partitions, this object may still exist after this timestamp, until + an administrator or automated process can determine the resource is + fully terminated. If not set, graceful deletion of the object has + not been requested. \n Populated by the system when a graceful deletion + is requested. Read-only. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata" + format: date-time + type: string + finalizers: + description: Must be empty before the object is deleted from the registry. + Each entry is an identifier for the responsible component that will + remove the entry from the list. If the deletionTimestamp of the object + is non-nil, entries in this list can only be removed. + items: + type: string + type: array + generateName: + description: "GenerateName is an optional prefix, used by the server, + to generate a unique name ONLY IF the Name field has not been provided. + If this field is used, the name returned to the client will be different + than the name passed. This value will also be combined with a unique + suffix. The provided value has the same validation rules as the Name + field, and may be truncated by the length of the suffix required to + make the value unique on the server. \n If this field is specified + and the generated name exists, the server will NOT return a 409 - + instead, it will either return 201 Created or 500 with Reason ServerTimeout + indicating a unique name could not be found in the time allotted, + and the client should retry (optionally after the time indicated in + the Retry-After header). \n Applied only if Name is not specified. + More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#idempotency" + type: string + generation: + description: A sequence number representing a specific generation of + the desired state. Populated by the system. Read-only. + format: int64 + type: integer + initializers: + description: "An initializer is a controller which enforces some system + invariant at object creation time. This field is a list of initializers + that have not yet acted on this object. If nil or empty, this object + has been completely initialized. Otherwise, the object is considered + uninitialized and is hidden (in list/watch and get calls) from clients + that haven't explicitly asked to observe uninitialized objects. \n + When an object is created, the system will populate this list with + the current set of initializers. Only privileged users may set or + modify this list. Once it is empty, it may not be modified further + by any user. \n DEPRECATED - initializers are an alpha field and will + be removed in v1.15." + properties: + pending: + description: Pending is a list of initializers that must execute + in order before this object is visible. When the last pending + initializer is removed, and no failing result is set, the initializers + struct will be set to nil and the object is considered as initialized + and visible to all clients. + items: + description: Initializer is information about an initializer that + has not yet completed. + properties: + name: + description: name of the process that is responsible for initializing + this object. + type: string + required: + - name + type: object + type: array + result: + description: If result is set with the Failure field, the object + will be persisted to storage and then deleted, ensuring that other + clients can observe the deletion. + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this + representation of an object. Servers should convert recognized + schemas to the latest internal value, and may reject unrecognized + values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources' + type: string + code: + description: Suggested HTTP return code for this status, 0 if + not set. + format: int32 + type: integer + details: + description: Extended data associated with the reason. Each + reason may define its own extended details. This field is + optional and the data returned is not guaranteed to conform + to any schema except that defined by the reason type. + properties: + causes: + description: The Causes array includes more details associated + with the StatusReason failure. Not all StatusReasons may + provide detailed causes. + items: + description: StatusCause provides more information about + an api.Status failure, including cases when multiple + errors are encountered. + properties: + field: + description: "The field of the resource that has caused + this error, as named by its JSON serialization. + May include dot and postfix notation for nested + attributes. Arrays are zero-indexed. Fields may + appear more than once in an array of causes due + to fields having multiple errors. Optional. \n Examples: + \ \"name\" - the field \"name\" on the current + resource \"items[0].name\" - the field \"name\" + on the first array entry in \"items\"" + type: string + message: + description: A human-readable description of the cause + of the error. This field may be presented as-is + to a reader. + type: string + reason: + description: A machine-readable description of the + cause of the error. If this value is empty there + is no information available. + type: string + type: object + type: array + group: + description: The group attribute of the resource associated + with the status StatusReason. + type: string + kind: + description: 'The kind attribute of the resource associated + with the status StatusReason. On some operations may differ + from the requested resource Kind. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + name: + description: The name attribute of the resource associated + with the status StatusReason (when there is a single name + which can be described). + type: string + retryAfterSeconds: + description: If specified, the time in seconds before the + operation should be retried. Some errors may indicate + the client must take an alternate action - for those errors + this field may indicate how long to wait before taking + the alternate action. + format: int32 + type: integer + uid: + description: 'UID of the resource. (when there is a single + resource which can be described). More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + type: string + type: object + kind: + description: 'Kind is a string value representing the REST resource + this object represents. Servers may infer this from the endpoint + the client submits requests to. Cannot be updated. In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + message: + description: A human-readable description of the status of this + operation. + type: string + metadata: + description: 'Standard list metadata. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + properties: + continue: + description: continue may be set if the user set a limit + on the number of items returned, and indicates that the + server has more data available. The value is opaque and + may be used to issue another request to the endpoint that + served this list to retrieve the next set of available + objects. Continuing a consistent list may not be possible + if the server configuration has changed or more than a + few minutes have passed. The resourceVersion field returned + when using this continue value will be identical to the + value in the first response, unless you have received + this token from an error message. + type: string + resourceVersion: + description: 'String that identifies the server''s internal + version of this object that can be used by clients to + determine when objects have changed. Value must be treated + as opaque by clients and passed unmodified back to the + server. Populated by the system. Read-only. More info: + https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency' + type: string + selfLink: + description: selfLink is a URL representing this object. + Populated by the system. Read-only. + type: string + type: object + reason: + description: A machine-readable description of why this operation + is in the "Failure" status. If this value is empty there is + no information available. A Reason clarifies an HTTP status + code but does not override it. + type: string + status: + description: 'Status of the operation. One of: "Success" or + "Failure". More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status' + type: string + type: object + required: + - pending + type: object + labels: + additionalProperties: + type: string + description: 'Map of string keys and values that can be used to organize + and categorize (scope and select) objects. May match selectors of + replication controllers and services. More info: http://kubernetes.io/docs/user-guide/labels' + type: object + managedFields: + description: "ManagedFields maps workflow-id and version to the set + of fields that are managed by that workflow. This is mostly for internal + housekeeping, and users typically shouldn't need to set or understand + this field. A workflow can be the user's name, a controller's name, + or the name of a specific apply path like \"ci-cd\". The set of fields + is always in the version that the workflow used when modifying the + object. \n This field is alpha and can be changed or removed without + notice." + items: + description: ManagedFieldsEntry is a workflow-id, a FieldSet and the + group version of the resource that the fieldset applies to. + properties: + apiVersion: + description: APIVersion defines the version of this resource that + this field set applies to. The format is "group/version" just + like the top-level APIVersion field. It is necessary to track + the version of a field set because it cannot be automatically + converted. + type: string + fields: + additionalProperties: true + description: Fields identifies a set of fields. + type: object + manager: + description: Manager is an identifier of the workflow managing + these fields. + type: string + operation: + description: Operation is the type of operation which lead to + this ManagedFieldsEntry being created. The only valid values + for this field are 'Apply' and 'Update'. + type: string + time: + description: Time is timestamp of when these fields were set. + It should always be empty if Operation is 'Apply' + format: date-time + type: string + type: object + type: array + name: + description: 'Name must be unique within a namespace. Is required when + creating resources, although some resources may allow a client to + request the generation of an appropriate name automatically. Name + is primarily intended for creation idempotence and configuration definition. + Cannot be updated. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + namespace: + description: "Namespace defines the space within each name must be unique. + An empty namespace is equivalent to the \"default\" namespace, but + \"default\" is the canonical representation. Not all objects are required + to be scoped to a namespace - the value of this field for those objects + will be empty. \n Must be a DNS_LABEL. Cannot be updated. More info: + http://kubernetes.io/docs/user-guide/namespaces" + type: string + ownerReferences: + description: List of objects depended by this object. If ALL objects + in the list have been deleted, this object will be garbage collected. + If this object is managed by a controller, then an entry in this list + will point to this controller, with the controller field set to true. + There cannot be more than one managing controller. + items: + description: OwnerReference contains enough information to let you + identify an owning object. An owning object must be in the same + namespace as the dependent, or be cluster-scoped, so there is no + namespace field. + properties: + apiVersion: + description: API version of the referent. + type: string + blockOwnerDeletion: + description: If true, AND if the owner has the "foregroundDeletion" + finalizer, then the owner cannot be deleted from the key-value + store until this reference is removed. Defaults to false. To + set this field, a user needs "delete" permission of the owner, + otherwise 422 (Unprocessable Entity) will be returned. + type: boolean + controller: + description: If true, this reference points to the managing controller. + type: boolean + kind: + description: 'Kind of the referent. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds' + type: string + name: + description: 'Name of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#names' + type: string + uid: + description: 'UID of the referent. More info: http://kubernetes.io/docs/user-guide/identifiers#uids' + type: string + required: + - apiVersion + - kind + - name + - uid + type: object + type: array + resourceVersion: + description: "An opaque value that represents the internal version of + this object that can be used by clients to determine when objects + have changed. May be used for optimistic concurrency, change detection, + and the watch operation on a resource or set of resources. Clients + must treat these values as opaque and passed unmodified back to the + server. They may only be valid for a particular resource or set of + resources. \n Populated by the system. Read-only. Value must be treated + as opaque by clients and . More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#concurrency-control-and-consistency" + type: string + selfLink: + description: SelfLink is a URL representing this object. Populated by + the system. Read-only. + type: string + uid: + description: "UID is the unique in time and space value for this object. + It is typically generated by the server on successful creation of + a resource and is not allowed to change on PUT operations. \n Populated + by the system. Read-only. More info: http://kubernetes.io/docs/user-guide/identifiers#uids" + type: string + type: object + spec: + description: ConsumerGroupSpec defines the desired state of ConsumerGroup + properties: + eventhub: + type: string + namespace: + type: string + resourcegroup: + description: 'INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + Important: Run "make" to regenerate code after modifying this file' + type: string + type: object + status: + description: ConsumerGroupStatus defines the observed state of ConsumerGroup + properties: + provisioned: + type: boolean + provisioning: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed state + of cluster Important: Run "make" to regenerate code after modifying + this file' + type: boolean + type: object + type: object + versions: + - name: v1 + served: true + storage: true +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/azure.microsoft.com_eventhubs.yaml b/config/crd/bases/azure.microsoft.com_eventhubs.yaml index f881df6e68a..76c5fd857ec 100644 --- a/config/crd/bases/azure.microsoft.com_eventhubs.yaml +++ b/config/crd/bases/azure.microsoft.com_eventhubs.yaml @@ -11,8 +11,6 @@ spec: kind: Eventhub plural: eventhubs scope: "" - subresources: - status: {} validation: openAPIV3Schema: description: Eventhub is the Schema for the eventhubs API diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 1b65e87209c..24e4d6bcd14 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -5,9 +5,7 @@ resources: - bases/azure.microsoft.com_eventhubs.yaml - bases/azure.microsoft.com_resourcegroups.yaml - bases/azure.microsoft.com_eventhubnamespaces.yaml -- bases/azure.microsoft.com_sqlservers.yaml -- bases/azure.microsoft.com_sqldatabases.yaml -- bases/azure.microsoft.com_sqlfirewallrules.yaml +- bases/azure.microsoft.com_consumergroups.yaml - bases/azure.microsoft.com_keyvaults.yaml # +kubebuilder:scaffold:crdkustomizeresource @@ -16,9 +14,7 @@ patches: #- patches/webhook_in_eventhubs.yaml #- patches/webhook_in_resourcegroups.yaml #- patches/webhook_in_eventhubnamespaces.yaml -#- patches/webhook_in_sqlservers.yaml -#- patches/webhook_in_sqldatabases.yaml -#- patches/webhook_in_sqlfirewallrules.yaml +#- patches/webhook_in_consumergroups.yaml #- patches/webhook_in_keyvaults.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch @@ -26,9 +22,7 @@ patches: #- patches/cainjection_in_eventhubs.yaml #- patches/cainjection_in_resourcegroups.yaml #- patches/cainjection_in_eventhubnamespaces.yaml -#- patches/cainjection_in_sqlservers.yaml -#- patches/cainjection_in_sqldatabases.yaml -#- patches/cainjection_in_sqlfirewallrules.yaml +#- patches/cainjection_in_consumergroups.yaml #- patches/cainjection_in_keyvaults.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch diff --git a/config/crd/patches/cainjection_in_consumergroups.yaml b/config/crd/patches/cainjection_in_consumergroups.yaml new file mode 100644 index 00000000000..4c029c56574 --- /dev/null +++ b/config/crd/patches/cainjection_in_consumergroups.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + certmanager.k8s.io/inject-ca-from: $(NAMESPACE)/$(CERTIFICATENAME) + name: consumergroups.azure.microsoft.com diff --git a/config/crd/patches/webhook_in_consumergroups.yaml b/config/crd/patches/webhook_in_consumergroups.yaml new file mode 100644 index 00000000000..2e82d0f6726 --- /dev/null +++ b/config/crd/patches/webhook_in_consumergroups.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: consumergroups.azure.microsoft.com +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/default/manager_image_patch.yaml b/config/default/manager_image_patch.yaml index 9e09ecc83ec..6e060cb4438 100644 --- a/config/default/manager_image_patch.yaml +++ b/config/default/manager_image_patch.yaml @@ -8,7 +8,7 @@ spec: spec: containers: # Change the value of image field below to your controller image URL - - image: controller:latest + - image: IMAGE_URL name: manager env: - name: AZURE_CLIENT_ID diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 2dece016ed4..451b4729c65 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -9,7 +9,7 @@ rules: - apiGroups: - azure.microsoft.com resources: - - resourcegroups + - eventhubs verbs: - create - delete @@ -21,7 +21,7 @@ rules: - apiGroups: - azure.microsoft.com resources: - - resourcegroups/status + - eventhubs/status verbs: - create - delete @@ -53,7 +53,7 @@ rules: - apiGroups: - apps resources: - - sqlservers + - secrets verbs: - create - delete @@ -65,23 +65,7 @@ rules: - apiGroups: - apps resources: - - sqlservers/status - verbs: - - get - - patch - - update -- apiGroups: - - apps - resources: - - deployments/status - verbs: - - get - - patch - - update -- apiGroups: - - azure.microsoft.com - resources: - - eventhubs + - consumergroups verbs: - get - patch @@ -109,6 +93,14 @@ rules: - apiGroups: - azure.microsoft.com resources: + - consumergroups/status + verbs: + - get + - patch + - update +- apiGroups: + - "" + resources: - eventhubnamespaces/status verbs: - get @@ -117,15 +109,11 @@ rules: - apiGroups: - azure.microsoft.com resources: - - sqldatabases + - eventhubs/status verbs: - - create - - delete - get - - list - patch - update - - watch - apiGroups: - "" resources: @@ -136,7 +124,7 @@ rules: - apiGroups: - azure.microsoft.com resources: - - eventhubnamespaces + - deployments verbs: - create - delete @@ -148,15 +136,7 @@ rules: - apiGroups: - azure.microsoft.com resources: - - eventhubs/status - verbs: - - get - - patch - - update -- apiGroups: - - "" - resources: - - secrets + - eventhubnamespaces verbs: - create - delete @@ -168,11 +148,15 @@ rules: - apiGroups: - azure.microsoft.com resources: - - sqldatabases/status + - resourcegroups verbs: + - create + - delete - get + - list - patch - update + - watch - apiGroups: - azure.microsoft.com resources: @@ -181,16 +165,7 @@ rules: - create - patch - apiGroups: - - azure.microsoft.com - resources: - - keyvaults - verbs: - - get - - patch - - update - - watch -- apiGroups: - - azure.microsoft.com + - apps resources: - keyvaults/status verbs: diff --git a/config/samples/azure_v1_consumergroup.yaml b/config/samples/azure_v1_consumergroup.yaml new file mode 100644 index 00000000000..076bf32c357 --- /dev/null +++ b/config/samples/azure_v1_consumergroup.yaml @@ -0,0 +1,9 @@ +apiVersion: azure.microsoft.com/v1 +kind: ConsumerGroup +metadata: + name: consumergroup-sample-1 +spec: + # Add fields here + resourcegroup: "resourcegroup-sample-1" + namespace: "eventhubnamespace-sample-1" + eventhub: "eventhub-sample-2" diff --git a/controllers/consumergroup_controller.go b/controllers/consumergroup_controller.go new file mode 100644 index 00000000000..f6988cee06e --- /dev/null +++ b/controllers/consumergroup_controller.go @@ -0,0 +1,184 @@ +/* + +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 controllers + +import ( + "context" + "fmt" + "os" + "strconv" + "time" + + "github.com/go-logr/logr" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + azurev1 "github.com/Azure/azure-service-operator/api/v1" + eventhubsresourcemanager "github.com/Azure/azure-service-operator/pkg/resourcemanager/eventhubs" +) + +// ConsumerGroupReconciler reconciles a ConsumerGroup object +type ConsumerGroupReconciler struct { + client.Client + Log logr.Logger + Recorder record.EventRecorder +} + +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=consumergroups,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=azure.microsoft.com,resources=consumergroups/status,verbs=get;update;patch + +//Reconcile reconciler for consumergroup +func (r *ConsumerGroupReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { + ctx := context.Background() + log := r.Log.WithValues("consumergroup", req.NamespacedName) + + var instance azurev1.ConsumerGroup + if err := r.Get(ctx, req.NamespacedName, &instance); err != nil { + log.Error(err, "unable to fetch consumergroup") + // we'll ignore not-found errors, since they can't be fixed by an immediate + // requeue (we'll need to wait for a new notification), and we can get them + // on deleted requests. + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if instance.IsBeingDeleted() { + err := r.handleFinalizer(&instance) + if err != nil { + return reconcile.Result{}, fmt.Errorf("error when handling finalizer: %v", err) + } + return ctrl.Result{}, nil + } + + if !instance.HasFinalizer(consumerGroupFinalizerName) { + err := r.addFinalizer(&instance) + if err != nil { + return ctrl.Result{}, fmt.Errorf("error when removing finalizer: %v", err) + } + return ctrl.Result{}, nil + } + + if !instance.IsSubmitted() { + err := r.createConsumerGroup(&instance) + if err != nil { + + return ctrl.Result{}, fmt.Errorf("error when creating consumer group in azure: %v", err) + } + return ctrl.Result{}, nil + } + + requeueAfter, err := strconv.Atoi(os.Getenv("REQUEUE_AFTER")) + if err != nil { + requeueAfter = 30 + } + + return ctrl.Result{ + RequeueAfter: time.Second * time.Duration(requeueAfter), + }, nil +} + +func (r *ConsumerGroupReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&azurev1.ConsumerGroup{}). + Complete(r) +} + +func (r *ConsumerGroupReconciler) createConsumerGroup(instance *azurev1.ConsumerGroup) error { + + ctx := context.Background() + var err error + consumergroupName := instance.ObjectMeta.Name + namespaceName := instance.Spec.NamespaceName + resourcegroup := instance.Spec.ResourceGroupName + eventhubName := instance.Spec.EventhubName + + // write information back to instance + instance.Status.Provisioning = true + + //get owner instance + var ownerInstance azurev1.Eventhub + eventhubNamespacedName := types.NamespacedName{Name: eventhubName, Namespace: instance.Namespace} + err = r.Get(ctx, eventhubNamespacedName, &ownerInstance) + + if err != nil { + //log error and kill it, as the parent might not exist in the cluster. It could have been created elsewhere or through the portal directly + r.Recorder.Event(instance, "Warning", "Failed", "Unable to get owner instance of eventhub") + } else { + //set owner reference for consumer group if it exists + references := []metav1.OwnerReference{ + metav1.OwnerReference{ + APIVersion: "v1", + Kind: "Eventhub", + Name: ownerInstance.GetName(), + UID: ownerInstance.GetUID(), + }, + } + instance.ObjectMeta.SetOwnerReferences(references) + } + + err = r.Update(ctx, instance) + if err != nil { + //log error and kill it + r.Recorder.Event(instance, "Warning", "Failed", "Unable to update instance") + } + + _, err = eventhubsresourcemanager.CreateConsumerGroup(ctx, resourcegroup, namespaceName, eventhubName, consumergroupName) + if err != nil { + + r.Recorder.Event(instance, "Warning", "Failed", "Couldn't create consumer group in azure") + instance.Status.Provisioning = false + errUpdate := r.Update(ctx, instance) + if errUpdate != nil { + //log error and kill it + r.Recorder.Event(instance, "Warning", "Failed", "Unable to update instance") + } + return err + } + // write information back to instance + instance.Status.Provisioning = false + instance.Status.Provisioned = true + + err = r.Update(ctx, instance) + if err != nil { + //log error and kill it + r.Recorder.Event(instance, "Warning", "Failed", "Unable to update instance") + } + + r.Recorder.Event(instance, "Normal", "Updated", consumergroupName+" provisioned") + + return nil + +} + +func (r *ConsumerGroupReconciler) deleteConsumerGroup(instance *azurev1.ConsumerGroup) error { + ctx := context.Background() + + consumergroupName := instance.ObjectMeta.Name + namespaceName := instance.Spec.NamespaceName + resourcegroup := instance.Spec.ResourceGroupName + eventhubName := instance.Spec.EventhubName + + var err error + _, err = eventhubsresourcemanager.DeleteConsumerGroup(ctx, resourcegroup, namespaceName, eventhubName, consumergroupName) + if err != nil { + r.Recorder.Event(instance, "Warning", "Failed", "Couldn't delete consumer group in azure") + return err + } + return nil +} diff --git a/controllers/consumergroup_controller_finalizer.go b/controllers/consumergroup_controller_finalizer.go new file mode 100644 index 00000000000..99cc3883fd8 --- /dev/null +++ b/controllers/consumergroup_controller_finalizer.go @@ -0,0 +1,57 @@ +/* +Copyright 2019 microsoft. + +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 controllers + +import ( + "context" + "fmt" + + azurev1 "github.com/Azure/azure-service-operator/api/v1" +) + +const consumerGroupFinalizerName = "consumergroup.finalizers.com" + +func (r *ConsumerGroupReconciler) addFinalizer(instance *azurev1.ConsumerGroup) error { + instance.AddFinalizer(consumerGroupFinalizerName) + err := r.Update(context.Background(), instance) + if err != nil { + return fmt.Errorf("failed to update finalizer: %v", err) + } + r.Recorder.Event(instance, "Normal", "Updated", fmt.Sprintf("finalizer %s added", consumerGroupFinalizerName)) + return nil +} + +func (r *ConsumerGroupReconciler) handleFinalizer(instance *azurev1.ConsumerGroup) error { + if instance.HasFinalizer(consumerGroupFinalizerName) { + // our finalizer is present, so lets handle our external dependency + if err := r.deleteExternalDependency(instance); err != nil { + return err + } + + instance.RemoveFinalizer(consumerGroupFinalizerName) + if err := r.Update(context.Background(), instance); err != nil { + return err + } + } + // Our finalizer has finished, so the reconciler can do nothing. + return nil +} + +func (r *ConsumerGroupReconciler) deleteExternalDependency(instance *azurev1.ConsumerGroup) error { + + return r.deleteConsumerGroup(instance) +} diff --git a/controllers/consumergroup_controller_test.go b/controllers/consumergroup_controller_test.go new file mode 100644 index 00000000000..0bd10170073 --- /dev/null +++ b/controllers/consumergroup_controller_test.go @@ -0,0 +1,100 @@ +/* +Copyright 2019 microsoft. + +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 controllers + +import ( + "context" + + azurev1 "github.com/Azure/azure-service-operator/api/v1" + helpers "github.com/Azure/azure-service-operator/pkg/helpers" + + "time" + + . "github.com/onsi/ginkgo" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + . "github.com/onsi/gomega" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/types" +) + +var _ = Describe("ConsumerGroup Controller", func() { + + const timeout = time.Second * 240 + + BeforeEach(func() { + // Add any setup steps that needs to be executed before each test + }) + + AfterEach(func() { + // Add any teardown steps that needs to be executed after each test + }) + + // Add Tests for OpenAPI validation (or additonal CRD features) specified in + // your API definition. + // Avoid adding tests for vanilla CRUD operations because they would + // test Kubernetes API server, which isn't the goal here. + Context("Create and Delete", func() { + It("should create and delete consumer groups", func() { + + resourceGroupName = "t-rg-dev-controller" + eventhubNamespaceName = "t-ns-dev-eh-ns" + eventhubName = "t-eh-dev-sample" + consumerGroupName := "t-cg-" + helpers.RandomString(10) + + var err error + + // Create the consumer group object and expect the Reconcile to be created + consumerGroupInstance := &azurev1.ConsumerGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: consumerGroupName, + Namespace: "default", + }, + Spec: azurev1.ConsumerGroupSpec{ + NamespaceName: eventhubNamespaceName, + ResourceGroupName: resourceGroupName, + EventhubName: eventhubName, + }, + } + + err = k8sClient.Create(context.Background(), consumerGroupInstance) + Expect(apierrors.IsInvalid(err)).To(Equal(false)) + Expect(err).NotTo(HaveOccurred()) + + consumerGroupNamespacedName := types.NamespacedName{Name: consumerGroupName, Namespace: "default"} + + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), consumerGroupNamespacedName, consumerGroupInstance) + return consumerGroupInstance.HasFinalizer(consumerGroupFinalizerName) + }, timeout, + ).Should(BeTrue()) + + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), consumerGroupNamespacedName, consumerGroupInstance) + return consumerGroupInstance.IsSubmitted() + }, timeout, + ).Should(BeTrue()) + + k8sClient.Delete(context.Background(), consumerGroupInstance) + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), consumerGroupNamespacedName, consumerGroupInstance) + return consumerGroupInstance.IsBeingDeleted() + }, timeout, + ).Should(BeTrue()) + + }) + }) +}) diff --git a/controllers/eventhub_controller.go b/controllers/eventhub_controller.go index c781eab6864..57ff6558797 100644 --- a/controllers/eventhub_controller.go +++ b/controllers/eventhub_controller.go @@ -38,6 +38,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) // EventhubReconciler reconciles a Eventhub object @@ -70,14 +71,7 @@ func (r *EventhubReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { if instance.IsBeingDeleted() { err := r.handleFinalizer(&instance) if err != nil { - catch := []string{ - errhelp.ResourceGroupNotFoundErrorCode, - } - if helpers.ContainsString(catch, err.(*errhelp.AzureError).Type) { - log.Info("Got ignorable error", "type", err.(*errhelp.AzureError).Type) - return ctrl.Result{RequeueAfter: time.Second * 30}, nil - } - return ctrl.Result{}, fmt.Errorf("error when handling finalizer: %v", err) + return reconcile.Result{}, fmt.Errorf("error when handling finalizer: %v", err) } return ctrl.Result{}, nil } diff --git a/controllers/eventhub_controller_test.go b/controllers/eventhub_controller_test.go index 216f03c4df4..2271e708cf6 100644 --- a/controllers/eventhub_controller_test.go +++ b/controllers/eventhub_controller_test.go @@ -33,7 +33,6 @@ import ( ) var _ = Describe("EventHub Controller", func() { - const timeout = time.Second * 240 BeforeEach(func() { @@ -82,7 +81,6 @@ var _ = Describe("EventHub Controller", func() { }) It("should create and delete eventhubs", func() { - resourceGroupName = "t-rg-dev-controller" eventhubNamespaceName = "t-ns-dev-eh-ns" eventhubName := "t-eh-" + helpers.RandomString(10) @@ -103,6 +101,10 @@ var _ = Describe("EventHub Controller", func() { MessageRetentionInDays: 7, PartitionCount: 1, }, + AuthorizationRule: azurev1.EventhubAuthorizationRule{ + Name: "RootManageSharedAccessKey", + Rights: []string{"Listen"}, + }, }, } diff --git a/controllers/suite_test.go b/controllers/suite_test.go index 64a49c50a89..8b022e99e19 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -49,6 +49,7 @@ var testEnv *envtest.Environment var resourceGroupName string var resourcegroupLocation string var eventhubNamespaceName string +var eventhubName string var namespaceLocation string func TestAPIs(t *testing.T) { @@ -58,6 +59,7 @@ func TestAPIs(t *testing.T) { resourcegroupLocation = "westus" eventhubNamespaceName = "t-ns-dev-eh-ns" + eventhubName = "t-eh-dev-sample" namespaceLocation = "westus" RunSpecsWithDefaultAndCustomReporters(t, "Controller Suite", @@ -108,6 +110,7 @@ var _ = BeforeSuite(func(done Done) { Client: k8sManager.GetClient(), Log: ctrl.Log.WithName("controllers").WithName("EventHub"), Recorder: k8sManager.GetEventRecorderFor("Eventhub-controller"), + Scheme: scheme.Scheme, }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) @@ -125,6 +128,13 @@ var _ = BeforeSuite(func(done Done) { }).SetupWithManager(k8sManager) Expect(err).ToNot(HaveOccurred()) + err = (&ConsumerGroupReconciler{ + Client: k8sManager.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("ConsumerGroup"), + Recorder: k8sManager.GetEventRecorderFor("ConsumerGroup-controller"), + }).SetupWithManager(k8sManager) + Expect(err).ToNot(HaveOccurred()) + go func() { err = k8sManager.Start(ctrl.SetupSignalHandler()) Expect(err).ToNot(HaveOccurred()) @@ -142,6 +152,8 @@ var _ = BeforeSuite(func(done Done) { // Create the Eventhub namespace resource _, err = eventhubs.CreateNamespaceAndWait(context.Background(), resourceGroupName, eventhubNamespaceName, namespaceLocation) + // Create the Eventhub resource + _, err = eventhubs.CreateHub(context.Background(), resourceGroupName, eventhubNamespaceName, eventhubName, int32(7), int32(1)) close(done) }, 120) diff --git a/devops/azure-pipelines.yaml b/devops/azure-pipelines.yaml index 09a244a1f14..a77a0e27d0d 100644 --- a/devops/azure-pipelines.yaml +++ b/devops/azure-pipelines.yaml @@ -86,7 +86,7 @@ steps: gocov convert coverage.txt > coverage.json gocov-xml < coverage.json > coverage.xml mkdir coverage - continueOnError: 'true' + continueOnError: 'false' displayName: 'Run unit tests' env: GO111MODULE: on @@ -125,7 +125,7 @@ steps: echo "all the pods should be running" make deploy make test-existing - continueOnError: 'true' + continueOnError: 'false' displayName: 'Set kind cluster and Run unit tests' env: GO111MODULE: on @@ -139,23 +139,27 @@ steps: - task: PublishTestResults@2 displayName: 'test cluster' - inputs: - failedTaskOnFailedTest: true + inputs: testRunner: JUnit testResultsFiles: $(System.DefaultWorkingDirectory)/**/report.xml + failTaskOnFailedTests: 'true' + failOnStandardError: 'true' - task: PublishTestResults@2 displayName: 'existing cluster' - inputs: - failedTaskOnFailedTest: true + inputs: testRunner: JUnit testResultsFiles: $(System.DefaultWorkingDirectory)/**/report-existing.xml + failTaskOnFailedTests: 'true' + failOnStandardError: 'true' - task: PublishCodeCoverageResults@1 inputs: codeCoverageTool: Cobertura summaryFileLocation: $(System.DefaultWorkingDirectory)/**/coverage.xml reportDirectory: $(System.DefaultWorkingDirectory)/**/coverage + failIfCoverageEmpty: 'true' + failOnStandardError: 'true' - script: docker build -t $(IMAGE_NAME) . # add options to this command to meet your needs condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master')) diff --git a/go.mod b/go.mod index e7b0fa90be7..24079a36543 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/Azure/azure-service-operator go 1.12 require ( + github.com/AlekSi/gocov-xml v0.0.0-20190121064608-3a14fb1c4737 // indirect github.com/Azure-Samples/azure-sdk-for-go-samples v0.0.0-20190805235326-79e3f3af791c github.com/Azure/azure-sdk-for-go v32.0.0+incompatible github.com/Azure/go-autorest/autorest v0.5.0 @@ -10,6 +11,7 @@ require ( github.com/Azure/go-autorest/autorest/azure/auth v0.1.0 github.com/Azure/go-autorest/autorest/to v0.2.0 github.com/Azure/go-autorest/autorest/validation v0.1.0 // indirect + github.com/axw/gocov v1.0.0 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20190724012636-11b2859924c1 github.com/go-logr/logr v0.1.0 github.com/gobuffalo/envy v1.7.0 @@ -18,10 +20,11 @@ require ( github.com/marstr/randname v0.0.0-20181206212954-d5b0f288ab8c github.com/onsi/ginkgo v1.8.0 github.com/onsi/gomega v1.5.0 - github.com/satori/go.uuid v1.2.0 + github.com/satori/go.uuid v1.2.0 // indirect golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8 // indirect golang.org/x/net v0.0.0-20190620200207-3b0461eec859 golang.org/x/sys v0.0.0-20190621203818-d432491b9138 // indirect + golang.org/x/tools v0.0.0-20190822191935-b1e2c8edcefd // indirect k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible diff --git a/go.sum b/go.sum index 8599b887ff3..fac5a314131 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= contrib.go.opencensus.io/exporter/ocagent v0.4.12 h1:jGFvw3l57ViIVEPKKEUXPcLYIXJmQxLUh6ey1eJhwyc= contrib.go.opencensus.io/exporter/ocagent v0.4.12/go.mod h1:450APlNTSR6FrvC3CTRqYosuDstRB9un7SOx2k/9ckA= +github.com/AlekSi/gocov-xml v0.0.0-20190121064608-3a14fb1c4737 h1:JZHBkt0GhM+ARQykshqpI49yaWCHQbJonH3XpDTwMZQ= +github.com/AlekSi/gocov-xml v0.0.0-20190121064608-3a14fb1c4737/go.mod h1:w1KSuh2JgIL3nyRiZijboSUwbbxOrTzWwyWVFUHtXBQ= github.com/Azure-Samples/azure-sdk-for-go-samples v0.0.0-20190717201944-ad38292fb10f/go.mod h1:fHSkenNUlKPxCdWPfkfEubZHu7B3er09X30oJ6db8Fg= github.com/Azure-Samples/azure-sdk-for-go-samples v0.0.0-20190805235326-79e3f3af791c h1:ND9JhvcUO30ENxUh4zfMHom+pszWyFqM5TCbyE/dXnM= github.com/Azure-Samples/azure-sdk-for-go-samples v0.0.0-20190805235326-79e3f3af791c/go.mod h1:fHSkenNUlKPxCdWPfkfEubZHu7B3er09X30oJ6db8Fg= @@ -44,12 +46,18 @@ github.com/Azure/go-autorest/tracing v0.1.0/go.mod h1:ROEEAFwXycQw7Sn3DXNtEedEvd github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.0 h1:rmGxhojJlM0tuKtfdvliR84CFHljx9ag64t2xmVkjK4= +github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/axw/gocov v1.0.0 h1:YsqYR66hUmilVr23tu8USgnJIJvnwh3n7j5zRn7x4LU= +github.com/axw/gocov v1.0.0/go.mod h1:LvQpEYiwwIb2nYkXY2fDWhg9/AsYqkhmrCshjlUJECE= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/census-instrumentation/opencensus-proto v0.2.0 h1:LzQXZOgg4CQfE6bFvXGM30YZL1WW/M337pXml+GrcZ4= @@ -68,7 +76,9 @@ github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633 h1:H2pdYOb3KQ1/YsqVWoWNLQO+fusocsw354rqGTZtAgw= github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= @@ -83,9 +93,17 @@ github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7 github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.17.0 h1:nH6xp8XdXHx8dqveo0ZuJBluCO2qGrPbDNZ0dwoRHP0= +github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.17.0 h1:yJW3HCkTHg7NOA+gZ83IPHzUSnUzGXhGmsdiCcMexbA= +github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.0 h1:A4SZ6IWh3lnjH0rG0Z5lkxazMGBECtrZcbyYQi+64k4= +github.com/go-openapi/spec v0.19.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.17.0 h1:iqrgMg7Q7SvtbWLlltPrkMs0UBJI6oTSs79JFRUi880= +github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= @@ -120,6 +138,8 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.2.0 h1:l6N3VoaVzTncYYW+9yOz2LJJammFZGBO13sqgEhpy9g= @@ -146,6 +166,7 @@ github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswD github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -158,6 +179,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329 h1:2gxZ0XQIU/5z3Z3bUBu+FXuk2pFbkN6tcwi/pjyaDic= +github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/marstr/collection v1.0.1 h1:j61osRfyny7zxBlLRtoCvOZ2VX7HEyybkZcsLNLJ0z0= github.com/marstr/collection v1.0.1/go.mod h1:HHDXVxjLO3UYCBXJWY+J/ZrxCUOYqrO66ob1AzIsmYA= github.com/marstr/randname v0.0.0-20181206212954-d5b0f288ab8c h1:JE+MDz5rhFN5EC9Dj/N8dLYKboTWm6FXeWhnyKVj0vA= @@ -222,6 +245,8 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= @@ -264,6 +289,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -317,6 +343,13 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190501045030-23463209683d h1:D7DVZUZEUgsSIDTivnUtVeGfN5AvhDIKtdIZAqx0ieE= golang.org/x/tools v0.0.0-20190501045030-23463209683d/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774 h1:CQVOmarCBFzTx0kbOU0ru54Cvot8SdSrNYjZPhQl+gk= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190822000311-fc82fb2afd64 h1:4EN1tY9aQxwLGYHWT5WdQN56Xzbwlg2UTINDbZ04l10= +golang.org/x/tools v0.0.0-20190822000311-fc82fb2afd64/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190822191935-b1e2c8edcefd h1:sl3cZ9UhakOcf0k3nWTLpJFHPGbvWf5Cao9HxvzkDos= +golang.org/x/tools v0.0.0-20190822191935-b1e2c8edcefd/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.0 h1:OyHbl+7IOECpPKfVK42oFr6N7+Y2dR+Jsb/IiDV3hOo= gomodules.xyz/jsonpatch/v2 v2.0.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= google.golang.org/api v0.3.1 h1:oJra/lMfmtm13/rgY/8i3MzjFWYXvQIAKjQ3HqofMk8= @@ -372,6 +405,7 @@ k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c= k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM= k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190510232812-a01b7d5d6c22/go.mod h1:iU+ZGYsNlvU9XKUSso6SQfKTCCw7lFduMZy26Mgr2Fw= k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco= k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= diff --git a/main.go b/main.go index 801290a8971..1da4005f2b6 100644 --- a/main.go +++ b/main.go @@ -118,6 +118,15 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "EventhubNamespace") os.Exit(1) } + err = (&controllers.ConsumerGroupReconciler{ + Client: mgr.GetClient(), + Log: ctrl.Log.WithName("controllers").WithName("ConsumerGroup"), + Recorder: mgr.GetEventRecorderFor("ConsumerGroup-controller"), + }).SetupWithManager(mgr) + if err != nil { + setupLog.Error(err, "unable to create controller", "controller", "ConsumerGroup") + os.Exit(1) + } if !resourcemanagerconfig.Declarative() { if err = (&azurev1.EventhubNamespace{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "EventhubNamespace") diff --git a/pkg/errhelp/errors.go b/pkg/errhelp/errors.go index 9a27642ab45..122cebf37ba 100644 --- a/pkg/errhelp/errors.go +++ b/pkg/errhelp/errors.go @@ -14,9 +14,6 @@ const ( ) func NewAzureError(err error) error { - if err == nil { - return nil - } ae := AzureError{ Original: err, } @@ -25,7 +22,6 @@ func NewAzureError(err error) error { if det, ok := err.(autorest.DetailedError); ok { var kind, reason string ae.Code = det.StatusCode.(int) - if e, ok := det.Original.(*azure.RequestError); ok { kind = e.ServiceError.Code reason = e.ServiceError.Message diff --git a/pkg/resourcemanager/eventhubs/consumergroup.go b/pkg/resourcemanager/eventhubs/consumergroup.go new file mode 100644 index 00000000000..0e1223345e7 --- /dev/null +++ b/pkg/resourcemanager/eventhubs/consumergroup.go @@ -0,0 +1,65 @@ +package eventhubs + +import ( + "context" + + "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" + "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" + "github.com/Azure/go-autorest/autorest" + + "github.com/Azure/azure-sdk-for-go/services/eventhub/mgmt/2017-04-01/eventhub" +) + +func getConsumerGroupsClient() eventhub.ConsumerGroupsClient { + consumerGroupClient := eventhub.NewConsumerGroupsClient(config.SubscriptionID()) + auth, _ := iam.GetResourceManagementAuthorizer() + consumerGroupClient.Authorizer = auth + consumerGroupClient.AddToUserAgent(config.UserAgent()) + return consumerGroupClient +} + +// CreateConsumerGroup creates an Event Hub Consumer Group +// Parameters: +// resourceGroupName - name of the resource group within the azure subscription. +// namespaceName - the Namespace name +// eventHubName - the Event Hub name +// consumerGroupName - the consumer group name +// parameters - parameters supplied to create or update a consumer group resource. +func CreateConsumerGroup(ctx context.Context, resourceGroupName string, namespaceName string, eventHubName string, consumerGroupName string) (eventhub.ConsumerGroup, error) { + consumerGroupClient := getConsumerGroupsClient() + + parameters := eventhub.ConsumerGroup{} + return consumerGroupClient.CreateOrUpdate( + ctx, + resourceGroupName, + namespaceName, + eventHubName, + consumerGroupName, + parameters, + ) + +} + +// DeleteConsumerGroup deletes an Event Hub Consumer Group +// Parameters: +// resourceGroupName - name of the resource group within the azure subscription. +// namespaceName - the Namespace name +// eventHubName - the Event Hub name +// consumerGroupName - the consumer group name +func DeleteConsumerGroup(ctx context.Context, resourceGroupName string, namespaceName string, eventHubName string, consumerGroupName string) (result autorest.Response, err error) { + consumerGroupClient := getConsumerGroupsClient() + return consumerGroupClient.Delete( + ctx, + resourceGroupName, + namespaceName, + eventHubName, + consumerGroupName, + ) + +} + +//GetConsumerGroup gets consumer group description for the specified Consumer Group. +func GetConsumerGroup(ctx context.Context, resourceGroupName string, namespaceName string, eventHubName string, consumerGroupName string) (eventhub.ConsumerGroup, error) { + consumerGroupClient := getConsumerGroupsClient() + return consumerGroupClient.Get(ctx, resourceGroupName, namespaceName, eventHubName, consumerGroupName) +} diff --git a/pkg/resourcemanager/eventhubs/consumergroup_test.go b/pkg/resourcemanager/eventhubs/consumergroup_test.go new file mode 100644 index 00000000000..b20b5da106b --- /dev/null +++ b/pkg/resourcemanager/eventhubs/consumergroup_test.go @@ -0,0 +1,90 @@ +/* +Copyright 2019 microsoft. + +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 eventhubs + +import ( + "context" + "time" + + helpers "github.com/Azure/azure-service-operator/pkg/helpers" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("ConsumerGroup", func() { + + const timeout = time.Second * 240 + var resourceGroupName string + var eventhubNamespaceName string + var eventhubName string + var namespaceLocation string + var messageRetentionInDays int32 + var partitionCount int32 + + BeforeEach(func() { + // Add any setup steps that needs to be executed before each test + resourceGroupName = "t-rg-dev-rm-eh" + eventhubNamespaceName = "t-ns-dev-eh-" + helpers.RandomString(10) + namespaceLocation = "westus" + eventhubName = "t-eh-dev-ehs" + messageRetentionInDays = int32(7) + partitionCount = int32(1) + + _, _ = CreateNamespaceAndWait(context.Background(), resourceGroupName, eventhubNamespaceName, namespaceLocation) + + _, _ = CreateHub(context.Background(), resourceGroupName, eventhubNamespaceName, eventhubName, messageRetentionInDays, partitionCount) + + }) + + AfterEach(func() { + // Add any teardown steps that needs to be executed after each test + }) + + // Add Tests for OpenAPI validation (or additonal CRD features) specified in + // your API definition. + // Avoid adding tests for vanilla CRUD operations because they would + // test Kubernetes API server, which isn't the goal here. + + Context("Create and Delete", func() { + It("should create and delete consumer groups in azure", func() { + + consumerGroupName := "t-cg-" + helpers.RandomString(10) + + var err error + + _, err = CreateConsumerGroup(context.Background(), resourceGroupName, eventhubNamespaceName, eventhubName, consumerGroupName) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + result, _ := GetConsumerGroup(context.Background(), resourceGroupName, eventhubNamespaceName, eventhubName, consumerGroupName) + return result.Response.StatusCode == 200 + }, timeout, + ).Should(BeTrue()) + + _, err = DeleteConsumerGroup(context.Background(), resourceGroupName, eventhubNamespaceName, eventhubName, consumerGroupName) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() bool { + result, _ := GetConsumerGroup(context.Background(), resourceGroupName, eventhubNamespaceName, eventhubName, consumerGroupName) + return result.Response.StatusCode == 404 + }, timeout, + ).Should(BeTrue()) + + }) + + }) +}) diff --git a/pkg/resourcemanager/sqlclient/endtoend_test.go b/pkg/resourcemanager/sqlclient/endtoend_test.go index 37f7090026b..6fd007a7584 100644 --- a/pkg/resourcemanager/sqlclient/endtoend_test.go +++ b/pkg/resourcemanager/sqlclient/endtoend_test.go @@ -40,67 +40,118 @@ func TestCreateOrUpdateSQLServer(t *testing.T) { ServerName: generateName("sqlsrvtest"), Location: "eastus2", } - - // create the server + + // create the sql server properties struct sqlServerProperties := SQLServerProperties{ AdministratorLogin: to.StringPtr("Moss"), AdministratorLoginPassword: to.StringPtr("TheITCrowd_{01}!"), - AllowAzureServicesAccess: true, } - _, err = sdk.CreateOrUpdateSQLServer(sqlServerProperties) - if err != nil { - util.PrintAndLog(fmt.Sprintf("cannot create sql server: %v", err)) + + // this firewall rule should fail (the server doesn't exist yet) + result, err := sdk.CreateOrUpdateSQLFirewallRule("fail rule", "0.0.0.0", "1.2.3.4") + if result { + util.PrintAndLog("firewall rule add succeeded, but shouldn't have") t.FailNow() + } else { + util.PrintAndLog("firewall rule add failed (good)") } - util.PrintAndLog("sql server created") - // wait for server to be created - // then only proceed once activated + // wait for server to be created, then only proceed once activated for { time.Sleep(time.Second) - ready, err := sdk.SQLServerReady() - if err != nil { - util.PrintAndLog(fmt.Sprintf("error checking for sql status: %v", err)) - t.FailNow() - break + server, err := sdk.CreateOrUpdateSQLServer(sqlServerProperties) + if err == nil { + if *server.State == "Ready" { + util.PrintAndLog("sql server ready") + break + } + } else { + if sdk.IsAsyncNotCompleted(err) { + util.PrintAndLog("waiting for sql server to be ready...") + continue + } else { + util.PrintAndLog(fmt.Sprintf("cannot create sql server: %v", err)) + t.FailNow() + break + } } - if ready { - break - } - util.PrintAndLog("waiting for sql server to be ready...") } - util.PrintAndLog("sql server ready") + + // this firewall rule should succeed + result, err = sdk.CreateOrUpdateSQLFirewallRule("succeed rule", "0.0.0.0", "4.3.2.1") + if result { + util.PrintAndLog("firewall rule add succeeded") + } else { + util.PrintAndLog("firewall rule add failed, but should have succeeded") + t.FailNow() + } + + // this firewall rule deletion should succeed + err = sdk.DeleteSQLFirewallRule("succeed rule") + if err == nil { + util.PrintAndLog("firewall rule deletion succeeded") + } else { + util.PrintAndLog(fmt.Sprintf("firewall rule deletion failed, but should have succeeded: %v", err)) + t.FailNow() + } // create a DB sqlDBProperties := SQLDatabaseProperties{ DatabaseName: "testDB", - Edition: Free, - } - _, err = sdk.CreateOrUpdateDB(sqlDBProperties) - if err != nil { - util.PrintAndLog(fmt.Sprintf("cannot create db: %v", err)) - t.FailNow() + Edition: Basic, } - util.PrintAndLog("db created") - // wait a minute - time.Sleep(time.Second * 30) + // wait for db to be created, then only proceed once activated + for { + time.Sleep(time.Second) + db, err := sdk.CreateOrUpdateDB(sqlDBProperties) + if err == nil { + if *db.Status == "Online" { + util.PrintAndLog("db ready") + break + } + } else { + if sdk.IsAsyncNotCompleted(err) { + util.PrintAndLog("waiting for db to be ready...") + continue + } else { + util.PrintAndLog(fmt.Sprintf("cannot create db: %v", err)) + t.FailNow() + break + } + } + } // delete the DB - _, err = sdk.DeleteDB("testDB") - if err != nil { - util.PrintAndLog(fmt.Sprintf("cannot delete the db: %v", err)) - t.FailNow() + time.Sleep(time.Second) + response, err := sdk.DeleteDB("testDB") + if err == nil { + if response.StatusCode == 200 { + util.PrintAndLog("db deleted") + } } else { - util.PrintAndLog("db deleted") + util.PrintAndLog(fmt.Sprintf("cannot delete db: %v", err)) + t.FailNow() } // delete the server - _, err = sdk.DeleteSQLServer() - if err != nil { - util.PrintAndLog(fmt.Sprintf("cannot delete the sql server: %v", err)) - t.FailNow() - } else { - util.PrintAndLog("sql server deleted") + for { + time.Sleep(time.Second) + response, err := sdk.DeleteSQLServer() + if err == nil { + if response.StatusCode == 200 { + util.PrintAndLog("sql server deleted") + break + } + } else { + if sdk.IsAsyncNotCompleted(err) { + util.PrintAndLog("waiting for sql server to be deleted...") + continue + } else { + util.PrintAndLog(fmt.Sprintf("cannot delete sql server: %v", err)) + t.FailNow() + break + } + } } } diff --git a/pkg/resourcemanager/sqlclient/resourceclient.go b/pkg/resourcemanager/sqlclient/resourceclient.go index 4fbcc41ac9c..b3329a923b6 100644 --- a/pkg/resourcemanager/sqlclient/resourceclient.go +++ b/pkg/resourcemanager/sqlclient/resourceclient.go @@ -5,11 +5,18 @@ package sqlclient +import ( + "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" + "github.com/Azure/go-autorest/autorest" +) + // ResourceClient contains the helper functions for interacting with SQL servers / databases type ResourceClient interface { - CreateOrUpdateSQLServer(properties SQLServerProperties) (result bool, err error) - SQLServerReady() (result bool, err error) - CreateOrUpdateDB(properties SQLDatabaseProperties) (result bool, err error) - DeleteDB(databaseName string) (result bool, err error) - DeleteSQLServer() (result bool, err error) + CreateOrUpdateSQLServer(properties SQLServerProperties) (result sql.Server, err error) + CreateOrUpdateSQLFirewallRule(ruleName string, startIP string, endIP string) (result bool, err error) + CreateOrUpdateDB(properties SQLDatabaseProperties) (result sql.Database, err error) + DeleteDB(databaseName string) (result autorest.Response, err error) + DeleteSQLServer() (result autorest.Response, err error) + DeleteSQLFirewallRule(ruleName string) (err error) + IsAsyncNotCompleted(err error) (result bool) } diff --git a/pkg/resourcemanager/sqlclient/sqlclient_godsk.go b/pkg/resourcemanager/sqlclient/sqlclient_godsk.go index bba273d3929..216aefcec0f 100644 --- a/pkg/resourcemanager/sqlclient/sqlclient_godsk.go +++ b/pkg/resourcemanager/sqlclient/sqlclient_godsk.go @@ -6,12 +6,14 @@ package sqlclient import ( - "fmt" + "net/http" "strings" "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" "github.com/Azure/azure-service-operator/pkg/resourcemanager/config" "github.com/Azure/azure-service-operator/pkg/resourcemanager/iam" + + "github.com/Azure/go-autorest/autorest" "github.com/Azure/go-autorest/autorest/to" ) @@ -24,8 +26,8 @@ func getGoServersClient() sql.ServersClient { return serversClient } -// getGoCBClient retrieves a DatabasesClient -func getGoCBClient() sql.DatabasesClient { +// getGoDbClient retrieves a DatabasesClient +func getGoDbClient() sql.DatabasesClient { dbClient := sql.NewDatabasesClient(config.SubscriptionID()) a, _ := iam.GetResourceManagementAuthorizer() dbClient.Authorizer = a @@ -33,55 +35,101 @@ func getGoCBClient() sql.DatabasesClient { return dbClient } +// getGoFirewallClient retrieves a FirewallRulesClient +func getGoFirewallClient() sql.FirewallRulesClient { + firewallClient := sql.NewFirewallRulesClient(config.SubscriptionID()) + a, _ := iam.GetResourceManagementAuthorizer() + firewallClient.Authorizer = a + firewallClient.AddToUserAgent(config.UserAgent()) + return firewallClient +} + // CreateOrUpdateSQLServer creates a SQL server in Azure -func (sdk GoSDKClient) CreateOrUpdateSQLServer(properties SQLServerProperties) (s sql.Server, err error) { +func (sdk GoSDKClient) CreateOrUpdateSQLServer(properties SQLServerProperties) (result sql.Server, err error) { serversClient := getGoServersClient() serverProp := SQLServerPropertiesToServer(properties) + // check to see if the server exists, if it does then short-circuit + server, err := serversClient.Get( + sdk.Ctx, + sdk.ResourceGroupName, + sdk.ServerName, + ) + if err == nil && *server.State == "Ready" { + return server, nil + } + + // issue the creation future, err := serversClient.CreateOrUpdate( sdk.Ctx, sdk.ResourceGroupName, sdk.ServerName, sql.Server{ - Location: to.StringPtr(sdk.Location), + Location: to.StringPtr(config.Location()), ServerProperties: &serverProp, }) - - serv, err := future.Result(serversClient) if err != nil { - if !strings.Contains(err.Error(), "asynchronous operation has not completed") { - return sql.Server{}, err - } - + return result, err } - return serv, nil + + return future.Result(serversClient) } -// SQLServerReady returns true if the SQL server is active -func (sdk GoSDKClient) SQLServerReady() (result bool, err error) { +// CreateOrUpdateSQLFirewallRule creates or updates a firewall rule +// based on code from: https://github.com/Azure-Samples/azure-sdk-for-go-samples/blob/master/sql/sql.go#L111 +// to allow allow Azure services to connect example: https://docs.microsoft.com/en-us/azure/sql-database/sql-database-firewall-configure#manage-firewall-rules-using-azure-cli +func (sdk GoSDKClient) CreateOrUpdateSQLFirewallRule(ruleName string, startIP string, endIP string) (result bool, err error) { serversClient := getGoServersClient() + firewallClient := getGoFirewallClient() + // check to see if the server exists, if it doesn't then short-circuit server, err := serversClient.Get( sdk.Ctx, sdk.ResourceGroupName, sdk.ServerName, ) - if err != nil { - if strings.Contains(err.Error(), "ResourceNotFound") { - return false, nil - } - return false, fmt.Errorf("cannot get sql server: %v", err) + if err != nil || *server.State != "Ready" { + return false, err } - return *server.State == "Ready", err + _, err = firewallClient.CreateOrUpdate( + sdk.Ctx, + sdk.ResourceGroupName, + sdk.ServerName, + ruleName, + sql.FirewallRule{ + FirewallRuleProperties: &sql.FirewallRuleProperties{ + StartIPAddress: to.StringPtr(startIP), + EndIPAddress: to.StringPtr(endIP), + }, + }, + ) + result = false + if err == nil { + result = true + } + + return result, err } // CreateOrUpdateDB creates or updates a DB in Azure -func (sdk GoSDKClient) CreateOrUpdateDB(properties SQLDatabaseProperties) (result bool, err error) { - dbClient := getGoCBClient() +func (sdk GoSDKClient) CreateOrUpdateDB(properties SQLDatabaseProperties) (result sql.Database, err error) { + dbClient := getGoDbClient() dbProp := SQLDatabasePropertiesToDatabase(properties) - _, err = dbClient.CreateOrUpdate( + // check to see if the db exists, if it does then short-circuit + db, err := dbClient.Get( + sdk.Ctx, + sdk.ResourceGroupName, + sdk.ServerName, + properties.DatabaseName, + "serviceTierAdvisors, transparentDataEncryption", + ) + if err == nil && *db.Status == "Online" { + return db, nil + } + + future, err := dbClient.CreateOrUpdate( sdk.Ctx, sdk.ResourceGroupName, sdk.ServerName, @@ -91,52 +139,115 @@ func (sdk GoSDKClient) CreateOrUpdateDB(properties SQLDatabaseProperties) (resul DatabaseProperties: &dbProp, }) if err != nil { - return false, fmt.Errorf("cannot create sql database: %v", err) + return result, err + } + + // TODO: Will needs to remove the sync + err = future.WaitForCompletionRef( + sdk.Ctx, + dbClient.Client, + ) + if err != nil { + return result, err } - return true, nil + return future.Result(dbClient) } -// GetServer returns a server -func (sdk GoSDKClient) GetServer(rgroup, name string) (sql.Server, error) { - serversClient := getGoServersClient() +// DeleteDB deletes a DB +func (sdk GoSDKClient) DeleteDB(databaseName string) (result autorest.Response, err error) { + dbClient := getGoDbClient() - return serversClient.Get( + // check to see if the db exists, if it does then short-circuit + _, err = dbClient.Get( sdk.Ctx, sdk.ResourceGroupName, sdk.ServerName, + databaseName, + "serviceTierAdvisors, transparentDataEncryption", ) + if err != nil { + result = autorest.Response{ + Response: &http.Response{ + StatusCode: 200, + }, + } + return result, nil + } + + result, err = dbClient.Delete( + sdk.Ctx, + sdk.ResourceGroupName, + sdk.ServerName, + databaseName, + ) + + return result, err } -// DeleteDB deletes a DB -func (sdk GoSDKClient) DeleteDB(databaseName string) (result bool, err error) { - dbClient := getGoCBClient() +// DeleteSQLFirewallRule deletes a firewall rule +func (sdk GoSDKClient) DeleteSQLFirewallRule(ruleName string) (err error) { + serversClient := getGoServersClient() + firewallClient := getGoFirewallClient() - _, err = dbClient.Delete( + // check to see if the server exists, if it doesn't then short-circuit + server, err := serversClient.Get( sdk.Ctx, sdk.ResourceGroupName, sdk.ServerName, - databaseName, ) - if err != nil { - return false, fmt.Errorf("cannot delete db: %v", err) + if err != nil || *server.State != "Ready" { + return err } - return true, nil + _, err = firewallClient.Delete( + sdk.Ctx, + sdk.ResourceGroupName, + sdk.ServerName, + ruleName, + ) + + return err } -// DeleteSQLServer deletes a DB -func (sdk GoSDKClient) DeleteSQLServer() (result bool, err error) { +// DeleteSQLServer deletes a SQL server +func (sdk GoSDKClient) DeleteSQLServer() (result autorest.Response, err error) { serversClient := getGoServersClient() - _, err = serversClient.Delete( + // check to see if the server exists, if it doesn't then short-circuit + _, err = serversClient.Get( sdk.Ctx, sdk.ResourceGroupName, sdk.ServerName, ) if err != nil { - return false, fmt.Errorf("cannot delete sql server: %v", err) + result = autorest.Response{ + Response: &http.Response{ + StatusCode: 200, + }, + } + return result, nil } - return true, nil + future, err := serversClient.Delete( + sdk.Ctx, + sdk.ResourceGroupName, + sdk.ServerName, + ) + if err != nil { + return result, err + } + + return future.Result(serversClient) +} + +// IsAsyncNotCompleted returns true if the error is due to async not completed +func (sdk GoSDKClient) IsAsyncNotCompleted(err error) (result bool) { + result = false + if err != nil && strings.Contains(err.Error(), "asynchronous operation has not completed") { + result = true + } else if strings.Contains(err.Error(), "is busy with another operation") { + result = true + } + return result } diff --git a/pkg/resourcemanager/sqlclient/sqlproperties.go b/pkg/resourcemanager/sqlclient/sqlproperties.go index 9ec05bf1d04..32a920793e5 100644 --- a/pkg/resourcemanager/sqlclient/sqlproperties.go +++ b/pkg/resourcemanager/sqlclient/sqlproperties.go @@ -9,38 +9,38 @@ import ( "github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql" ) -// DBAddition - wraps: https://godoc.org/github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql#DatabaseEdition -type DBAddition byte +// DBEdition - wraps: https://godoc.org/github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql#DatabaseEdition +type DBEdition byte const ( // Basic ... - Basic DBAddition = 0 + Basic DBEdition = 0 // Business ... - Business DBAddition = 1 + Business DBEdition = 1 // BusinessCritical ... - BusinessCritical DBAddition = 2 + BusinessCritical DBEdition = 2 // DataWarehouse ... - DataWarehouse DBAddition = 3 + DataWarehouse DBEdition = 3 // Free ... - Free DBAddition = 4 + Free DBEdition = 4 // GeneralPurpose ... - GeneralPurpose DBAddition = 5 + GeneralPurpose DBEdition = 5 // Hyperscale ... - Hyperscale DBAddition = 6 + Hyperscale DBEdition = 6 // Premium ... - Premium DBAddition = 7 + Premium DBEdition = 7 // PremiumRS ... - PremiumRS DBAddition = 8 + PremiumRS DBEdition = 8 // Standard ... - Standard DBAddition = 9 + Standard DBEdition = 9 // Stretch ... - Stretch DBAddition = 10 + Stretch DBEdition = 10 // System ... - System DBAddition = 11 + System DBEdition = 11 // System2 ... - System2 DBAddition = 12 + System2 DBEdition = 12 // Web ... - Web DBAddition = 13 + Web DBEdition = 13 ) // SQLServerProperties contains values needed for adding / updating SQL servers, @@ -54,10 +54,6 @@ type SQLServerProperties struct { // AdministratorLoginPassword - The administrator login password (required for server creation). AdministratorLoginPassword *string - // AllowAzureServicesAccess - allow Azure services and resources to access this server - AllowAzureServicesAccess bool -} - // SQLDatabaseProperties contains values needed for adding / updating SQL servers, // wraps: https://godoc.org/github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql#Database // also wraps: https://godoc.org/github.com/Azure/azure-sdk-for-go/services/preview/sql/mgmt/2015-05-01-preview/sql#DatabaseProperties @@ -78,7 +74,7 @@ type SQLDatabaseProperties struct { // Get-AzSqlServerServiceObjective -Location // ```` // . Possible values include: 'Web', 'Business', 'Basic', 'Standard', 'Premium', 'PremiumRS', 'Free', 'Stretch', 'DataWarehouse', 'System', 'System2', 'GeneralPurpose', 'BusinessCritical', 'Hyperscale' - Edition DBAddition + Edition DBEdition } // SQLServerPropertiesToServer translates SQLServerProperties to ServerProperties @@ -96,14 +92,14 @@ func SQLServerPropertiesToServer(properties SQLServerProperties) (result sql.Ser func SQLDatabasePropertiesToDatabase(properties SQLDatabaseProperties) (result sql.DatabaseProperties) { result = sql.DatabaseProperties{ - Edition: translateDBAddition(properties.Edition), + Edition: translateDBEdition(properties.Edition), } return result } -// translateDBAddition translates enums -func translateDBAddition(in DBAddition) (result sql.DatabaseEdition) { +// translateDBEdition translates enums +func translateDBEdition(in DBEdition) (result sql.DatabaseEdition) { switch in { case 0: result = sql.Basic