From cb26623f5fb2116c323585cc40d21fb1948edc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Tue, 19 Jan 2021 15:47:05 +0100 Subject: [PATCH 01/15] Upgrade operator-sdk to 1.3.0 without e2e tests --- .dockerignore | 5 + .gitignore | 3 + Dockerfile | 31 + Makefile | 151 +- PROJECT | 13 + api/v1alpha2/groupversion_info.go | 36 + .../jenkins => api}/v1alpha2/jenkins_types.go | 47 +- .../v1alpha2/jenkinsimage_types.go | 7 +- .../apis/jenkins => api}/v1alpha2/register.go | 4 - .../v1alpha2/zz_generated.deepcopy.go | 85 +- cmd/manager/main.go | 277 -- config.base.env | 4 +- config/certmanager/certificate.yaml | 25 + config/certmanager/kustomization.yaml | 5 + config/certmanager/kustomizeconfig.yaml | 16 + .../bases/jenkins.io.jenkins.io_jenkins.yaml | 3420 +++++++++++++++++ .../jenkins.io.jenkins.io_jenkinsimages.yaml | 95 + config/crd/kustomization.yaml | 21 + config/crd/kustomizeconfig.yaml | 19 + .../crd/patches/cainjection_in_jenkins.yaml | 7 + config/crd/patches/webhook_in_jenkins.yaml | 14 + config/default/kustomization.yaml | 74 + config/default/manager_auth_proxy_patch.yaml | 26 + config/default/manager_config_patch.yaml | 20 + config/manager/controller_manager_config.yaml | 11 + config/manager/kustomization.yaml | 16 + config/manager/manager.yaml | 60 + config/prometheus/kustomization.yaml | 2 + config/prometheus/monitor.yaml | 16 + .../rbac/auth_proxy_client_clusterrole.yaml | 7 + config/rbac/auth_proxy_role.yaml | 13 + config/rbac/auth_proxy_role_binding.yaml | 12 + config/rbac/auth_proxy_service.yaml | 14 + config/rbac/jenkins_editor_role.yaml | 24 + config/rbac/jenkins_viewer_role.yaml | 20 + config/rbac/kustomization.yaml | 12 + config/rbac/leader_election_role.yaml | 27 + config/rbac/leader_election_role_binding.yaml | 12 + config/rbac/role.yaml | 62 + config/rbac/role_binding.yaml | 12 + .../samples/jenkins.io_v1alpha2_jenkins.yaml | 55 + config/samples/kustomization.yaml | 4 + config/scorecard/bases/config.yaml | 7 + config/scorecard/kustomization.yaml | 16 + config/scorecard/patches/basic.config.yaml | 10 + config/scorecard/patches/olm.config.yaml | 50 + .../jenkins => controllers}/handler.go | 26 +- .../jenkinsImage_controller.go | 97 +- .../jenkins_controller.go | 179 +- go.mod | 80 +- go.sum | 1283 ++++--- hack/boilerplate.go.txt | 15 + main.go | 208 + operator-sdk.mk | 94 + pkg/apis/apis.go | 24 - pkg/apis/jenkins/v1alpha2/doc.go | 4 - .../jenkins/v1alpha2/zz_generated.openapi.go | 275 -- .../backuprestore/backuprestore.go | 28 +- pkg/configuration/base/configmap.go | 6 +- pkg/configuration/base/container.go | 2 +- pkg/configuration/base/deployment.go | 4 +- pkg/configuration/base/label.go | 4 +- pkg/configuration/base/plugin.go | 8 +- pkg/configuration/base/pod.go | 25 +- pkg/configuration/base/rbac.go | 5 +- pkg/configuration/base/reconcile_test.go | 34 +- .../base/{reconcile.go => reconciler.go} | 63 +- .../resources/base_configuration_configmap.go | 2 +- pkg/configuration/base/resources/builder.go | 15 +- .../base/resources/deployment.go | 2 +- .../resources/init_configuration_configmap.go | 2 +- pkg/configuration/base/resources/meta.go | 2 +- .../resources/operator_credentials_secret.go | 2 +- pkg/configuration/base/resources/pod.go | 2 +- pkg/configuration/base/resources/pod_test.go | 3 +- .../base/resources/resources_test.go | 4 +- pkg/configuration/base/resources/route.go | 6 +- .../base/resources/scripts_configmap.go | 2 +- pkg/configuration/base/resources/service.go | 29 +- pkg/configuration/base/route.go | 8 +- pkg/configuration/base/service.go | 4 +- pkg/configuration/base/serviceaccount.go | 2 +- pkg/configuration/base/validate.go | 32 +- pkg/configuration/base/validate_test.go | 40 +- pkg/configuration/configuration.go | 45 +- pkg/configuration/user/casc/casc.go | 4 +- pkg/configuration/user/reconcile.go | 6 +- pkg/configuration/user/seedjobs/seedjobs.go | 18 +- .../user/seedjobs/seedjobs_test.go | 11 +- pkg/configuration/user/seedjobs/validate.go | 3 +- .../user/seedjobs/validate_test.go | 54 +- pkg/configuration/user/validate.go | 2 +- pkg/controller/jenkins/reconciler.go | 52 - pkg/groovy/groovy.go | 30 +- pkg/groovy/groovy_test.go | 26 +- pkg/notifications/event/event.go | 5 +- pkg/notifications/mailgun/mailgun.go | 2 +- pkg/notifications/mailgun/mailgun_test.go | 10 +- pkg/notifications/msteams/msteams.go | 2 +- pkg/notifications/msteams/msteams_test.go | 34 +- pkg/notifications/provider/provider.go | 2 +- pkg/notifications/sender.go | 2 +- pkg/notifications/slack/slack.go | 2 +- pkg/notifications/slack/slack_test.go | 12 +- pkg/notifications/smtp/smtp.go | 2 +- pkg/notifications/smtp/smtp_test.go | 20 +- tools.go | 15 - variables.mk | 96 + 108 files changed, 5962 insertions(+), 1981 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 PROJECT create mode 100644 api/v1alpha2/groupversion_info.go rename {pkg/apis/jenkins => api}/v1alpha2/jenkins_types.go (96%) rename {pkg/apis/jenkins => api}/v1alpha2/jenkinsimage_types.go (87%) rename {pkg/apis/jenkins => api}/v1alpha2/register.go (87%) rename {pkg/apis/jenkins => api}/v1alpha2/zz_generated.deepcopy.go (95%) delete mode 100644 cmd/manager/main.go create mode 100644 config/certmanager/certificate.yaml create mode 100644 config/certmanager/kustomization.yaml create mode 100644 config/certmanager/kustomizeconfig.yaml create mode 100644 config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml create mode 100644 config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml create mode 100644 config/crd/kustomization.yaml create mode 100644 config/crd/kustomizeconfig.yaml create mode 100644 config/crd/patches/cainjection_in_jenkins.yaml create mode 100644 config/crd/patches/webhook_in_jenkins.yaml create mode 100644 config/default/kustomization.yaml create mode 100644 config/default/manager_auth_proxy_patch.yaml create mode 100644 config/default/manager_config_patch.yaml create mode 100644 config/manager/controller_manager_config.yaml create mode 100644 config/manager/kustomization.yaml create mode 100644 config/manager/manager.yaml create mode 100644 config/prometheus/kustomization.yaml create mode 100644 config/prometheus/monitor.yaml create mode 100644 config/rbac/auth_proxy_client_clusterrole.yaml create mode 100644 config/rbac/auth_proxy_role.yaml create mode 100644 config/rbac/auth_proxy_role_binding.yaml create mode 100644 config/rbac/auth_proxy_service.yaml create mode 100644 config/rbac/jenkins_editor_role.yaml create mode 100644 config/rbac/jenkins_viewer_role.yaml create mode 100644 config/rbac/kustomization.yaml create mode 100644 config/rbac/leader_election_role.yaml create mode 100644 config/rbac/leader_election_role_binding.yaml create mode 100644 config/rbac/role.yaml create mode 100644 config/rbac/role_binding.yaml create mode 100644 config/samples/jenkins.io_v1alpha2_jenkins.yaml create mode 100644 config/samples/kustomization.yaml create mode 100644 config/scorecard/bases/config.yaml create mode 100644 config/scorecard/kustomization.yaml create mode 100644 config/scorecard/patches/basic.config.yaml create mode 100644 config/scorecard/patches/olm.config.yaml rename {pkg/controller/jenkins => controllers}/handler.go (77%) rename pkg/controller/jenkinsimage/jenkinsimage_controller.go => controllers/jenkinsImage_controller.go (53%) rename {pkg/controller/jenkins => controllers}/jenkins_controller.go (77%) create mode 100644 hack/boilerplate.go.txt create mode 100644 main.go create mode 100644 operator-sdk.mk delete mode 100644 pkg/apis/apis.go delete mode 100644 pkg/apis/jenkins/v1alpha2/doc.go delete mode 100644 pkg/apis/jenkins/v1alpha2/zz_generated.openapi.go rename pkg/configuration/base/{reconcile.go => reconciler.go} (80%) delete mode 100644 pkg/controller/jenkins/reconciler.go delete mode 100644 tools.go create mode 100644 variables.mk diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..243f81a5 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore all files which are not go type +!**/*.go +!**/*.mod +!**/*.sum diff --git a/.gitignore b/.gitignore index 5a3cd2b3..ff5b041f 100644 --- a/.gitignore +++ b/.gitignore @@ -88,3 +88,6 @@ tags ### IntelliJ IDEA ### *.iml + +bin +testbin/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..da398ee5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,31 @@ +# Build the manager binary +FROM golang:1.15 as builder + +WORKDIR /workspace +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go mod download + +# Copy the go source +COPY api/ api/ +COPY controllers/ controllers/ +COPY internal/ internal/ +COPY pkg/ pkg/ +COPY version/ version/ +COPY main.go main.go + +# Build +# FIXME look at makefile build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go + +# Use distroless as minimal base image to package the manager binary +# Refer to https://github.com/GoogleContainerTools/distroless for more details +FROM gcr.io/distroless/static:nonroot +WORKDIR / +COPY --from=builder /workspace/manager . +USER 65532:65532 + +ENTRYPOINT ["/manager"] diff --git a/Makefile b/Makefile index 0fcfdcb9..03a8c6df 100644 --- a/Makefile +++ b/Makefile @@ -1,74 +1,4 @@ -# Set POSIX sh for maximum interoperability -SHELL := /bin/sh -PATH := $(GOPATH)/bin:$(PATH) - -OSFLAG := -ifeq ($(OS),Windows_NT) - OSFLAG = WIN32 -else - UNAME_S := $(shell uname -s) - ifeq ($(UNAME_S),Linux) - OSFLAG = LINUX - endif - ifeq ($(UNAME_S),Darwin) - OSFLAG = OSX - endif -endif - -include config.base.env - -# Import config -# You can change the default config with `make config="config_special.env" build` -config ?= config.minikube.env -include $(config) - -# Set an output prefix, which is the local directory if not specified -PREFIX?=$(shell pwd) - -VERSION := $(shell cat VERSION.txt) -GITCOMMIT := $(shell git rev-parse --short HEAD) -GITBRANCH := $(shell git rev-parse --abbrev-ref HEAD) -GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no) -GITIGNOREDBUTTRACKEDCHANGES := $(shell git ls-files -i --exclude-standard) -ifneq ($(GITUNTRACKEDCHANGES),) - GITCOMMIT := $(GITCOMMIT)-dirty -endif -ifneq ($(GITIGNOREDBUTTRACKEDCHANGES),) - GITCOMMIT := $(GITCOMMIT)-dirty -endif - -VERSION_TAG := $(VERSION) -LATEST_TAG := latest -BUILD_TAG := $(GITBRANCH)-$(GITCOMMIT) - -BUILD_PATH := ./cmd/manager - -# CONTAINER_RUNTIME_COMMAND is Container Runtime - it could be docker or podman -CONTAINER_RUNTIME_COMMAND := docker - -# Set any default go build tags -BUILDTAGS := - -# Set the build dir, where built cross-compiled binaries will be output -BUILDDIR := ${PREFIX}/cross - -CTIMEVAR=-X $(PKG)/version.GitCommit=$(GITCOMMIT) -X $(PKG)/version.Version=$(VERSION) -GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)" -GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static" - -# List the GOOS and GOARCH to build -GOOSARCHES = linux/amd64 - -PACKAGES = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor) -PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e) - -# Run all the e2e tests by default -E2E_TEST_SELECTOR ?= .* - -JENKINS_API_HOSTNAME := $(shell $(JENKINS_API_HOSTNAME_COMMAND) 2> /dev/null || echo "" ) -OPERATOR_ARGS ?= --jenkins-api-hostname=$(JENKINS_API_HOSTNAME) --jenkins-api-port=$(JENKINS_API_PORT) --jenkins-api-use-nodeport=$(JENKINS_API_USE_NODEPORT) --cluster-domain=$(CLUSTER_DOMAIN) $(OPERATOR_EXTRA_ARGS) - -.DEFAULT_GOAL := help +include variables.mk .PHONY: all all: status checkmake clean build verify install container-runtime-build container-runtime-images ## Build the image @@ -121,7 +51,7 @@ build: deepcopy-gen $(NAME) ## Builds a dynamic executable or package .PHONY: $(NAME) $(NAME): $(wildcard *.go) $(wildcard */*.go) VERSION.txt @echo "+ $@" - CGO_ENABLED=0 go build -tags "$(BUILDTAGS)" ${GO_LDFLAGS} -o build/_output/bin/jenkins-operator $(BUILD_PATH) + CGO_ENABLED=0 go build -tags "$(BUILDTAGS)" ${GO_LDFLAGS} -o bin/manager $(BUILD_PATH) .PHONY: static static: ## Builds a static executable @@ -206,6 +136,7 @@ vet: ## Verifies `go vet` passes @echo "+ $@" @go vet $(PACKAGES) +#FIXME download to tmp not locally .PHONY: staticcheck HAS_STATICCHECK := $(shell which staticcheck) PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) @@ -242,7 +173,7 @@ install: ## Installs the executable .PHONY: run run: export WATCH_NAMESPACE = $(NAMESPACE) run: export OPERATOR_NAME = $(NAME) -run: build ## Run the executable, you can use EXTRA_ARGS +run: fmt vet manifests install build ## Run the executable, you can use EXTRA_ARGS @echo "+ $@" ifeq ($(KUBERNETES_PROVIDER),minikube) kubectl config use-context $(KUBECTL_CONTEXT) @@ -250,10 +181,8 @@ endif ifeq ($(KUBERNETES_PROVIDER),crc) oc project $(CRC_OC_PROJECT) endif - kubectl apply -f deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml - kubectl apply -f deploy/crds/jenkins_$(API_VERSION)_jenkinsimage_crd.yaml @echo "Watching '$(WATCH_NAMESPACE)' namespace" - build/_output/bin/jenkins-operator $(OPERATOR_ARGS) + bin/manager $(OPERATOR_ARGS) .PHONY: clean clean: ## Cleanup any build binaries or packages @@ -368,27 +297,18 @@ container-runtime-run: ## Run the container in docker, you can use EXTRA_ARGS .PHONY: minikube-run minikube-run: export WATCH_NAMESPACE = $(NAMESPACE) minikube-run: export OPERATOR_NAME = $(NAME) -minikube-run: minikube-start ## Run the operator locally and use minikube as Kubernetes cluster, you can use OPERATOR_ARGS +minikube-run: minikube-start run ## Run the operator locally and use minikube as Kubernetes cluster, you can use OPERATOR_ARGS @echo "+ $@" - kubectl config use-context minikube - kubectl apply -f deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml - @echo "Watching '$(WATCH_NAMESPACE)' namespace" - build/_output/bin/jenkins-operator $(OPERATOR_ARGS) .PHONY: crc-run crc-run: export WATCH_NAMESPACE = $(NAMESPACE) crc-run: export OPERATOR_NAME = $(NAME) -crc-run: crc-start ## Run the operator locally and use CodeReady Containers as Kubernetes cluster, you can use OPERATOR_ARGS +crc-run: crc-start run ## Run the operator locally and use CodeReady Containers as Kubernetes cluster, you can use OPERATOR_ARGS @echo "+ $@" - oc project $(CRC_OC_PROJECT) - kubectl apply -f deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml - @echo "Watching '$(WATCH_NAMESPACE)' namespace" - build/_output/bin/jenkins-operator $(OPERATOR_ARGS) .PHONY: deepcopy-gen -deepcopy-gen: ## Generate deepcopy golang code +deepcopy-gen: generate ## Generate deepcopy golang code @echo "+ $@" - operator-sdk generate k8s .PHONY: scheme-doc-gen HAS_GEN_CRD_API_REFERENCE_DOCS := $(shell ls gen-crd-api-reference-docs 2> /dev/null) @@ -498,3 +418,58 @@ generate-docs: ## Re-generate docs directory from the website directory @echo "+ $@" rm -rf docs || echo "Cannot remove docs dir, ignoring" hugo -s website -d ../docs + +##################### FROM OPERATOR SDK ######################## +#TODO rename +# Install CRDs into a cluster +install: manifests kustomize + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +# Uninstall CRDs from a cluster +uninstall: manifests kustomize + $(KUSTOMIZE) build config/crd | kubectl delete -f - + +# Generate manifests e.g. CRD, RBAC etc. +manifests: controller-gen + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +# Generate code +generate: controller-gen + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +# Download controller-gen locally if necessary +CONTROLLER_GEN = $(shell pwd)/bin/controller-gen +controller-gen: + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) + +# Download kustomize locally if necessary +KUSTOMIZE = $(shell pwd)/bin/kustomize +kustomize: + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) + +# go-get-tool will 'go get' any package $2 and install it to $1. +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +define go-get-tool +@[ -f $(1) ] || { \ +set -e ;\ +TMP_DIR=$$(mktemp -d) ;\ +cd $$TMP_DIR ;\ +go mod init tmp ;\ +echo "Downloading $(2)" ;\ +GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ +rm -rf $$TMP_DIR ;\ +} +endef + +# Generate bundle manifests and metadata, then validate generated files. +.PHONY: bundle +bundle: manifests kustomize + operator-sdk generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + operator-sdk bundle validate ./bundle + +# Build the bundle image. +.PHONY: bundle-build +bundle-build: + docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . diff --git a/PROJECT b/PROJECT new file mode 100644 index 00000000..7ae7f502 --- /dev/null +++ b/PROJECT @@ -0,0 +1,13 @@ +domain: jenkins.io +layout: go.kubebuilder.io/v3 +projectName: jenkins-operator +repo: github.com/jenkinsci/kubernetes-operator +resources: +- crdVersion: v1 + group: jenkins.io + kind: Jenkins + version: v1alpha2 +version: 3-alpha +plugins: + manifests.sdk.operatorframework.io/v2: {} + scorecard.sdk.operatorframework.io/v2: {} diff --git a/api/v1alpha2/groupversion_info.go b/api/v1alpha2/groupversion_info.go new file mode 100644 index 00000000..0cfebbdc --- /dev/null +++ b/api/v1alpha2/groupversion_info.go @@ -0,0 +1,36 @@ +/* +Copyright 2021. + +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 v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group +// +kubebuilder:object:generate=true +// +groupName=jenkins.io.jenkins.io +package v1alpha2 + +import ( + "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" +) + +var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "jenkins.io.jenkins.io", Version: "v1alpha2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme +) diff --git a/pkg/apis/jenkins/v1alpha2/jenkins_types.go b/api/v1alpha2/jenkins_types.go similarity index 96% rename from pkg/apis/jenkins/v1alpha2/jenkins_types.go rename to api/v1alpha2/jenkins_types.go index 61b377c9..7a1461b6 100644 --- a/pkg/apis/jenkins/v1alpha2/jenkins_types.go +++ b/api/v1alpha2/jenkins_types.go @@ -1,3 +1,19 @@ +/* +Copyright 2021. + +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 v1alpha2 import ( @@ -6,8 +22,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// JenkinsSpec defines the desired state of the Jenkins. -// +k8s:openapi-gen=true +// JenkinsSpec defines the desired state of Jenkins type JenkinsSpec struct { // Master represents Jenkins master pod properties and Jenkins plugins. // Every single change here requires a pod restart. @@ -264,14 +279,6 @@ type JenkinsMaster struct { // +optional Annotations map[string]string `json:"annotations,omitempty"` - // 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 - // Deprecated: will be removed in the future, please use Annotations(annotations) - // +optional - AnnotationsDeprecated map[string]string `json:"masterAnnotations,omitempty"` - // 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. @@ -442,7 +449,6 @@ type Service struct { } // JenkinsStatus defines the observed state of Jenkins -// +k8s:openapi-gen=true type JenkinsStatus struct { // OperatorVersion is the operator version which manages this CR // +optional @@ -489,26 +495,21 @@ type JenkinsStatus struct { AppliedGroovyScripts []AppliedGroovyScript `json:"appliedGroovyScripts,omitempty"` } -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // Jenkins is the Schema for the jenkins API -// +k8s:openapi-gen=true -// +kubebuilder:subresource:status type Jenkins struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - // Spec defines the desired state of the Jenkins - Spec JenkinsSpec `json:"spec,omitempty"` - - // Status defines the observed state of Jenkins + Spec JenkinsSpec `json:"spec,omitempty"` Status JenkinsStatus `json:"status,omitempty"` } -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true -// JenkinsList contains a list of Jenkins. +// JenkinsList contains a list of Jenkins type JenkinsList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` @@ -673,3 +674,7 @@ type GroovyScripts struct { type ConfigurationAsCode struct { Customization `json:",inline"` } + +func init() { + SchemeBuilder.Register(&Jenkins{}, &JenkinsList{}) +} diff --git a/pkg/apis/jenkins/v1alpha2/jenkinsimage_types.go b/api/v1alpha2/jenkinsimage_types.go similarity index 87% rename from pkg/apis/jenkins/v1alpha2/jenkinsimage_types.go rename to api/v1alpha2/jenkinsimage_types.go index 4cd63654..1ac64dca 100644 --- a/pkg/apis/jenkins/v1alpha2/jenkinsimage_types.go +++ b/api/v1alpha2/jenkinsimage_types.go @@ -1,11 +1,6 @@ package v1alpha2 -import ( - 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. +import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" // JenkinsImageSpec defines the desired state of JenkinsImage type JenkinsImageSpec struct { diff --git a/pkg/apis/jenkins/v1alpha2/register.go b/api/v1alpha2/register.go similarity index 87% rename from pkg/apis/jenkins/v1alpha2/register.go rename to api/v1alpha2/register.go index 57aa81e4..8013c209 100644 --- a/pkg/apis/jenkins/v1alpha2/register.go +++ b/api/v1alpha2/register.go @@ -6,7 +6,6 @@ package v1alpha2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" ) const ( @@ -17,9 +16,6 @@ const ( var ( // SchemeGroupVersion is group version used to register these objects SchemeGroupVersion = schema.GroupVersion{Group: "jenkins.io", Version: "v1alpha2"} - - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: SchemeGroupVersion} ) // GetObjectKind returns Jenkins object kind diff --git a/pkg/apis/jenkins/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go similarity index 95% rename from pkg/apis/jenkins/v1alpha2/zz_generated.deepcopy.go rename to api/v1alpha2/zz_generated.deepcopy.go index f390614c..af28e1fd 100644 --- a/pkg/apis/jenkins/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -1,11 +1,27 @@ // +build !ignore_autogenerated -// Code generated by operator-sdk. DO NOT EDIT. +/* +Copyright 2021. + +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. +*/ + +// Code generated by controller-gen. DO NOT EDIT. package v1alpha2 import ( - v1 "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -13,7 +29,6 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AppliedGroovyScript) DeepCopyInto(out *AppliedGroovyScript) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AppliedGroovyScript. @@ -30,7 +45,6 @@ func (in *AppliedGroovyScript) DeepCopy() *AppliedGroovyScript { func (in *Backup) DeepCopyInto(out *Backup) { *out = *in in.Action.DeepCopyInto(&out.Action) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backup. @@ -46,7 +60,6 @@ func (in *Backup) DeepCopy() *Backup { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ConfigMapRef) DeepCopyInto(out *ConfigMapRef) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigMapRef. @@ -63,7 +76,6 @@ func (in *ConfigMapRef) DeepCopy() *ConfigMapRef { func (in *ConfigurationAsCode) DeepCopyInto(out *ConfigurationAsCode) { *out = *in in.Customization.DeepCopyInto(&out.Customization) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigurationAsCode. @@ -92,51 +104,50 @@ func (in *Container) DeepCopyInto(out *Container) { } if in.Ports != nil { in, out := &in.Ports, &out.Ports - *out = make([]v1.ContainerPort, len(*in)) + *out = make([]corev1.ContainerPort, len(*in)) copy(*out, *in) } if in.EnvFrom != nil { in, out := &in.EnvFrom, &out.EnvFrom - *out = make([]v1.EnvFromSource, len(*in)) + *out = make([]corev1.EnvFromSource, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.Env != nil { in, out := &in.Env, &out.Env - *out = make([]v1.EnvVar, len(*in)) + *out = make([]corev1.EnvVar, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.VolumeMounts != nil { in, out := &in.VolumeMounts, &out.VolumeMounts - *out = make([]v1.VolumeMount, len(*in)) + *out = make([]corev1.VolumeMount, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.LivenessProbe != nil { in, out := &in.LivenessProbe, &out.LivenessProbe - *out = new(v1.Probe) + *out = new(corev1.Probe) (*in).DeepCopyInto(*out) } if in.ReadinessProbe != nil { in, out := &in.ReadinessProbe, &out.ReadinessProbe - *out = new(v1.Probe) + *out = new(corev1.Probe) (*in).DeepCopyInto(*out) } if in.Lifecycle != nil { in, out := &in.Lifecycle, &out.Lifecycle - *out = new(v1.Lifecycle) + *out = new(corev1.Lifecycle) (*in).DeepCopyInto(*out) } if in.SecurityContext != nil { in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.SecurityContext) + *out = new(corev1.SecurityContext) (*in).DeepCopyInto(*out) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Container. @@ -158,7 +169,6 @@ func (in *Customization) DeepCopyInto(out *Customization) { *out = make([]ConfigMapRef, len(*in)) copy(*out, *in) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Customization. @@ -175,7 +185,6 @@ func (in *Customization) DeepCopy() *Customization { func (in *GroovyScripts) DeepCopyInto(out *GroovyScripts) { *out = *in in.Customization.DeepCopyInto(&out.Customization) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroovyScripts. @@ -193,10 +202,9 @@ func (in *Handler) DeepCopyInto(out *Handler) { *out = *in if in.Exec != nil { in, out := &in.Exec, &out.Exec - *out = new(v1.ExecAction) + *out = new(corev1.ExecAction) (*in).DeepCopyInto(*out) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Handler. @@ -212,7 +220,6 @@ func (in *Handler) DeepCopy() *Handler { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Image) DeepCopyInto(out *Image) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Image. @@ -232,7 +239,6 @@ func (in *Jenkins) DeepCopyInto(out *Jenkins) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Jenkins. @@ -256,7 +262,6 @@ func (in *Jenkins) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JenkinsAPISettings) DeepCopyInto(out *JenkinsAPISettings) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsAPISettings. @@ -276,7 +281,6 @@ func (in *JenkinsImage) DeepCopyInto(out *JenkinsImage) { in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImage. @@ -309,7 +313,6 @@ func (in *JenkinsImageList) DeepCopyInto(out *JenkinsImageList) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImageList. @@ -339,7 +342,6 @@ func (in *JenkinsImageSpec) DeepCopyInto(out *JenkinsImageSpec) { *out = make([]JenkinsPlugin, len(*in)) copy(*out, *in) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImageSpec. @@ -360,7 +362,6 @@ func (in *JenkinsImageStatus) DeepCopyInto(out *JenkinsImageStatus) { *out = make([]JenkinsPlugin, len(*in)) copy(*out, *in) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImageStatus. @@ -385,7 +386,6 @@ func (in *JenkinsList) DeepCopyInto(out *JenkinsList) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsList. @@ -416,13 +416,6 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { (*out)[key] = val } } - if in.AnnotationsDeprecated != nil { - in, out := &in.AnnotationsDeprecated, &out.AnnotationsDeprecated - *out = make(map[string]string, len(*in)) - for key, val := range *in { - (*out)[key] = val - } - } if in.Labels != nil { in, out := &in.Labels, &out.Labels *out = make(map[string]string, len(*in)) @@ -439,7 +432,7 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { } if in.SecurityContext != nil { in, out := &in.SecurityContext, &out.SecurityContext - *out = new(v1.PodSecurityContext) + *out = new(corev1.PodSecurityContext) (*in).DeepCopyInto(*out) } if in.Containers != nil { @@ -451,19 +444,19 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { } if in.ImagePullSecrets != nil { in, out := &in.ImagePullSecrets, &out.ImagePullSecrets - *out = make([]v1.LocalObjectReference, len(*in)) + *out = make([]corev1.LocalObjectReference, len(*in)) copy(*out, *in) } if in.Volumes != nil { in, out := &in.Volumes, &out.Volumes - *out = make([]v1.Volume, len(*in)) + *out = make([]corev1.Volume, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } if in.Tolerations != nil { in, out := &in.Tolerations, &out.Tolerations - *out = make([]v1.Toleration, len(*in)) + *out = make([]corev1.Toleration, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -478,7 +471,6 @@ func (in *JenkinsMaster) DeepCopyInto(out *JenkinsMaster) { *out = make([]Plugin, len(*in)) copy(*out, *in) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsMaster. @@ -494,7 +486,6 @@ func (in *JenkinsMaster) DeepCopy() *JenkinsMaster { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JenkinsPlugin) DeepCopyInto(out *JenkinsPlugin) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsPlugin. @@ -536,7 +527,6 @@ func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) { } in.ServiceAccount.DeepCopyInto(&out.ServiceAccount) out.JenkinsAPISettings = in.JenkinsAPISettings - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsSpec. @@ -574,7 +564,6 @@ func (in *JenkinsStatus) DeepCopyInto(out *JenkinsStatus) { *out = make([]AppliedGroovyScript, len(*in)) copy(*out, *in) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsStatus. @@ -591,7 +580,6 @@ func (in *JenkinsStatus) DeepCopy() *JenkinsStatus { func (in *Mailgun) DeepCopyInto(out *Mailgun) { *out = *in out.APIKeySecretKeySelector = in.APIKeySecretKeySelector - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Mailgun. @@ -608,7 +596,6 @@ func (in *Mailgun) DeepCopy() *Mailgun { func (in *MicrosoftTeams) DeepCopyInto(out *MicrosoftTeams) { *out = *in out.WebHookURLSecretKeySelector = in.WebHookURLSecretKeySelector - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MicrosoftTeams. @@ -644,7 +631,6 @@ func (in *Notification) DeepCopyInto(out *Notification) { *out = new(SMTP) **out = **in } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Notification. @@ -660,7 +646,6 @@ func (in *Notification) DeepCopy() *Notification { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Plugin) DeepCopyInto(out *Plugin) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Plugin. @@ -678,7 +663,6 @@ func (in *Restore) DeepCopyInto(out *Restore) { *out = *in in.Action.DeepCopyInto(&out.Action) in.GetLatestAction.DeepCopyInto(&out.GetLatestAction) - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Restore. @@ -696,7 +680,6 @@ func (in *SMTP) DeepCopyInto(out *SMTP) { *out = *in out.UsernameSecretKeySelector = in.UsernameSecretKeySelector out.PasswordSecretKeySelector = in.PasswordSecretKeySelector - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SMTP. @@ -713,7 +696,6 @@ func (in *SMTP) DeepCopy() *SMTP { func (in *SecretKeySelector) DeepCopyInto(out *SecretKeySelector) { *out = *in out.LocalObjectReference = in.LocalObjectReference - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretKeySelector. @@ -729,7 +711,6 @@ func (in *SecretKeySelector) DeepCopy() *SecretKeySelector { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SecretRef) DeepCopyInto(out *SecretRef) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretRef. @@ -745,7 +726,6 @@ func (in *SecretRef) DeepCopy() *SecretRef { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *SeedJob) DeepCopyInto(out *SeedJob) { *out = *in - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SeedJob. @@ -780,7 +760,6 @@ func (in *Service) DeepCopyInto(out *Service) { *out = make([]string, len(*in)) copy(*out, *in) } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Service. @@ -803,7 +782,6 @@ func (in *ServiceAccount) DeepCopyInto(out *ServiceAccount) { (*out)[key] = val } } - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccount. @@ -820,7 +798,6 @@ func (in *ServiceAccount) DeepCopy() *ServiceAccount { func (in *Slack) DeepCopyInto(out *Slack) { *out = *in out.WebHookURLSecretKeySelector = in.WebHookURLSecretKeySelector - return } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Slack. diff --git a/cmd/manager/main.go b/cmd/manager/main.go deleted file mode 100644 index 165aed93..00000000 --- a/cmd/manager/main.go +++ /dev/null @@ -1,277 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "os" - "runtime" - - "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkinsimage" - - // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) - _ "k8s.io/client-go/plugin/pkg/client/auth" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis" - "github.com/jenkinsci/kubernetes-operator/pkg/client" - "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" - "github.com/jenkinsci/kubernetes-operator/pkg/constants" - "github.com/jenkinsci/kubernetes-operator/pkg/controller/jenkins" - "github.com/jenkinsci/kubernetes-operator/pkg/event" - "github.com/jenkinsci/kubernetes-operator/pkg/log" - "github.com/jenkinsci/kubernetes-operator/pkg/notifications" - e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" - "github.com/jenkinsci/kubernetes-operator/version" - - "github.com/operator-framework/operator-sdk/pkg/k8sutil" - kubemetrics "github.com/operator-framework/operator-sdk/pkg/kube-metrics" - "github.com/operator-framework/operator-sdk/pkg/leader" - "github.com/operator-framework/operator-sdk/pkg/log/zap" - "github.com/operator-framework/operator-sdk/pkg/metrics" - sdkVersion "github.com/operator-framework/operator-sdk/version" - "github.com/pkg/errors" - "github.com/spf13/pflag" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/util/intstr" - "k8s.io/client-go/kubernetes" - _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client/config" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/manager/signals" -) - -// Change below variables to serve metrics on different host or port. -var ( - metricsHost = "0.0.0.0" - metricsPort int32 = 8383 - operatorMetricsPort int32 = 8686 -) - -var logger = log.Log.WithName("cmd") - -func printInfo() { - logger.Info(fmt.Sprintf("Version: %s", version.Version)) - logger.Info(fmt.Sprintf("Git commit: %s", version.GitCommit)) - logger.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) - logger.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) - logger.Info(fmt.Sprintf("operator-sdk Version: %v", sdkVersion.Version)) -} - -func main() { - // Add the zap logger flag set to the CLI. The flag set must - // be added before calling pflag.Parse(). - pflag.CommandLine.AddFlagSet(zap.FlagSet()) - - // Add flags registered by imported packages (e.g. glog and - // controller-runtime) - pflag.CommandLine.AddGoFlagSet(flag.CommandLine) - - hostname := pflag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") - port := pflag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") - useNodePort := pflag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") - debug := pflag.Bool("debug", false, "Set log level to debug") - kubernetesClusterDomain := pflag.String("cluster-domain", "cluster.local", "Use custom domain name instead of 'cluster.local'.") - pflag.Parse() - - log.SetupLogger(*debug) - printInfo() - - namespace, err := k8sutil.GetWatchNamespace() - if err != nil { - fatal(errors.Wrap(err, "failed to get watch namespace"), *debug) - } - logger.Info(fmt.Sprintf("Watch namespace: %v", namespace)) - - // get a config to talk to the apiserver - cfg, err := config.GetConfig() - if err != nil { - fatal(errors.Wrap(err, "failed to get config"), *debug) - } - - ctx := context.TODO() - - // Become the leader before proceeding - err = leader.Become(ctx, "jenkins-operator-lock") - if err != nil { - fatal(errors.Wrap(err, "failed to become leader"), *debug) - } - - // Create a new Cmd to provide shared dependencies and start components - mgr, err := manager.New(cfg, manager.Options{ - Namespace: namespace, - MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), - }) - if err != nil { - fatal(errors.Wrap(err, "failed to create manager"), *debug) - } - - logger.Info("Registering Components.") - - // setup Scheme for all resources - if err := apis.AddToScheme(mgr.GetScheme()); err != nil { - fatal(errors.Wrap(err, "failed to setup scheme"), *debug) - } - - // setup events - events, err := event.New(cfg, constants.OperatorName) - if err != nil { - fatal(errors.Wrap(err, "failed to create manager"), *debug) - } - - clientSet, err := kubernetes.NewForConfig(cfg) - if err != nil { - fatal(errors.Wrap(err, "failed to create Kubernetes client set"), *debug) - } - - if resources.IsRouteAPIAvailable(clientSet) { - logger.Info("Route API found: Route creation will be performed") - } - c := make(chan e.Event) - go notifications.Listen(c, events, mgr.GetClient()) - - // validate jenkins API connection - jenkinsAPIConnectionSettings := client.JenkinsAPIConnectionSettings{Hostname: *hostname, Port: *port, UseNodePort: *useNodePort} - if err := jenkinsAPIConnectionSettings.Validate(); err != nil { - fatal(errors.Wrap(err, "invalid command line parameters"), *debug) - } - - // validate kubernetes cluster domain - if *kubernetesClusterDomain == "" { - fatal(errors.Wrap(err, "Kubernetes cluster domain can't be empty"), *debug) - } - - // setup Jenkins controller - if err := jenkins.Add(mgr, jenkinsAPIConnectionSettings, *kubernetesClusterDomain, *clientSet, *cfg, &c); err != nil { - fatal(errors.Wrap(err, "failed to setup controllers"), *debug) - } - // setup JenkinsImage controller - if err = jenkinsimage.Add(mgr); err != nil { - fatal(errors.Wrap(err, "failed to setup controllers"), *debug) - } - - if err = serveCRMetrics(cfg); err != nil { - logger.V(log.VWarn).Info("Could not generate and serve custom resource metrics", "error", err.Error()) - } - - // Add to the below struct any other metrics ports you want to expose. - servicePorts := []v1.ServicePort{ - {Port: metricsPort, Name: metrics.OperatorPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: metricsPort}}, - {Port: operatorMetricsPort, Name: metrics.CRPortName, Protocol: v1.ProtocolTCP, TargetPort: intstr.IntOrString{Type: intstr.Int, IntVal: operatorMetricsPort}}, - } - // Create Service object to expose the metrics port(s). - service, err := metrics.CreateMetricsService(ctx, cfg, servicePorts) - if err != nil { - logger.V(log.VWarn).Info("Could not create metrics Service", "error", err.Error()) - } - - // CreateServiceMonitors will automatically create the prometheus-operator ServiceMonitor resources - // necessary to configure Prometheus to scrape metrics from this operator. - services := []*v1.Service{service} - _, err = metrics.CreateServiceMonitors(cfg, namespace, services) - if err != nil { - logger.V(log.VWarn).Info("Could not create ServiceMonitor object", "error", err.Error()) - // If this operator is deployed to a cluster without the prometheus-operator running, it will return - // ErrServiceMonitorNotPresent, which can be used to safely skip ServiceMonitor creation. - if err == metrics.ErrServiceMonitorNotPresent { - logger.V(log.VWarn).Info("Install prometheus-operator in your cluster to create ServiceMonitor objects", "error", err.Error()) - } - } - - logger.Info("Starting the Cmd.") - - // start the Cmd - if err := mgr.Start(signals.SetupSignalHandler()); err != nil { - fatal(errors.Wrap(err, "failed to start cmd"), *debug) - } -} - -// serveCRMetrics gets the Operator/CustomResource GVKs and generates metrics based on those types. -// It serves those metrics on "http://metricsHost:operatorMetricsPort". -func serveCRMetrics(cfg *rest.Config) error { - // Below function returns filtered operator/CustomResource specific GVKs. - // For more control override the below GVK list with your own custom logic. - gvks, err := k8sutil.GetGVKsFromAddToScheme(apis.AddToScheme) - if err != nil { - return err - } - // We perform our custom GKV filtering on top of the one performed - // by operator-sdk code - filteredGVK := filterGKVsFromAddToScheme(gvks) - if err != nil { - return err - } - // Get the namespace the operator is currently deployed in. - operatorNs, err := k8sutil.GetOperatorNamespace() - if err != nil { - return err - } - // To generate metrics in other namespaces, add the values below. - ns := []string{operatorNs} - // Generate and serve custom resource specific metrics. - return kubemetrics.GenerateAndServeCRMetrics(cfg, ns, filteredGVK, metricsHost, operatorMetricsPort) -} - -func filterGKVsFromAddToScheme(gvks []schema.GroupVersionKind) []schema.GroupVersionKind { - // We use gkvFilters to filter from the existing GKVs defined in the used - // runtime.Schema for the operator. The reason for that is that - // kube-metrics tries to list all of the defined Kinds in the schemas - // that are passed, including Kinds that the operator doesn't use and - // thus the role used the operator doesn't have them set and we don't want - // to set as they are not used by the operator. - // For the fields that the filters have we have defined the value '*' to - // specify any will be a match (accepted) - matchAnyValue := "*" - gvkFilters := []schema.GroupVersionKind{ - // Kubernetes Resources - {Kind: "PersistentVolumeClaim", Version: matchAnyValue}, - {Kind: "ServiceAccount", Version: matchAnyValue}, - {Kind: "Secret", Version: matchAnyValue}, - {Kind: "Pod", Version: matchAnyValue}, - {Kind: "ConfigMap", Version: matchAnyValue}, - {Kind: "Service", Version: matchAnyValue}, - {Group: "apps", Kind: "Deployment", Version: matchAnyValue}, - // Openshift Resources - {Group: "route.openshift.io", Kind: "Route", Version: matchAnyValue}, - {Group: "image.openshift.io", Kind: "ImageStream", Version: matchAnyValue}, - // Custom Resources - {Group: "jenkins.io", Kind: "Jenkins", Version: matchAnyValue}, - {Group: "jenkins.io", Kind: "JenkinsImage", Version: matchAnyValue}, - } - - ownGVKs := []schema.GroupVersionKind{} - for _, gvk := range gvks { - for _, gvkFilter := range gvkFilters { - match := true - if gvkFilter.Kind == matchAnyValue && gvkFilter.Group == matchAnyValue && gvkFilter.Version == matchAnyValue { - logger.V(1).Info("gvkFilter should at least have one of its fields defined. Skipping...") - match = false - } else { - if gvkFilter.Kind != matchAnyValue && gvkFilter.Kind != gvk.Kind { - match = false - } - if gvkFilter.Group != matchAnyValue && gvkFilter.Group != gvk.Group { - match = false - } - if gvkFilter.Version != matchAnyValue && gvkFilter.Version != gvk.Version { - match = false - } - } - if match { - ownGVKs = append(ownGVKs, gvk) - } - } - } - - return ownGVKs -} - -func fatal(err error, debug bool) { - if debug { - logger.Error(nil, fmt.Sprintf("%+v", err)) - } else { - logger.Error(nil, fmt.Sprintf("%s", err)) - } - os.Exit(-1) -} diff --git a/config.base.env b/config.base.env index dda8893c..21c422f7 100644 --- a/config.base.env +++ b/config.base.env @@ -1,7 +1,7 @@ # Setup variables for the Makefile NAME=kubernetes-operator -OPERATOR_SDK_VERSION=0.17.0 -GO_VERSION=1.14.2 +OPERATOR_SDK_VERSION=1.3.0 +GO_VERSION=1.15.6 PKG=github.com/jenkinsci/kubernetes-operator DOCKER_ORGANIZATION=virtuslab DOCKER_REGISTRY=jenkins-operator diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml new file mode 100644 index 00000000..52d86618 --- /dev/null +++ b/config/certmanager/certificate.yaml @@ -0,0 +1,25 @@ +# The following manifests contain a self-signed issuer CR and a certificate CR. +# More document can be found at https://docs.cert-manager.io +# WARNING: Targets CertManager v1.0. Check https://cert-manager.io/docs/installation/upgrading/ for breaking changes. +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: selfsigned-issuer + namespace: system +spec: + selfSigned: {} +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml + namespace: system +spec: + # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize + dnsNames: + - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc + - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local + issuerRef: + kind: Issuer + name: selfsigned-issuer + secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize diff --git a/config/certmanager/kustomization.yaml b/config/certmanager/kustomization.yaml new file mode 100644 index 00000000..bebea5a5 --- /dev/null +++ b/config/certmanager/kustomization.yaml @@ -0,0 +1,5 @@ +resources: +- certificate.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/certmanager/kustomizeconfig.yaml b/config/certmanager/kustomizeconfig.yaml new file mode 100644 index 00000000..90d7c313 --- /dev/null +++ b/config/certmanager/kustomizeconfig.yaml @@ -0,0 +1,16 @@ +# This configuration is for teaching kustomize how to update name ref and var substitution +nameReference: +- kind: Issuer + group: cert-manager.io + fieldSpecs: + - kind: Certificate + group: cert-manager.io + path: spec/issuerRef/name + +varReference: +- kind: Certificate + group: cert-manager.io + path: spec/commonName +- kind: Certificate + group: cert-manager.io + path: spec/dnsNames diff --git a/config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml b/config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml new file mode 100644 index 00000000..237e7a9c --- /dev/null +++ b/config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml @@ -0,0 +1,3420 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: jenkins.jenkins.io.jenkins.io +spec: + group: jenkins.io.jenkins.io + names: + kind: Jenkins + listKind: JenkinsList + plural: jenkins + singular: jenkins + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: Jenkins is the Schema for the jenkins 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: JenkinsSpec defines the desired state of Jenkins + properties: + backup: + description: 'Backup defines configuration of Jenkins backup More + info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + properties: + action: + description: Action defines action which performs backup in backup + container sidecar + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + type: object + containerName: + description: ContainerName is the container name responsible for + backup operation + type: string + interval: + description: Interval tells how often make backup in seconds Defaults + to 30. + format: int64 + type: integer + makeBackupBeforePodDeletion: + description: MakeBackupBeforePodDeletion tells operator to make + backup before Jenkins master pod deletion + type: boolean + required: + - action + - containerName + - interval + - makeBackupBeforePodDeletion + type: object + configurationAsCode: + description: ConfigurationAsCode defines configuration of Jenkins + customization via Configuration as Code Jenkins plugin + properties: + configurations: + items: + description: ConfigMapRef is reference to Kubernetes ConfigMap. + properties: + name: + type: string + required: + - name + type: object + type: array + secret: + description: SecretRef is reference to Kubernetes secret. + properties: + name: + type: string + required: + - name + type: object + required: + - configurations + - secret + type: object + groovyScripts: + description: GroovyScripts defines configuration of Jenkins customization + via groovy scripts + properties: + configurations: + items: + description: ConfigMapRef is reference to Kubernetes ConfigMap. + properties: + name: + type: string + required: + - name + type: object + type: array + secret: + description: SecretRef is reference to Kubernetes secret. + properties: + name: + type: string + required: + - name + type: object + required: + - configurations + - secret + type: object + jenkinsAPISettings: + description: JenkinsAPISettings defines configuration used by the + operator to gain admin access to the Jenkins API + properties: + authorizationStrategy: + description: AuthorizationStrategy defines authorization strategy + of the operator for the Jenkins API + type: string + required: + - authorizationStrategy + type: object + master: + description: Master represents Jenkins master pod properties and Jenkins + plugins. Every single change here requires a pod restart. + 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 + basePlugins: + description: 'BasePlugins contains plugins required by operator + Defaults to : - name: kubernetes version: 1.15.7 - name: workflow-job + version: "2.39" - name: workflow-aggregator version: "2.6" - + name: git version: 3.10.0 - name: job-dsl version: "1.74" - + name: configuration-as-code version: "1.19" - name: kubernetes-credentials-provider + version: 0.12.1' + items: + description: Plugin defines Jenkins plugin. + properties: + downloadURL: + description: DownloadURL is the custom url from where plugin + has to be downloaded. + type: string + name: + description: Name is the name of Jenkins plugin + type: string + version: + description: Version is the version of Jenkins plugin + type: string + required: + - name + - version + type: object + type: array + containers: + description: 'List of containers belonging to the pod. Containers + cannot currently be added or removed. There must be at least + one container in a Pod. Defaults to: - image: jenkins/jenkins:lts imagePullPolicy: + Always livenessProbe: failureThreshold: 12 httpGet: path: + /login port: http scheme: HTTP initialDelaySeconds: + 80 periodSeconds: 10 successThreshold: 1 timeoutSeconds: + 5 name: jenkins-master readinessProbe: failureThreshold: + 3 httpGet: path: /login port: http scheme: + HTTP initialDelaySeconds: 30 periodSeconds: 10 successThreshold: + 1 timeoutSeconds: 1 resources: limits: cpu: + 1500m memory: 3Gi requests: cpu: "1" memory: + 600Mi' + items: + description: Container defines Kubernetes container attributes. + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the + input string will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). Escaped + references will never be expanded, regardless of whether + the variable exists or not. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not + provided. Variable references $(VAR_NAME) are expanded + using the container''s environment. If a variable cannot + be resolved, the reference in the input string will be + unchanged. The $(VAR_NAME) syntax can be escaped with + a double $$, ie: $$(VAR_NAME). Escaped references will + never be expanded, regardless of whether the variable + exists or not. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the + container. + items: + description: EnvVar represents an environment variable + present in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are + expanded using the previous defined environment + variables in the container and any service environment + variables. If a variable cannot be resolved, the + reference in the input string will be unchanged. + The $(VAR_NAME) syntax can be escaped with a double + $$, ie: $$(VAR_NAME). Escaped references will never + be expanded, regardless of whether the variable + exists or not. Defaults to "".' + type: string + valueFrom: + description: Source for the environment variable's + value. Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, `metadata.labels['''']`, + `metadata.annotations['''']`, spec.nodeName, + spec.serviceAccountName, status.hostIP, status.podIP, + status.podIPs.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the + pod's namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env + with a duplicate key will take precedence. + items: + description: EnvFromSource represents the source of a + set of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to + each key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images' + type: string + imagePullPolicy: + description: Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always. + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. + properties: + postStart: + description: 'PostStart is called immediately after + a container is created. If the handler fails, the + container is terminated and restarted according to + its restart policy. Other management of the container + blocks until the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a + container is terminated due to an API request or management + event such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The reason for + termination is passed to the handler. The Pod''s termination + grace period countdown begins before the PreStop hooked + is executed. Regardless of the outcome of the handler, + the container will eventually terminate within the + Pod''s termination grace period. Other management + of the container blocks until the hook completes or + until the termination grace period is reached. More + info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to + execute inside the container, the working + directory for the command is root ('/') in + the container's filesystem. The command is + simply exec'd, it is not run inside a shell, + so traditional shell instructions ('|', etc) + won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is + treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request + to perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom + header to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to + the host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: + implement a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect + to, defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: Periodic probe of container liveness. Container + will be restarted if the probe fails. + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: implement + a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + type: string + ports: + description: List of ports to expose from the container. + Exposing a port here gives the system additional information + about the network connections a container uses, but is + primarily informational. Not specifying a port here DOES + NOT prevent that port from being exposed. Any port which + is listening on the default "0.0.0.0" address inside a + container will be accessible from the network. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's + IP address. This must be a valid port number, 0 + < x < 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. + If specified, this must be a valid port number, + 0 < x < 65536. If HostNetwork is specified, this + must match ContainerPort. Most containers do not + need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a + pod must have a unique name. Name for the port that + can be referred to by services. + type: string + protocol: + default: TCP + description: Protocol for port. Must be UDP, TCP, + or SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + description: Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's + filesystem. The command is simply exec'd, it is + not run inside a shell, so traditional shell instructions + ('|', etc) won't work. To use a shell, you need + to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is + unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in + httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container + has started before liveness probes are initiated. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum + value is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: implement + a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is + 1. More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum amount of + compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum amount + of compute resources required. If Requests is omitted + for a container, it defaults to Limits if that is + explicitly specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: 'Security options the pod should run with. + More info: https://kubernetes.io/docs/concepts/policy/security-context/ + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent + process. This bool directly controls if the no_new_privs + flag will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only + root filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if + it does. If unset or false, no such validation will + be performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both + SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by this container. + If seccomp options are provided at both the pod & + container level, the container options override the + pod options. + properties: + localhostProfile: + description: localhostProfile indicates a profile + defined in a file on the node should be used. + The profile must be preconfigured on the node + to work. Must be a descending path, relative to + the kubelet's configured seccomp profile location. + Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp + profile will be applied. Valid options are: \n + Localhost - a profile defined in a file on the + node should be used. RuntimeDefault - the container + runtime default profile should be used. Unconfined + - no profile should be applied." + type: string + required: + - type + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name + of the GMSA credential spec to use. + type: string + runAsUserName: + description: The UserName in Windows to run the + entrypoint of the container process. Defaults + to the user specified in image metadata if unspecified. + May also be set in PodSecurityContext. If set + in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: string + type: object + type: object + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write + otherwise (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the + container's volume should be mounted. Defaults to + "" (volume's root). + type: string + subPathExpr: + description: Expanded path within the volume from + which the container's volume should be mounted. + Behaves similarly to SubPath but environment variable + references $(VAR_NAME) are expanded using the container's + environment. Defaults to "" (volume's root). SubPathExpr + and SubPath are mutually exclusive. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. + type: string + required: + - image + - imagePullPolicy + - name + - resources + type: object + type: array + disableCSRFProtection: + description: DisableCSRFProtection allows you to toggle CSRF Protection + on Jenkins + type: boolean + imagePullSecrets: + description: 'ImagePullSecrets is an optional list of references + to secrets in the same namespace to use for pulling any of the + images used by this PodSpec. If specified, these secrets will + be passed to individual puller implementations for them to use. + For example, in the case of docker, only DockerConfig type secrets + are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' + items: + description: LocalObjectReference contains enough information + to let you locate the referenced object inside the same namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + type: array + 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 + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for + the pod to fit on a node. Selector which must match a node''s + labels for the pod to be scheduled on that node. More info: + https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + plugins: + description: Plugins contains plugins required by user + items: + description: Plugin defines Jenkins plugin. + properties: + downloadURL: + description: DownloadURL is the custom url from where plugin + has to be downloaded. + type: string + name: + description: Name is the name of Jenkins plugin + type: string + version: + description: Version is the version of Jenkins plugin + type: string + required: + - name + - version + type: object + type: array + priorityClassName: + description: PriorityClassName for Jenkins master pod + type: string + securityContext: + description: 'SecurityContext that applies to all the containers + of the Jenkins Master. As per kubernetes specification, it can + be overridden for each container individually. Defaults to: + runAsUser: 1000 fsGroup: 1000' + properties: + fsGroup: + description: "A special supplemental group that applies to + all containers in a pod. Some volume types allow the Kubelet + to change the ownership of that volume to be owned by the + pod: \n 1. The owning GID will be the FSGroup 2. The setgid + bit is set (new files created in the volume will be owned + by FSGroup) 3. The permission bits are OR'd with rw-rw---- + \n If unset, the Kubelet will not modify the ownership and + permissions of any volume." + format: int64 + type: integer + fsGroupChangePolicy: + description: 'fsGroupChangePolicy defines behavior of changing + ownership and permission of the volume before being exposed + inside Pod. This field will only apply to volume types which + support fsGroup based ownership(and permissions). It will + have no effect on ephemeral volume types such as: secret, + configmaps and emptydir. Valid values are "OnRootMismatch" + and "Always". If not specified defaults to "Always".' + type: string + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set + in SecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail + to start the container if it does. If unset or false, no + such validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the + value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if + unspecified. May also be set in SecurityContext. If set + in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence + for that container. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + seccompProfile: + description: The seccomp options to use by the containers + in this pod. + properties: + localhostProfile: + description: localhostProfile indicates a profile defined + in a file on the node should be used. The profile must + be preconfigured on the node to work. Must be a descending + path, relative to the kubelet's configured seccomp profile + location. Must only be set if type is "Localhost". + type: string + type: + description: "type indicates which kind of seccomp profile + will be applied. Valid options are: \n Localhost - a + profile defined in a file on the node should be used. + RuntimeDefault - the container runtime default profile + should be used. Unconfined - no profile should be applied." + type: string + required: + - type + type: object + supplementalGroups: + description: A list of groups applied to the first process + run in each container, in addition to the container's primary + GID. If unspecified, no groups will be added to any container. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to all + containers. If unspecified, the options within a container's + SecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the + GMSA credential spec to use. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in + PodSecurityContext. If set in both SecurityContext and + PodSecurityContext, the value specified in SecurityContext + takes precedence. + type: string + type: object + type: object + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using + the matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. + Empty means match all taint effects. When specified, allowed + values are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to + the value. Valid operators are Exists and Equal. Defaults + to Equal. Exists is equivalent to wildcard for value, + so that a pod can tolerate all taints of a particular + category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of + time the toleration (which must be of effect NoExecute, + otherwise this field is ignored) tolerates the taint. + By default, it is not set, which means tolerate the taint + forever (do not evict). Zero and negative values will + be treated as 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + volumes: + description: 'List of volumes that can be mounted by containers + belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' + items: + description: Volume represents a named volume in a pod that + may be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk + resource that is attached to a kubelet''s host machine + and then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set the ReadOnly + property in VolumeMounts to "true". If omitted, the + default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource + in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read + Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob + disks per storage account Dedicated: single blob + disk per storage account Managed: azure managed data + disk (only in managed availability set). defaults + to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service + mount on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure + Storage Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection of + Ceph monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, rather + than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key + ring for User, default is /etc/ceph/user.secret More + info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the + authentication secret for User, default is empty. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user name, + default is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached + and mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing + parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume + in cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced ConfigMap will be + projected into the volume as a file whose name is + the key and content is the value. If specified, the + listed keys will be projected into the specified paths, + and unlisted keys will not be present. If a key is + specified which is not present in the ConfigMap, the + volume setup will error unless it is marked optional. + Paths must be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys + must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + ephemeral storage that is handled by certain external + CSI drivers (Beta feature). + properties: + driver: + description: Driver is the name of the CSI driver that + handles this volume. Consult with your admin for the + correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", + "ntfs". If not provided, the empty value is passed + to the associated CSI driver which will determine + the default filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to + the secret object containing sensitive information + to pass to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the + secret object contains more than one secret, all secret + references are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration for + the volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific + properties that are passed to the CSI driver. Consult + your driver's documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created + files by default. Must be a Optional: mode bits used + to set permissions on created files by default. Must + be an octal value between 0000 and 0777 or a decimal + value between 0 and 511. YAML accepts both octal and + decimal values, JSON requires decimal values for mode + bits. Defaults to 0644. Directories within the path + are not affected by this setting. This might be in + conflict with other options that affect the file mode, + like fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume + file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the + pod: only annotations, labels, name and namespace + are supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used to set + permissions on this file, must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. Must not + be absolute or contain the ''..'' path. Must + be utf-8 encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for + volumes, optional for env vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output format of + the exposed resources, defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory + that shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should back + this directory. The default is "" which means to use + the node''s default medium. Must be an empty string + (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + anyOf: + - type: integer + - type: string + description: 'Total amount of local storage required + for this EmptyDir volume. The size limit is also applicable + for memory medium. The maximum usage on memory medium + EmptyDir would be the minimum value between the SizeLimit + specified here and the sum of memory limits of all + containers in a pod. The default is nil which means + that the limit is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + type: object + ephemeral: + description: "Ephemeral represents a volume that is handled + by a cluster storage driver (Alpha feature). The volume's + lifecycle is tied to the pod that defines it - it will + be created before the pod starts, and deleted when the + pod is removed. \n Use this if: a) the volume is only + needed while the pod runs, b) features of normal volumes + like restoring from snapshot or capacity tracking are + needed, c) the storage driver is specified through a storage + class, and d) the storage driver supports dynamic volume + provisioning through a PersistentVolumeClaim (see EphemeralVolumeSource + for more information on the connection between this + volume type and PersistentVolumeClaim). \n Use PersistentVolumeClaim + or one of the vendor-specific APIs for volumes that persist + for longer than the lifecycle of an individual pod. \n + Use CSI for light-weight local ephemeral volumes if the + CSI driver is meant to be used that way - see the documentation + of the driver for more information. \n A pod can use both + types of ephemeral volumes and persistent volumes at the + same time." + properties: + readOnly: + description: Specifies a read-only configuration for + the volume. Defaults to false (read/write). + type: boolean + volumeClaimTemplate: + description: "Will be used to create a stand-alone PVC + to provision the volume. The pod in which this EphemeralVolumeSource + is embedded will be the owner of the PVC, i.e. the + PVC will be deleted together with the pod. The name + of the PVC will be `-` where + `` is the name from the `PodSpec.Volumes` + array entry. Pod validation will reject the pod if + the concatenated name is not valid for a PVC (for + example, too long). \n An existing PVC with that name + that is not owned by the pod will *not* be used for + the pod to avoid using an unrelated volume by mistake. + Starting the pod is then blocked until the unrelated + PVC is removed. If such a pre-created PVC is meant + to be used by the pod, the PVC has to updated with + an owner reference to the pod once the pod exists. + Normally this should not be necessary, but it may + be useful when manually reconstructing a broken cluster. + \n This field is read-only and no changes will be + made by Kubernetes to the PVC after it has been created. + \n Required, must not be nil." + properties: + metadata: + description: May contain labels and annotations + that will be copied into the PVC when creating + it. No other fields are allowed and will be rejected + during validation. + type: object + spec: + description: The specification for the PersistentVolumeClaim. + The entire content is copied unchanged into the + PVC that gets created from this template. The + same fields as in a PersistentVolumeClaim are + also valid here. + properties: + accessModes: + description: 'AccessModes contains the desired + access modes the volume should have. More + info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1' + items: + type: string + type: array + dataSource: + description: 'This field can be used to specify + either: * An existing VolumeSnapshot object + (snapshot.storage.k8s.io/VolumeSnapshot - + Beta) * An existing PVC (PersistentVolumeClaim) + * An existing custom resource/object that + implements data population (Alpha) In order + to use VolumeSnapshot object types, the appropriate + feature gate must be enabled (VolumeSnapshotDataSource + or AnyVolumeDataSource) If the provisioner + or an external controller can support the + specified data source, it will create a new + volume based on the contents of the specified + data source. If the specified data source + is not supported, the volume will not be created + and the failure will be reported as an event. + In the future, we plan to support more data + source types and the behavior of the provisioner + may change.' + properties: + apiGroup: + description: APIGroup is the group for the + resource being referenced. If APIGroup + is not specified, the specified Kind must + be in the core API group. For any other + third-party types, APIGroup is required. + type: string + kind: + description: Kind is the type of resource + being referenced + type: string + name: + description: Name is the name of resource + being referenced + type: string + required: + - kind + - name + type: object + resources: + description: 'Resources represents the minimum + resources the volume should have. More info: + https://kubernetes.io/docs/concepts/storage/persistent-volumes#resources' + properties: + limits: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Limits describes the maximum + amount of compute resources allowed. More + info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + anyOf: + - type: integer + - type: string + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + description: 'Requests describes the minimum + amount of compute resources required. + If Requests is omitted for a container, + it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined + value. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + selector: + description: A label query over volumes to consider + for binding. + properties: + matchExpressions: + description: matchExpressions is a list + of label selector requirements. The requirements + are ANDed. + items: + description: A label selector requirement + is a selector that contains values, + a key, and an operator that relates + the key and values. + properties: + key: + description: key is the label key + that the selector applies to. + type: string + operator: + description: operator represents a + key's relationship to a set of values. + Valid operators are In, NotIn, Exists + and DoesNotExist. + type: string + values: + description: values is an array of + string values. If the operator is + In or NotIn, the values array must + be non-empty. If the operator is + Exists or DoesNotExist, the values + array must be empty. This array + is replaced during a strategic merge + patch. + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + description: matchLabels is a map of {key,value} + pairs. A single {key,value} in the matchLabels + map is equivalent to an element of matchExpressions, + whose key field is "key", the operator + is "In", and the values array contains + only "value". The requirements are ANDed. + type: object + type: object + storageClassName: + description: 'Name of the StorageClass required + by the claim. More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#class-1' + type: string + volumeMode: + description: volumeMode defines what type of + volume is required by the claim. Value of + Filesystem is implied when not included in + claim spec. + type: string + volumeName: + description: VolumeName is the binding reference + to the PersistentVolume backing this claim. + type: string + type: object + required: + - spec + type: object + type: object + fc: + description: FC represents a Fibre Channel resource that + is attached to a kubelet's host machine and then exposed + to the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. TODO: how do we prevent errors in the + filesystem from compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs + and lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: Driver is the name of the driver to use + for this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". The default filesystem depends on FlexVolume + script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the + secret object containing sensitive information to + pass to the plugin scripts. This may be empty if no + secret object is specified. If the secret object contains + more than one secret, all secrets are passed to the + plugin scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata + -> name on the dataset for Flocker should be considered + as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then + exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify + the partition as "1". Similarly, the volume partition + for /dev/sda is "0" (or you can leave the property + empty). More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in GCE. + Used to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an + InitContainer that clones the repo using git, then mount + the EmptyDir into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain + or start with '..'. If '.' is supplied, the volume + directory will be the git repository. Otherwise, + if specified, the volume will contain the git repository + in the subdirectory with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on + the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name that + details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More + info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs + volume to be mounted with read-only permissions. Defaults + to false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file or + directory on the host machine that is directly exposed + to the container. This is generally used for system agents + or other privileged things that are allowed to see the + host machine. Most containers will NOT need this. More + info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host + directory mounts and who can/can not mount host directories + as read/write.' + properties: + path: + description: 'Path of the directory on the host. If + the path is a symlink, it will follow the link to + the real path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that + is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, new + iSCSI interface : will + be created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI + transport. Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal is + either an IP or ip_addr:port if the port is other + than default (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator + authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is either + an IP or ip_addr:port if the port is other than default + (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique + within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that + shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export + to be mounted with read-only permissions. Defaults + to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of + the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host + machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent + disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx volume + attached and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem type to + mount Must be a filesystem type supported by the host + operating system. Ex. "ext4", "xfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx + volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, configmaps, + and downward API + properties: + defaultMode: + description: Mode bits used to set permissions on created + files by default. Must be an octal value between 0000 + and 0777 or a decimal value between 0 and 511. YAML + accepts both octal and decimal values, JSON requires + decimal values for mode bits. Directories within the + path are not affected by this setting. This might + be in conflict with other options that affect the + file mode, like fsGroup, and the result can be other + mode bits set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + configMap: + description: information about the configMap data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + ConfigMap will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the ConfigMap, the volume setup will + error unless it is marked optional. Paths + must be relative and may not contain the + '..' path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap + or its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI + data to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits used + to set permissions on this file, must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the + relative path name of the file to + be created. Must not be absolute or + contain the ''..'' path. Must be utf-8 + encoded. The first item of the relative + path must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of + the container: only resources limits + and requests (limits.cpu, limits.memory, + requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env + vars' + type: string + divisor: + anyOf: + - type: integer + - type: string + description: Specifies the output + format of the exposed resources, + defaults to "1" + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + resource: + description: 'Required: resource + to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced + Secret will be projected into the volume + as a file whose name is the key and content + is the value. If specified, the listed keys + will be projected into the specified paths, + and unlisted keys will not be present. If + a key is specified which is not present + in the Secret, the volume setup will error + unless it is marked optional. Paths must + be relative and may not contain the '..' + path or start with '..'. + items: + description: Maps a string key to a path + within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used + to set permissions on this file. Must + be an octal value between 0000 and + 0777 or a decimal value between 0 + and 511. YAML accepts both octal and + decimal values, JSON requires decimal + values for mode bits. If not specified, + the volume defaultMode will be used. + This might be in conflict with other + options that affect the file mode, + like fsGroup, and the result can be + other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be + an absolute path. May not contain + the path element '..'. May not start + with the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or + its key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended audience + of the token. A recipient of a token must + identify itself with an identifier specified + in the audience of the token, and otherwise + should reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, + the kubelet volume plugin will proactively + rotate the service account token. The kubelet + will start trying to rotate the token if + the token is older than 80 percent of its + time to live or if the token is older than + 24 hours.Defaults to 1 hour and must be + at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative to + the mount point of the file to project the + token into. + type: string + required: + - path + type: object + type: object + type: array + required: + - sources + type: object + quobyte: + description: Quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default is + no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults + to false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string as + host:port pair (multiple entries are separated with + commas) which acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume + in the Backend Used with dynamically provisioned Quobyte + volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to + serivceaccount user + type: string + volume: + description: Volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device mount + on the host that shares a pod''s lifetime. More info: + https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that you + want to mount. Tip: Ensure that the filesystem type + is supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is rbd. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly + setting in VolumeMounts. Defaults to false. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. + Default is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'The rados user name. Default is admin. + More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain + for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for + ScaleIO user and other sensitive information. If this + is not provided, Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume + should be ThickProvisioned or ThinProvisioned. Default + is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with + the protection domain. + type: string + system: + description: The name of the storage system as configured + in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in + the ScaleIO system that is associated with this volume + source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits used to set permissions + on created files by default. Must be an octal value + between 0000 and 0777 or a decimal value between 0 + and 511. YAML accepts both octal and decimal values, + JSON requires decimal values for mode bits. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in + the Data field of the referenced Secret will be projected + into the volume as a file whose name is the key and + content is the value. If specified, the listed keys + will be projected into the specified paths, and unlisted + keys will not be present. If a key is specified which + is not present in the Secret, the volume setup will + error unless it is marked optional. Paths must be + relative and may not contain the '..' path or start + with '..'. + items: + description: Maps a string key to a path within a + volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits used to set + permissions on this file. Must be an octal value + between 0000 and 0777 or a decimal value between + 0 and 511. YAML accepts both octal and decimal + values, JSON requires decimal values for mode + bits. If not specified, the volume defaultMode + will be used. This might be in conflict with + other options that affect the file mode, like + fsGroup, and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the file to + map the key to. May not be an absolute path. + May not contain the path element '..'. May not + start with the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its keys + must be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace + to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for + obtaining the StorageOS API credentials. If not specified, + default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable name of + the StorageOS volume. Volume names are only unique + within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of + the volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows + the Kubernetes name scoping to be mirrored within + StorageOS for tighter integration. Set VolumeName + to any name to override the default behaviour. Set + to "default" if you are not using namespaces within + StorageOS. Namespaces that do not pre-exist within + StorageOS will be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if + unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) + profile ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) + profile name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - disableCSRFProtection + type: object + notifications: + description: Notifications defines list of a services which are used + to inform about Jenkins status Can be used to integrate chat services + like Slack, Microsoft Teams or Mailgun + items: + description: Notification is a service configuration used to send + notifications about Jenkins status. + properties: + level: + description: NotificationLevel defines the level of a Notification. + type: string + mailgun: + description: Mailgun is handler for Mailgun email service notification + channel. + properties: + apiKeySecretKeySelector: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + domain: + type: string + from: + type: string + recipient: + type: string + required: + - apiKeySecretKeySelector + - domain + - from + - recipient + type: object + name: + type: string + slack: + description: Slack is handler for Slack notification channel. + properties: + webHookURLSecretKeySelector: + description: The web hook URL to Slack App + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + required: + - webHookURLSecretKeySelector + type: object + smtp: + description: SMTP is handler for sending emails via this protocol. + properties: + from: + type: string + passwordSecretKeySelector: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + port: + type: integer + server: + type: string + tlsInsecureSkipVerify: + type: boolean + to: + type: string + usernameSecretKeySelector: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + required: + - from + - passwordSecretKeySelector + - port + - server + - to + - usernameSecretKeySelector + type: object + teams: + description: MicrosoftTeams is handler for Microsoft MicrosoftTeams + notification channel. + properties: + webHookURLSecretKeySelector: + description: The web hook URL to MicrosoftTeams App + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + required: + - webHookURLSecretKeySelector + type: object + verbose: + type: boolean + required: + - level + - name + - verbose + type: object + type: array + restore: + description: 'Backup defines configuration of Jenkins backup restore + More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + properties: + action: + description: Action defines action which performs restore backup + in restore container sidecar + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + type: object + containerName: + description: ContainerName is the container name responsible for + restore backup operation + type: string + getLatestAction: + description: GetLatestAction defines action which returns the + latest backup number. If there is no backup "-1" should be returned. + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit + status of 0 is treated as live/healthy and non-zero + is unhealthy. + items: + type: string + type: array + type: object + type: object + recoveryOnce: + description: RecoveryOnce if want to restore specific backup set + this field and then Jenkins will be restarted and desired backup + will be restored + format: int64 + type: integer + required: + - action + - containerName + type: object + roles: + description: Roles defines list of extra RBAC roles for the Jenkins + Master pod service account + items: + description: RoleRef contains information that points to the role + being used + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - apiGroup + - kind + - name + type: object + type: array + seedJobs: + description: 'SeedJobs defines list of Jenkins Seed Job configurations + More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines' + items: + description: 'SeedJob defines configuration for seed job More info: + https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines.' + properties: + additionalClasspath: + description: AdditionalClasspath is setting for Job DSL API + plugin to set Additional Classpath + type: string + bitbucketPushTrigger: + description: BitbucketPushTrigger is used for Bitbucket web + hooks + type: boolean + buildPeriodically: + description: BuildPeriodically is setting for scheduled trigger + type: string + credentialID: + description: CredentialID is the Kubernetes secret name which + stores repository access credentials + type: string + credentialType: + description: JenkinsCredentialType is the https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ + credential type + type: string + description: + description: Description is the description of the seed job + type: string + failOnMissingPlugin: + description: FailOnMissingPlugin is setting for Job DSL API + plugin that fails job if required plugin is missing + type: boolean + githubPushTrigger: + description: GitHubPushTrigger is used for GitHub web hooks + type: boolean + id: + description: ID is the unique seed job name + type: string + ignoreMissingFiles: + description: IgnoreMissingFiles is setting for Job DSL API plugin + to ignore files that miss + type: boolean + pollSCM: + description: PollSCM is setting for polling changes in SCM + type: string + repositoryBranch: + description: RepositoryBranch is the repository branch where + are seed job definitions + type: string + repositoryUrl: + description: RepositoryURL is the repository access URL. Can + be SSH or HTTPS. + type: string + targets: + description: Targets is the repository path where are seed job + definitions + type: string + unstableOnDeprecation: + description: UnstableOnDeprecation is setting for Job DSL API + plugin that sets build status as unstable if build using deprecated + features + type: boolean + type: object + type: array + service: + description: 'Service is Kubernetes service of Jenkins master HTTP + pod Defaults to : port: 8080 type: ClusterIP' + 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 + labels: + additionalProperties: + type: string + description: 'Route service traffic to pods with label keys and + values matching this selector. If empty or not present, the + service is assumed to have an external process managing its + endpoints, which Kubernetes will not modify. Only applies to + types ClusterIP, NodePort, and LoadBalancer. Ignored if type + is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/' + type: object + loadBalancerIP: + description: 'Only applies to Service Type: LoadBalancer LoadBalancer + will get created with the IP specified in this field. This feature + depends on whether the underlying cloud-provider supports specifying + the loadBalancerIP when a load balancer is created. This field + will be ignored if the cloud-provider does not support the feature.' + type: string + loadBalancerSourceRanges: + description: 'If specified and supported by the platform, this + will restrict traffic through the cloud-provider load-balancer + will be restricted to the specified client IPs. This field will + be ignored if the cloud-provider does not support the feature." + More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + items: + type: string + type: array + nodePort: + description: 'The port on each node on which this service is exposed + when type=NodePort or LoadBalancer. Usually assigned by the + system. If specified, it will be allocated to the service if + unused or else creation of the service will fail. Default is + to auto-allocate a port if the ServiceType of this Service requires + one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + format: int32 + type: integer + port: + description: 'The port that are exposed by this service. More + info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' + format: int32 + type: integer + type: + description: 'Type determines how the Service is exposed. Defaults + to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, + and LoadBalancer. "ExternalName" maps to the specified externalName. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if + that is not specified, by manual construction of an Endpoints + object. If clusterIP is "None", no virtual IP is allocated and + the endpoints are published as a set of endpoints rather than + a stable IP. "NodePort" builds on ClusterIP and allocates a + port on every node which routes to the clusterIP. "LoadBalancer" + builds on NodePort and creates an external load-balancer (if + supported in the current cloud) which routes to the clusterIP. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' + type: string + type: object + serviceAccount: + description: ServiceAccount defines Jenkins master service account + attributes + 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 + type: object + slaveService: + description: 'Service is Kubernetes service of Jenkins slave pods + Defaults to : port: 50000 type: ClusterIP' + 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 + labels: + additionalProperties: + type: string + description: 'Route service traffic to pods with label keys and + values matching this selector. If empty or not present, the + service is assumed to have an external process managing its + endpoints, which Kubernetes will not modify. Only applies to + types ClusterIP, NodePort, and LoadBalancer. Ignored if type + is ExternalName. More info: https://kubernetes.io/docs/concepts/services-networking/service/' + type: object + loadBalancerIP: + description: 'Only applies to Service Type: LoadBalancer LoadBalancer + will get created with the IP specified in this field. This feature + depends on whether the underlying cloud-provider supports specifying + the loadBalancerIP when a load balancer is created. This field + will be ignored if the cloud-provider does not support the feature.' + type: string + loadBalancerSourceRanges: + description: 'If specified and supported by the platform, this + will restrict traffic through the cloud-provider load-balancer + will be restricted to the specified client IPs. This field will + be ignored if the cloud-provider does not support the feature." + More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + items: + type: string + type: array + nodePort: + description: 'The port on each node on which this service is exposed + when type=NodePort or LoadBalancer. Usually assigned by the + system. If specified, it will be allocated to the service if + unused or else creation of the service will fail. Default is + to auto-allocate a port if the ServiceType of this Service requires + one. More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + format: int32 + type: integer + port: + description: 'The port that are exposed by this service. More + info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' + format: int32 + type: integer + type: + description: 'Type determines how the Service is exposed. Defaults + to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, + and LoadBalancer. "ExternalName" maps to the specified externalName. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if + that is not specified, by manual construction of an Endpoints + object. If clusterIP is "None", no virtual IP is allocated and + the endpoints are published as a set of endpoints rather than + a stable IP. "NodePort" builds on ClusterIP and allocates a + port on every node which routes to the clusterIP. "LoadBalancer" + builds on NodePort and creates an external load-balancer (if + supported in the current cloud) which routes to the clusterIP. + More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' + type: string + type: object + required: + - jenkinsAPISettings + - master + type: object + status: + description: JenkinsStatus defines the observed state of Jenkins + properties: + appliedGroovyScripts: + description: AppliedGroovyScripts is a list with all applied groovy + scripts in Jenkins by the operator + items: + description: AppliedGroovyScript is the applied groovy script in + Jenkins by the operator. + properties: + configurationType: + description: ConfigurationType is the name of the configuration + type(base-groovy, user-groovy, user-casc) + type: string + hash: + description: Hash is the hash of the groovy script and secrets + which it uses + type: string + name: + description: Name is the name of the groovy script + type: string + source: + description: Source is the name of source where is located groovy + script + type: string + required: + - configurationType + - hash + - name + - source + type: object + type: array + backupDoneBeforePodDeletion: + description: BackupDoneBeforePodDeletion tells if backup before pod + deletion has been made + type: boolean + baseConfigurationCompletedTime: + description: BaseConfigurationCompletedTime is a time when Jenkins + base configuration phase has been completed + format: date-time + type: string + createdSeedJobs: + description: CreatedSeedJobs contains list of seed job id already + created in Jenkins + items: + type: string + type: array + lastBackup: + description: LastBackup is the latest backup number + format: int64 + type: integer + operatorVersion: + description: OperatorVersion is the operator version which manages + this CR + type: string + pendingBackup: + description: PendingBackup is the pending backup number + format: int64 + type: integer + provisionStartTime: + description: ProvisionStartTime is a time when Jenkins master pod + has been created + format: date-time + type: string + restoredBackup: + description: RestoredBackup is the restored backup number after Jenkins + master pod restart + format: int64 + type: integer + userAndPasswordHash: + description: UserAndPasswordHash is a SHA256 hash made from user and + password + type: string + userConfigurationCompletedTime: + description: UserConfigurationCompletedTime is a time when Jenkins + user configuration phase has been completed + format: date-time + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml b/config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml new file mode 100644 index 00000000..70c0914a --- /dev/null +++ b/config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml @@ -0,0 +1,95 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.4.1 + creationTimestamp: null + name: jenkinsimages.jenkins.io.jenkins.io +spec: + group: jenkins.io.jenkins.io + names: + kind: JenkinsImage + listKind: JenkinsImageList + plural: jenkinsimages + singular: jenkinsimage + scope: Namespaced + versions: + - name: v1alpha2 + schema: + openAPIV3Schema: + description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: JenkinsImageSpec defines the desired state of JenkinsImage + properties: + image: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + plugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + required: + - image + - plugins + type: object + status: + description: JenkinsImageStatus defines the observed state of JenkinsImage + properties: + image: + type: string + installedPlugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + md5sum: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 00000000..d56a15ec --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,21 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/jenkins.io.jenkins.io_jenkins.yaml +# +kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_jenkins.yaml +# +kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- patches/cainjection_in_jenkins.yaml +# +kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/kustomizeconfig.yaml b/config/crd/kustomizeconfig.yaml new file mode 100644 index 00000000..ec5c150a --- /dev/null +++ b/config/crd/kustomizeconfig.yaml @@ -0,0 +1,19 @@ +# This file is for teaching kustomize how to substitute name and namespace reference in CRD +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/name + +namespace: +- kind: CustomResourceDefinition + version: v1 + group: apiextensions.k8s.io + path: spec/conversion/webhook/clientConfig/service/namespace + create: false + +varReference: +- path: metadata/annotations diff --git a/config/crd/patches/cainjection_in_jenkins.yaml b/config/crd/patches/cainjection_in_jenkins.yaml new file mode 100644 index 00000000..463fc3cb --- /dev/null +++ b/config/crd/patches/cainjection_in_jenkins.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: jenkins.jenkins.io.jenkins.io diff --git a/config/crd/patches/webhook_in_jenkins.yaml b/config/crd/patches/webhook_in_jenkins.yaml new file mode 100644 index 00000000..7b4a9a30 --- /dev/null +++ b/config/crd/patches/webhook_in_jenkins.yaml @@ -0,0 +1,14 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: jenkins.jenkins.io.jenkins.io +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml new file mode 100644 index 00000000..62d52def --- /dev/null +++ b/config/default/kustomization.yaml @@ -0,0 +1,74 @@ +# Adds namespace to all resources. +namespace: jenkins-operator-system + +# Value of this field is prepended to the +# names of all resources, e.g. a deployment named +# "wordpress" becomes "alices-wordpress". +# Note that it should also match with the prefix (text before '-') of the namespace +# field above. +namePrefix: jenkins-operator- + +# Labels to add to all resources and selectors. +#commonLabels: +# someName: someValue + +bases: +- ../crd +- ../rbac +- ../manager +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- ../webhook +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. +#- ../certmanager +# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. +#- ../prometheus + +patchesStrategicMerge: +# Protect the /metrics endpoint by putting it behind auth. +# If you want your controller-manager to expose the /metrics +# endpoint w/o any authn/z, please comment the following line. +- manager_auth_proxy_patch.yaml + +# Mount the controller config file for loading manager configurations +# through a ComponentConfig type +#- manager_config_patch.yaml + +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in +# crd/kustomization.yaml +#- manager_webhook_patch.yaml + +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. +# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. +# 'CERTMANAGER' needs to be enabled to use ca injection +#- webhookcainjection_patch.yaml + +# the following config is for teaching kustomize how to do var substitution +vars: +# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. +#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR +# objref: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +# fieldref: +# fieldpath: metadata.namespace +#- name: CERTIFICATE_NAME +# objref: +# kind: Certificate +# group: cert-manager.io +# version: v1 +# name: serving-cert # this name should match the one in certificate.yaml +#- name: SERVICE_NAMESPACE # namespace of the service +# objref: +# kind: Service +# version: v1 +# name: webhook-service +# fieldref: +# fieldpath: metadata.namespace +#- name: SERVICE_NAME +# objref: +# kind: Service +# version: v1 +# name: webhook-service diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml new file mode 100644 index 00000000..49b1f1ab --- /dev/null +++ b/config/default/manager_auth_proxy_patch.yaml @@ -0,0 +1,26 @@ +# This patch inject a sidecar container which is a HTTP proxy for the +# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: kube-rbac-proxy + image: gcr.io/kubebuilder/kube-rbac-proxy:v0.5.0 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=10" + ports: + - containerPort: 8443 + name: https + - name: manager + args: + - "--health-probe-bind-address=:8081" + - "--metrics-bind-address=127.0.0.1:8080" + - "--leader-elect" diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml new file mode 100644 index 00000000..6c400155 --- /dev/null +++ b/config/default/manager_config_patch.yaml @@ -0,0 +1,20 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + args: + - "--config=controller_manager_config.yaml" + volumeMounts: + - name: manager-config + mountPath: /controller_manager_config.yaml + subPath: controller_manager_config.yaml + volumes: + - name: manager-config + configMap: + name: manager-config diff --git a/config/manager/controller_manager_config.yaml b/config/manager/controller_manager_config.yaml new file mode 100644 index 00000000..e8205453 --- /dev/null +++ b/config/manager/controller_manager_config.yaml @@ -0,0 +1,11 @@ +apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 +kind: ControllerManagerConfig +health: + healthProbeBindAddress: :8081 +metrics: + bindAddress: 127.0.0.1:8080 +webhook: + port: 9443 +leaderElection: + leaderElect: true + resourceName: 9cf053ac.jenkins.io diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml new file mode 100644 index 00000000..5e793dd1 --- /dev/null +++ b/config/manager/kustomization.yaml @@ -0,0 +1,16 @@ +resources: +- manager.yaml + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: +- files: + - controller_manager_config.yaml + name: manager-config +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +images: +- name: controller + newName: controller + newTag: latest diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml new file mode 100644 index 00000000..4d62d526 --- /dev/null +++ b/config/manager/manager.yaml @@ -0,0 +1,60 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + control-plane: controller-manager + name: system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system + labels: + control-plane: controller-manager +spec: + selector: + matchLabels: + control-plane: controller-manager + replicas: 1 + template: + metadata: + labels: + control-plane: controller-manager + spec: + securityContext: + runAsUser: 65532 + containers: + - command: + - /manager + args: + - --leader-elect + image: controller:latest + name: manager + securityContext: + allowPrivilegeEscalation: false + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 100m + memory: 30Mi + requests: + cpu: 100m + memory: 20Mi + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + terminationGracePeriodSeconds: 10 diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml new file mode 100644 index 00000000..ed137168 --- /dev/null +++ b/config/prometheus/kustomization.yaml @@ -0,0 +1,2 @@ +resources: +- monitor.yaml diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml new file mode 100644 index 00000000..9b8047b7 --- /dev/null +++ b/config/prometheus/monitor.yaml @@ -0,0 +1,16 @@ + +# Prometheus Monitor Service (Metrics) +apiVersion: monitoring.coreos.com/v1 +kind: ServiceMonitor +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-monitor + namespace: system +spec: + endpoints: + - path: /metrics + port: https + selector: + matchLabels: + control-plane: controller-manager diff --git a/config/rbac/auth_proxy_client_clusterrole.yaml b/config/rbac/auth_proxy_client_clusterrole.yaml new file mode 100644 index 00000000..bd4af137 --- /dev/null +++ b/config/rbac/auth_proxy_client_clusterrole.yaml @@ -0,0 +1,7 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: metrics-reader +rules: +- nonResourceURLs: ["/metrics"] + verbs: ["get"] diff --git a/config/rbac/auth_proxy_role.yaml b/config/rbac/auth_proxy_role.yaml new file mode 100644 index 00000000..618f5e41 --- /dev/null +++ b/config/rbac/auth_proxy_role.yaml @@ -0,0 +1,13 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: proxy-role +rules: +- apiGroups: ["authentication.k8s.io"] + resources: + - tokenreviews + verbs: ["create"] +- apiGroups: ["authorization.k8s.io"] + resources: + - subjectaccessreviews + verbs: ["create"] diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml new file mode 100644 index 00000000..48ed1e4b --- /dev/null +++ b/config/rbac/auth_proxy_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: proxy-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: proxy-role +subjects: +- kind: ServiceAccount + name: default + namespace: system diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml new file mode 100644 index 00000000..6cf656be --- /dev/null +++ b/config/rbac/auth_proxy_service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + targetPort: https + selector: + control-plane: controller-manager diff --git a/config/rbac/jenkins_editor_role.yaml b/config/rbac/jenkins_editor_role.yaml new file mode 100644 index 00000000..1846db5a --- /dev/null +++ b/config/rbac/jenkins_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit jenkins. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: jenkins-editor-role +rules: +- apiGroups: + - jenkins.io.jenkins.io + resources: + - jenkins + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - jenkins.io.jenkins.io + resources: + - jenkins/status + verbs: + - get diff --git a/config/rbac/jenkins_viewer_role.yaml b/config/rbac/jenkins_viewer_role.yaml new file mode 100644 index 00000000..d65eb6f5 --- /dev/null +++ b/config/rbac/jenkins_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view jenkins. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: jenkins-viewer-role +rules: +- apiGroups: + - jenkins.io.jenkins.io + resources: + - jenkins + verbs: + - get + - list + - watch +- apiGroups: + - jenkins.io.jenkins.io + resources: + - jenkins/status + verbs: + - get diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml new file mode 100644 index 00000000..66c28338 --- /dev/null +++ b/config/rbac/kustomization.yaml @@ -0,0 +1,12 @@ +resources: +- role.yaml +- role_binding.yaml +- leader_election_role.yaml +- leader_election_role_binding.yaml +# Comment the following 4 lines if you want to disable +# the auth proxy (https://github.com/brancz/kube-rbac-proxy) +# which protects your /metrics endpoint. +- auth_proxy_service.yaml +- auth_proxy_role.yaml +- auth_proxy_role_binding.yaml +- auth_proxy_client_clusterrole.yaml diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml new file mode 100644 index 00000000..6334cc51 --- /dev/null +++ b/config/rbac/leader_election_role.yaml @@ -0,0 +1,27 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: +- apiGroups: + - "" + - coordination.k8s.io + resources: + - configmaps + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml new file mode 100644 index 00000000..eed16906 --- /dev/null +++ b/config/rbac/leader_election_role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: default + namespace: system diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 00000000..f6fd0865 --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,62 @@ + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - jenkins.io + resources: + - jenkins + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - jenkins.io + resources: + - jenkins/finalizers + verbs: + - update +- apiGroups: + - jenkins.io + resources: + - jenkins/status + verbs: + - get + - patch + - update +- apiGroups: + - v1 + resources: + - secrets + verbs: + - get + - list + - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml new file mode 100644 index 00000000..8f265870 --- /dev/null +++ b/config/rbac/role_binding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: default + namespace: system diff --git a/config/samples/jenkins.io_v1alpha2_jenkins.yaml b/config/samples/jenkins.io_v1alpha2_jenkins.yaml new file mode 100644 index 00000000..970a02b2 --- /dev/null +++ b/config/samples/jenkins.io_v1alpha2_jenkins.yaml @@ -0,0 +1,55 @@ +apiVersion: jenkins.io.jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: jenkins-example + namespace: default +spec: + configurationAsCode: + configurations: [] + secret: + name: "" + groovyScripts: + configurations: [] + secret: + name: "" + jenkinsAPISettings: + authorizationStrategy: createUser + master: + disableCSRFProtection: false + containers: + - name: jenkins-master + image: jenkins/jenkins:2.249.3-lts-alpine + imagePullPolicy: Always + livenessProbe: + failureThreshold: 12 + httpGet: + path: /login + port: http + scheme: HTTP + initialDelaySeconds: 100 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + readinessProbe: + failureThreshold: 5 + httpGet: + path: /login + port: http + scheme: HTTP + initialDelaySeconds: 60 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + limits: + cpu: 1500m + memory: 3Gi + requests: + cpu: "1" + memory: 500Mi + seedJobs: + - id: jenkins-operator + targets: "cicd/jobs/*.jenkins" + description: "Jenkins Operator repository" + repositoryBranch: master + repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml new file mode 100644 index 00000000..3eafa3e2 --- /dev/null +++ b/config/samples/kustomization.yaml @@ -0,0 +1,4 @@ +## Append samples you want in your CSV to this file as resources ## +resources: +- jenkins.io_v1alpha2_jenkins.yaml +# +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/scorecard/bases/config.yaml b/config/scorecard/bases/config.yaml new file mode 100644 index 00000000..c7704784 --- /dev/null +++ b/config/scorecard/bases/config.yaml @@ -0,0 +1,7 @@ +apiVersion: scorecard.operatorframework.io/v1alpha3 +kind: Configuration +metadata: + name: config +stages: +- parallel: true + tests: [] diff --git a/config/scorecard/kustomization.yaml b/config/scorecard/kustomization.yaml new file mode 100644 index 00000000..d73509ee --- /dev/null +++ b/config/scorecard/kustomization.yaml @@ -0,0 +1,16 @@ +resources: +- bases/config.yaml +patchesJson6902: +- path: patches/basic.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config +- path: patches/olm.config.yaml + target: + group: scorecard.operatorframework.io + version: v1alpha3 + kind: Configuration + name: config +# +kubebuilder:scaffold:patchesJson6902 diff --git a/config/scorecard/patches/basic.config.yaml b/config/scorecard/patches/basic.config.yaml new file mode 100644 index 00000000..d4347861 --- /dev/null +++ b/config/scorecard/patches/basic.config.yaml @@ -0,0 +1,10 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - basic-check-spec + image: quay.io/operator-framework/scorecard-test:v1.3.0 + labels: + suite: basic + test: basic-check-spec-test diff --git a/config/scorecard/patches/olm.config.yaml b/config/scorecard/patches/olm.config.yaml new file mode 100644 index 00000000..890bf057 --- /dev/null +++ b/config/scorecard/patches/olm.config.yaml @@ -0,0 +1,50 @@ +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-bundle-validation + image: quay.io/operator-framework/scorecard-test:v1.3.0 + labels: + suite: olm + test: olm-bundle-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-validation + image: quay.io/operator-framework/scorecard-test:v1.3.0 + labels: + suite: olm + test: olm-crds-have-validation-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-crds-have-resources + image: quay.io/operator-framework/scorecard-test:v1.3.0 + labels: + suite: olm + test: olm-crds-have-resources-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-spec-descriptors + image: quay.io/operator-framework/scorecard-test:v1.3.0 + labels: + suite: olm + test: olm-spec-descriptors-test +- op: add + path: /stages/0/tests/- + value: + entrypoint: + - scorecard-test + - olm-status-descriptors + image: quay.io/operator-framework/scorecard-test:v1.3.0 + labels: + suite: olm + test: olm-status-descriptors-test diff --git a/pkg/controller/jenkins/handler.go b/controllers/handler.go similarity index 77% rename from pkg/controller/jenkins/handler.go rename to controllers/handler.go index e03a909f..68414a36 100644 --- a/pkg/controller/jenkins/handler.go +++ b/controllers/handler.go @@ -1,10 +1,8 @@ -package jenkins +package controllers import ( "fmt" - "reflect" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" "github.com/jenkinsci/kubernetes-operator/pkg/log" @@ -12,7 +10,6 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -20,14 +17,14 @@ import ( type enqueueRequestForJenkins struct{} func (e *enqueueRequestForJenkins) Create(evt event.CreateEvent, q workqueue.RateLimitingInterface) { - if req := e.getOwnerReconcileRequests(evt.Meta); req != nil { + if req := e.getOwnerReconcileRequests(evt.Object); req != nil { q.Add(*req) } } func (e *enqueueRequestForJenkins) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { - req1 := e.getOwnerReconcileRequests(evt.MetaOld) - req2 := e.getOwnerReconcileRequests(evt.MetaNew) + req1 := e.getOwnerReconcileRequests(evt.ObjectOld) + req2 := e.getOwnerReconcileRequests(evt.ObjectNew) if req1 != nil || req2 != nil { jenkinsName := "unknown" @@ -39,7 +36,7 @@ func (e *enqueueRequestForJenkins) Update(evt event.UpdateEvent, q workqueue.Rat } log.Log.WithValues("cr", jenkinsName).Info( - fmt.Sprintf("%T/%s has been updated", evt.ObjectNew, evt.MetaNew.GetName())) + fmt.Sprintf("%T/%s has been updated", evt.ObjectNew, evt.ObjectNew.GetName())) } if req1 != nil { @@ -52,13 +49,13 @@ func (e *enqueueRequestForJenkins) Update(evt event.UpdateEvent, q workqueue.Rat } func (e *enqueueRequestForJenkins) Delete(evt event.DeleteEvent, q workqueue.RateLimitingInterface) { - if req := e.getOwnerReconcileRequests(evt.Meta); req != nil { + if req := e.getOwnerReconcileRequests(evt.Object); req != nil { q.Add(*req) } } func (e *enqueueRequestForJenkins) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { - if req := e.getOwnerReconcileRequests(evt.Meta); req != nil { + if req := e.getOwnerReconcileRequests(evt.Object); req != nil { q.Add(*req) } } @@ -75,7 +72,8 @@ func (e *enqueueRequestForJenkins) getOwnerReconcileRequests(object metav1.Objec return nil } -type jenkinsDecorator struct { +// FIXME unused +/*type jenkinsDecorator struct { handler handler.EventHandler } @@ -85,8 +83,8 @@ func (e *jenkinsDecorator) Create(evt event.CreateEvent, q workqueue.RateLimitin func (e *jenkinsDecorator) Update(evt event.UpdateEvent, q workqueue.RateLimitingInterface) { if !reflect.DeepEqual(evt.ObjectOld.(*v1alpha2.Jenkins).Spec, evt.ObjectNew.(*v1alpha2.Jenkins).Spec) { - log.Log.WithValues("cr", evt.MetaNew.GetName()).Info( - fmt.Sprintf("%T/%s has been updated", evt.ObjectNew, evt.MetaNew.GetName())) + log.Log.WithValues("cr", evt.ObjectNew.GetName()).Info( + fmt.Sprintf("%T/%s has been updated", evt.ObjectNew, evt.ObjectNew.GetName())) } e.handler.Update(evt, q) } @@ -97,4 +95,4 @@ func (e *jenkinsDecorator) Delete(evt event.DeleteEvent, q workqueue.RateLimitin func (e *jenkinsDecorator) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { e.handler.Generic(evt, q) -} +}*/ diff --git a/pkg/controller/jenkinsimage/jenkinsimage_controller.go b/controllers/jenkinsImage_controller.go similarity index 53% rename from pkg/controller/jenkinsimage/jenkinsimage_controller.go rename to controllers/jenkinsImage_controller.go index 92d81c25..5fb4d020 100644 --- a/pkg/controller/jenkinsimage/jenkinsimage_controller.go +++ b/controllers/jenkinsImage_controller.go @@ -1,91 +1,52 @@ -package jenkinsimage +package controllers import ( "context" - jenkinsv1alpha2 "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" + "github.com/jenkinsci/kubernetes-operator/pkg/log" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" - "sigs.k8s.io/controller-runtime/pkg/handler" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "sigs.k8s.io/controller-runtime/pkg/source" ) -var log = logf.Log.WithName("controller_jenkinsimage") +var logxx = log.Log -// Add creates a new JenkinsImage Controller and adds it to the Manager. The Manager will set fields on the Controller -// and Start it when the Manager is Started. -func Add(mgr manager.Manager) error { - r := &ReconcileJenkinsImage{client: mgr.GetClient(), scheme: mgr.GetScheme()} - return add(mgr, r) -} - -// add adds a new Controller to mgr with r as the reconcile.Reconciler -func add(mgr manager.Manager, r reconcile.Reconciler) error { - // Create a new controller - c, err := controller.New("jenkinsimage-controller", mgr, controller.Options{Reconciler: r}) - if err != nil { - return err - } - - // Watch for changes to primary resource JenkinsImage - eventHandlerForObject := &handler.EnqueueRequestForObject{} - src := &source.Kind{Type: &jenkinsv1alpha2.JenkinsImage{}} - err = c.Watch(src, eventHandlerForObject) - if err != nil { - return errors.WithStack(err) - } - - // Watch for changes to secondary resource Pods and requeue the owner JenkinsImage - eventHandlerForOwner := &handler.EnqueueRequestForOwner{ - IsController: true, - OwnerType: &jenkinsv1alpha2.JenkinsImage{}, - } - err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, eventHandlerForOwner) - if err != nil { - return errors.WithStack(err) - } - // Watch for changes to secondary ConfigMap and requeue the owner JenkinsImage - err = c.Watch(&source.Kind{Type: &corev1.ConfigMap{}}, eventHandlerForOwner) - if err != nil { - return errors.WithStack(err) - } - return nil -} - -// blank assignment to verify that ReconcileJenkinsImage implements reconcile.Reconciler -var _ reconcile.Reconciler = &ReconcileJenkinsImage{} - -// ReconcileJenkinsImage reconciles a JenkinsImage object -type ReconcileJenkinsImage struct { - // This client, initialized using mgr.Client() above, is a split client +// JenkinsImageReconciler reconciles a JenkinsImage object +type JenkinsImageReconciler struct { + // This Client, initialized using mgr.Client() above, is a split Client // that reads objects from the cache and writes to the apiserver - client client.Client - scheme *runtime.Scheme + Client client.Client + Scheme *runtime.Scheme +} + +// SetupWithManager sets up the controller with the Manager. +func (r *JenkinsImageReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha2.JenkinsImage{}). + Owns(&corev1.Pod{}). + Owns(&corev1.ConfigMap{}). + Complete(r) } // Reconcile reads that state of the cluster for a JenkinsImage object and makes changes based on the state read // and what is in the JenkinsImage.Spec // The Controller will requeue the Request to be processed again if the returned error is non-nil or // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. -func (r *ReconcileJenkinsImage) Reconcile(request reconcile.Request) (reconcile.Result, error) { - reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) +func (r *JenkinsImageReconciler) Reconcile(_ context.Context, request ctrl.Request) (ctrl.Result, error) { + reqLogger := logxx.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) reqLogger.Info("Reconciling JenkinsImage") // Fetch the JenkinsImage instance - instance := &jenkinsv1alpha2.JenkinsImage{} - err := r.client.Get(context.TODO(), request.NamespacedName, instance) + instance := &v1alpha2.JenkinsImage{} + err := r.Client.Get(context.TODO(), request.NamespacedName, instance) if err != nil { if apierrors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. @@ -100,16 +61,16 @@ func (r *ReconcileJenkinsImage) Reconcile(request reconcile.Request) (reconcile. // Define a new ConfigMap containing the Dockerfile used to build the image dockerfile := resources.NewDockerfileConfigMap(instance) // Set JenkinsImage instance as the owner and controller - if err := controllerutil.SetControllerReference(instance, dockerfile, r.scheme); err != nil { + if err := controllerutil.SetControllerReference(instance, dockerfile, r.Scheme); err != nil { return reconcile.Result{}, err } // Check if this ConfigMap already exists foundConfigMap := &corev1.ConfigMap{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: dockerfile.Name, Namespace: dockerfile.Namespace}, foundConfigMap) + err = r.Client.Get(context.TODO(), types.NamespacedName{Name: dockerfile.Name, Namespace: dockerfile.Namespace}, foundConfigMap) if err != nil && apierrors.IsNotFound(err) { reqLogger.Info("Creating a new ConfigMap", "ConfigMap.Namespace", dockerfile.Namespace, "ConfigMap.Name", dockerfile.Name) - err = r.client.Create(context.TODO(), dockerfile) + err = r.Client.Create(context.TODO(), dockerfile) if err != nil { return reconcile.Result{}, err } @@ -124,16 +85,16 @@ func (r *ReconcileJenkinsImage) Reconcile(request reconcile.Request) (reconcile. // Define a new Pod object pod := resources.NewBuilderPod(instance) // Set JenkinsImage instance as the owner and controller - if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil { + if err := controllerutil.SetControllerReference(instance, pod, r.Scheme); err != nil { return reconcile.Result{}, err } // Check if this Pod already exists foundPod := &corev1.Pod{} - err = r.client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, foundPod) + err = r.Client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, foundPod) if err != nil && apierrors.IsNotFound(err) { reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name) - err = r.client.Create(context.TODO(), pod) + err = r.Client.Create(context.TODO(), pod) if err != nil { return reconcile.Result{}, err } diff --git a/pkg/controller/jenkins/jenkins_controller.go b/controllers/jenkins_controller.go similarity index 77% rename from pkg/controller/jenkins/jenkins_controller.go rename to controllers/jenkins_controller.go index 5be62cde..fc7526e5 100644 --- a/pkg/controller/jenkins/jenkins_controller.go +++ b/controllers/jenkins_controller.go @@ -1,4 +1,20 @@ -package jenkins +/* +Copyright 2021. + +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" @@ -7,8 +23,9 @@ import ( "reflect" "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" + "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/user" @@ -17,15 +34,16 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" "github.com/jenkinsci/kubernetes-operator/pkg/plugins" + "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/controller" - "sigs.k8s.io/controller-runtime/pkg/handler" - "sigs.k8s.io/controller-runtime/pkg/manager" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" ) @@ -37,7 +55,6 @@ type reconcileError struct { const ( APIVersion = "core/v1" - PodKind = "Pod" SecretKind = "Secret" ConfigMapKind = "ConfigMap" containerProbeURI = "login" @@ -46,66 +63,57 @@ const ( var reconcileErrors = map[string]reconcileError{} var logx = log.Log -var _ reconcile.Reconciler = &ReconcileJenkins{} -// Add creates a newReconcilierConfiguration Jenkins Controller and adds it to the Manager. The Manager will set fields on the Controller -// and Start it when the Manager is Started. -func Add(mgr manager.Manager, jenkinsAPIConnectionSettings jenkinsclient.JenkinsAPIConnectionSettings, kubernetesClusterDomain string, clientSet kubernetes.Clientset, config rest.Config, notificationEvents *chan event.Event) error { - reconciler := newReconciler(mgr, jenkinsAPIConnectionSettings, kubernetesClusterDomain, clientSet, config, notificationEvents) - return add(mgr, reconciler) +// JenkinsReconciler reconciles a Jenkins object +type JenkinsReconciler struct { + Client client.Client + Scheme *runtime.Scheme + JenkinsAPIConnectionSettings jenkinsclient.JenkinsAPIConnectionSettings + ClientSet kubernetes.Clientset + Config rest.Config + NotificationEvents *chan event.Event + KubernetesClusterDomain string } -// add adds a newReconcilierConfiguration Controller to mgr with r as the reconcile.Reconciler. -func add(mgr manager.Manager, r reconcile.Reconciler) error { - // Create a newReconcilierConfiguration controller - c, err := controller.New("jenkins-controller", mgr, controller.Options{Reconciler: r}) - if err != nil { - return errors.WithStack(err) - } - - // Watch for changes to primary resource Jenkins - decorator := jenkinsDecorator{handler: &handler.EnqueueRequestForObject{}} - err = c.Watch(&source.Kind{Type: &v1alpha2.Jenkins{}}, &decorator) - if err != nil { - return errors.WithStack(err) - } - - // Watch for changes to secondary resource Pods and requeue the owner Jenkins - - podResource := &source.Kind{Type: &corev1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: APIVersion, Kind: PodKind}}} - err = c.Watch(podResource, &handler.EnqueueRequestForOwner{ - IsController: true, - OwnerType: &v1alpha2.Jenkins{}, - }) - if err != nil { - return errors.WithStack(err) - } - - secretResource := &source.Kind{Type: &corev1.Secret{TypeMeta: metav1.TypeMeta{APIVersion: APIVersion, Kind: SecretKind}}} - err = c.Watch(secretResource, &handler.EnqueueRequestForOwner{ - IsController: true, - OwnerType: &v1alpha2.Jenkins{}, - }) - if err != nil { - return errors.WithStack(err) - } - +// SetupWithManager sets up the controller with the Manager. +func (r *JenkinsReconciler) SetupWithManager(mgr ctrl.Manager) error { jenkinsHandler := &enqueueRequestForJenkins{} - err = c.Watch(secretResource, jenkinsHandler) - if err != nil { - return errors.WithStack(err) - } - configMapResource := &source.Kind{Type: &corev1.ConfigMap{TypeMeta: metav1.TypeMeta{APIVersion: APIVersion, Kind: ConfigMapKind}}} - err = c.Watch(configMapResource, jenkinsHandler) - if err != nil { - return errors.WithStack(err) - } - return nil + secretResource := &source.Kind{Type: &corev1.Secret{TypeMeta: metav1.TypeMeta{APIVersion: APIVersion, Kind: SecretKind}}} + return ctrl.NewControllerManagedBy(mgr). + For(&v1alpha2.Jenkins{}). + Owns(&corev1.Pod{}). + Owns(&corev1.Secret{}). + Owns(&corev1.ConfigMap{}). + Watches(secretResource, jenkinsHandler). + Watches(configMapResource, jenkinsHandler). + Complete(r) } -// Reconcile it's a main reconciliation loop which maintain desired state based on Jenkins.Spec. -func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Result, error) { +func (r *JenkinsReconciler) newJenkinsReconcilier(jenkins *v1alpha2.Jenkins) configuration.Configuration { + config := configuration.Configuration{ + Client: r.Client, + ClientSet: r.ClientSet, + Notifications: r.NotificationEvents, + Jenkins: jenkins, + Scheme: r.Scheme, + Config: &r.Config, + JenkinsAPIConnectionSettings: r.JenkinsAPIConnectionSettings, + KubernetesClusterDomain: r.KubernetesClusterDomain, + } + return config +} + +// +kubebuilder:rbac:groups=jenkins.io,resources=jenkins,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=jenkins.io,resources=jenkins/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=jenkins.io,resources=jenkins/finalizers,verbs=update +// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=core,resources=configmaps,verbs=get;list;watch +// +kubebuilder:rbac:groups=v1,resources=secrets,verbs=get;list;watch + +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.7.0/pkg/reconcile +func (r *JenkinsReconciler) Reconcile(_ context.Context, request ctrl.Request) (ctrl.Result, error) { reconcileFailLimit := uint64(10) logger := logx.WithValues("cr", request.Name) logger.V(log.VDebug).Info("Reconciling Jenkins") @@ -131,18 +139,18 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul reconcileErrors[request.Name] = lastErrors if lastErrors.counter >= reconcileFailLimit { if log.Debug { - logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed %d times with the same error, giving up: %+v", reconcileFailLimit, err)) + logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed %d times with the same errors, giving up: %+v", reconcileFailLimit, err)) } else { - logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed %d times with the same error, giving up: %s", reconcileFailLimit, err)) + logger.V(log.VWarn).Info(fmt.Sprintf("Reconcile loop failed %d times with the same errors, giving up: %s", reconcileFailLimit, err)) } - *r.notificationEvents <- event.Event{ + *r.NotificationEvents <- event.Event{ Jenkins: *jenkins, Phase: event.PhaseBase, Level: v1alpha2.NotificationLevelWarning, Reason: reason.NewReconcileLoopFailed( reason.OperatorSource, - []string{fmt.Sprintf("Reconcile loop failed %d times with the same error, giving up: %s", reconcileFailLimit, err)}, + []string{fmt.Sprintf("Reconcile loop failed %d times with the same errors, giving up: %s", reconcileFailLimit, err)}, ), } return reconcile.Result{Requeue: false}, nil @@ -155,7 +163,7 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul } if groovyErr, ok := err.(*jenkinsclient.GroovyScriptExecutionFailed); ok { - *r.notificationEvents <- event.Event{ + *r.NotificationEvents <- event.Event{ Jenkins: *jenkins, Phase: event.PhaseBase, Level: v1alpha2.NotificationLevelWarning, @@ -175,12 +183,12 @@ func (r *ReconcileJenkins) Reconcile(request reconcile.Request) (reconcile.Resul return result, nil } -func (r *ReconcileJenkins) reconcile(request reconcile.Request) (reconcile.Result, *v1alpha2.Jenkins, error) { +func (r *JenkinsReconciler) reconcile(request reconcile.Request) (reconcile.Result, *v1alpha2.Jenkins, error) { logger := logx.WithValues("cr", request.Name) // Fetch the Jenkins instance jenkins := &v1alpha2.Jenkins{} var err error - err = r.client.Get(context.TODO(), request.NamespacedName, jenkins) + err = r.Client.Get(context.TODO(), request.NamespacedName, jenkins) if err != nil { if apierrors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. @@ -208,9 +216,9 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request) (reconcile.Resul return reconcile.Result{Requeue: true}, jenkins, nil } - config := r.newReconcilierConfiguration(jenkins) + config := r.newJenkinsReconcilier(jenkins) // Reconcile base configuration - baseConfiguration := base.New(config, r.jenkinsAPIConnectionSettings) + baseConfiguration := base.New(config, r.JenkinsAPIConnectionSettings) var baseMessages []string baseMessages, err = baseConfiguration.Validate(jenkins) @@ -219,7 +227,7 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request) (reconcile.Resul } if len(baseMessages) > 0 { message := "Validation of base configuration failed, please correct Jenkins CR." - *r.notificationEvents <- event.Event{ + *r.NotificationEvents <- event.Event{ Jenkins: *jenkins, Phase: event.PhaseBase, Level: v1alpha2.NotificationLevelWarning, @@ -248,14 +256,14 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request) (reconcile.Resul if jenkins.Status.BaseConfigurationCompletedTime == nil { now := metav1.Now() jenkins.Status.BaseConfigurationCompletedTime = &now - err = r.client.Update(context.TODO(), jenkins) + err = r.Client.Status().Update(context.TODO(), jenkins) if err != nil { return reconcile.Result{}, jenkins, errors.WithStack(err) } message := fmt.Sprintf("Base configuration phase is complete, took %s", jenkins.Status.BaseConfigurationCompletedTime.Sub(jenkins.Status.ProvisionStartTime.Time)) - *r.notificationEvents <- event.Event{ + *r.NotificationEvents <- event.Event{ Jenkins: *jenkins, Phase: event.PhaseBase, Level: v1alpha2.NotificationLevelInfo, @@ -274,7 +282,7 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request) (reconcile.Resul } if len(messages) > 0 { message := "Validation of user configuration failed, please correct Jenkins CR" - *r.notificationEvents <- event.Event{ + *r.NotificationEvents <- event.Event{ Jenkins: *jenkins, Phase: event.PhaseUser, Level: v1alpha2.NotificationLevelWarning, @@ -309,13 +317,13 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request) (reconcile.Resul if jenkins.Status.UserConfigurationCompletedTime == nil { now := metav1.Now() jenkins.Status.UserConfigurationCompletedTime = &now - err = r.client.Update(context.TODO(), jenkins) + err = r.Client.Status().Update(context.TODO(), jenkins) if err != nil { return reconcile.Result{}, jenkins, errors.WithStack(err) } message := fmt.Sprintf("User configuration phase is complete, took %s", jenkins.Status.UserConfigurationCompletedTime.Sub(jenkins.Status.ProvisionStartTime.Time)) - *r.notificationEvents <- event.Event{ + *r.NotificationEvents <- event.Event{ Jenkins: *jenkins, Phase: event.PhaseUser, Level: v1alpha2.NotificationLevelInfo, @@ -326,7 +334,7 @@ func (r *ReconcileJenkins) reconcile(request reconcile.Request) (reconcile.Resul return reconcile.Result{}, jenkins, nil } -func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins) (requeue bool, err error) { +func (r *JenkinsReconciler) setDefaults(jenkins *v1alpha2.Jenkins) (requeue bool, err error) { changed := false logger := logx.WithValues("cr", jenkins.Name) @@ -390,7 +398,7 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins) (requeue bool, logger.Info("Setting default Jenkins master service") changed = true var serviceType = corev1.ServiceTypeClusterIP - if r.jenkinsAPIConnectionSettings.UseNodePort { + if r.JenkinsAPIConnectionSettings.UseNodePort { serviceType = corev1.ServiceTypeNodePort } jenkins.Spec.Service = v1alpha2.Service{ @@ -441,7 +449,7 @@ func (r *ReconcileJenkins) setDefaults(jenkins *v1alpha2.Jenkins) (requeue bool, } if changed { - return changed, errors.WithStack(r.client.Update(context.TODO(), jenkins)) + return changed, errors.WithStack(r.Client.Update(context.TODO(), jenkins)) } return changed, nil } @@ -455,7 +463,7 @@ func isJavaOpsVariableNotSet(container v1alpha2.Container) bool { return true } -func (r *ReconcileJenkins) setDefaultsForContainer(jenkins *v1alpha2.Jenkins, containerName string, containerIndex int) bool { +func (r *JenkinsReconciler) setDefaultsForContainer(jenkins *v1alpha2.Jenkins, containerName string, containerIndex int) bool { changed := false logger := logx.WithValues("cr", jenkins.Name, "container", containerName) @@ -483,17 +491,6 @@ func basePlugins() (result []v1alpha2.Plugin) { return } -func (r *ReconcileJenkins) handleDeprecatedData(jenkins *v1alpha2.Jenkins) (requeue bool, err error) { - changed := false - logger := logx.WithValues("cr", jenkins.Name) - if len(jenkins.Spec.Master.AnnotationsDeprecated) > 0 { - changed = true - jenkins.Spec.Master.Annotations = jenkins.Spec.Master.AnnotationsDeprecated - jenkins.Spec.Master.AnnotationsDeprecated = map[string]string{} - logger.V(log.VWarn).Info("spec.master.masterAnnotations is deprecated, the annotations have been moved to spec.master.annotations") - } - if changed { - return changed, errors.WithStack(r.client.Update(context.TODO(), jenkins)) - } - return changed, nil +func (r *JenkinsReconciler) handleDeprecatedData(_ *v1alpha2.Jenkins) (requeue bool, err error) { + return false, nil } diff --git a/go.mod b/go.mod index cb297196..4fa84ca9 100644 --- a/go.mod +++ b/go.mod @@ -1,75 +1,27 @@ module github.com/jenkinsci/kubernetes-operator -go 1.13 +go 1.15 require ( - github.com/bndr/gojenkins v0.0.0-20181125150310-de43c03cf849 + github.com/bndr/gojenkins v1.0.1 github.com/docker/distribution v2.7.1+incompatible - github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2 // indirect - github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 // indirect - github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e // indirect github.com/emersion/go-smtp v0.11.2 - github.com/go-logr/logr v0.1.0 - github.com/go-logr/zapr v0.1.1 - github.com/go-openapi/spec v0.19.4 - github.com/golang/mock v1.3.1 - github.com/golangci/golangci-lint v1.26.0 // indirect - github.com/mailgun/mailgun-go/v3 v3.6.0 - github.com/openshift/api v3.9.1-0.20190924102528-32369d4db2ad+incompatible - github.com/operator-framework/operator-sdk v0.17.0 + github.com/go-logr/logr v0.3.0 + github.com/go-logr/zapr v0.2.0 + github.com/golang/mock v1.4.1 + github.com/mailgun/mailgun-go/v3 v3.6.4 + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/openshift/api v3.9.0+incompatible github.com/pkg/errors v0.9.1 github.com/robfig/cron v1.2.0 - github.com/spf13/pflag v1.0.5 - github.com/stretchr/testify v1.5.1 - go.uber.org/zap v1.14.1 - golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect - golang.org/x/net v0.0.0-20200226121028-0de0cce0169b - golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 // indirect + github.com/stretchr/testify v1.6.1 + go.uber.org/zap v1.15.0 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df - k8s.io/api v0.17.4 - k8s.io/apimachinery v0.17.4 - k8s.io/cli-runtime v0.17.4 - k8s.io/client-go v12.0.0+incompatible - k8s.io/code-generator v0.17.4 - k8s.io/gengo v0.0.0-20191010091904-7fa3014cb28f - k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a - k8s.io/utils v0.0.0-20190801114015-581e00157fb1 - sigs.k8s.io/controller-runtime v0.5.2 - sigs.k8s.io/controller-tools v0.2.8 -) - -// Pinned to kubernetes-1.16.2 -replace ( - github.com/Azure/go-autorest => github.com/Azure/go-autorest v12.2.0+incompatible - k8s.io/api => k8s.io/api v0.0.0-20191016110408-35e52d86657a - k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65 - k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8 - k8s.io/apiserver => k8s.io/apiserver v0.0.0-20191016112112-5190913f932d - k8s.io/cli-runtime => k8s.io/cli-runtime v0.0.0-20191016114015-74ad18325ed5 - k8s.io/client-go => k8s.io/client-go v0.0.0-20191016111102-bec269661e48 - k8s.io/cloud-provider => k8s.io/cloud-provider v0.0.0-20191016115326-20453efc2458 - k8s.io/cluster-bootstrap => k8s.io/cluster-bootstrap v0.0.0-20191016115129-c07a134afb42 - k8s.io/component-base => k8s.io/component-base v0.0.0-20191016111319-039242c015a9 - k8s.io/cri-api => k8s.io/cri-api v0.0.0-20190828162817-608eb1dad4ac - k8s.io/csi-translation-lib => k8s.io/csi-translation-lib v0.0.0-20191016115521-756ffa5af0bd - k8s.io/kube-aggregator => k8s.io/kube-aggregator v0.0.0-20191016112429-9587704a8ad4 - k8s.io/kube-controller-manager => k8s.io/kube-controller-manager v0.0.0-20191016114939-2b2b218dc1df - k8s.io/kube-proxy => k8s.io/kube-proxy v0.0.0-20191016114407-2e83b6f20229 - k8s.io/kube-scheduler => k8s.io/kube-scheduler v0.0.0-20191016114748-65049c67a58b - k8s.io/kubectl => k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51 - k8s.io/kubelet => k8s.io/kubelet v0.0.0-20191016114556-7841ed97f1b2 - k8s.io/legacy-cloud-providers => k8s.io/legacy-cloud-providers v0.0.0-20191016115753-cf0698c3a16b - k8s.io/metrics => k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e - k8s.io/sample-apiserver => k8s.io/sample-apiserver v0.0.0-20191016112829-06bb3c9d77c9 -) - -replace ( - github.com/coreos/prometheus-operator => github.com/coreos/prometheus-operator v0.35.1 - github.com/docker/docker => github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309 - github.com/operator-framework/operator-sdk => github.com/operator-framework/operator-sdk v0.17.0 - k8s.io/code-generator => k8s.io/code-generator v0.0.0-20181117043124-c2090bec4d9b - k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20180711000925-0cf8f7e6ed1d - sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.4.0 - sigs.k8s.io/controller-tools => sigs.k8s.io/controller-tools v0.1.11-0.20190411181648-9d55346c2bde + k8s.io/api v0.20.2 + k8s.io/apimachinery v0.20.2 + k8s.io/cli-runtime v0.20.2 // indirect + k8s.io/client-go v0.20.2 + k8s.io/utils v0.0.0-20201110183641-67b214c5f920 + sigs.k8s.io/controller-runtime v0.7.0 ) diff --git a/go.sum b/go.sum index 3c47e83a..3d7039d2 100644 --- a/go.sum +++ b/go.sum @@ -1,170 +1,190 @@ bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1:Ulb78X89vxKYgdL24HMTiXYHlyHEvruOj1ZPlqeNEZM= -bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= -cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.51.0 h1:PvKAVQWCtlGUSlZkGW3QLelKaWq7KYv/MW1EboG8bfM= +cloud.google.com/go v0.51.0/go.mod h1:hWtGJ6gnXH+KgDv+V0zFGDvpi07n3z8ZNj3T1RW0Gcw= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0 h1:3ithwDMr7/3vpAMXiH+ZQnYbuIsh+OPhUPMFC9enmn0= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/azure-sdk-for-go v32.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v12.2.0+incompatible h1:2Fxszbg492oAJrcvJlgyVaTqnQYRkxmEK6VPCLLVpBI= -github.com/Azure/go-autorest v12.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.9.0 h1:MRvx8gncNaXJqOoLmhNjUAKh33JJF8LyxPhomEtOsjs= +github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= +github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= +github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest/adal v0.5.0 h1:q2gDruN08/guU9vAjuPWff0+QIrpH6ediguzdAzXAUU= +github.com/Azure/go-autorest/autorest v0.9.6 h1:5YWtOnckcudzIw8lPPBcWOnmIFWMtHci1ZWAZulMSx0= +github.com/Azure/go-autorest/autorest v0.9.6/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= +github.com/Azure/go-autorest/autorest v0.11.1 h1:eVvIXUKiTgv++6YnWb42DUA1YL7qDugnKP0HljexdnQ= +github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= -github.com/Azure/go-autorest/autorest/date v0.1.0 h1:YGrhWfrgtFs84+h0o46rJrlmsZtyZRg470CqAXTZaGM= +github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= +github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= +github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= +github.com/Azure/go-autorest/autorest/adal v0.9.5 h1:Y3bBUV4rTuxenJJs41HU3qmqsb+auo+a3Lz+PlJPpL0= +github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= +github.com/Azure/go-autorest/autorest/date v0.2.0 h1:yW+Zlqf26583pE43KhfnhFcdmSWlm5Ew6bxipnr/tbM= +github.com/Azure/go-autorest/autorest/date v0.2.0/go.mod h1:vcORJHLJEh643/Ioh9+vPmf1Ij9AEBM5FuBIXLmIy0g= +github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= +github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.1.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= github.com/Azure/go-autorest/autorest/mocks v0.2.0/go.mod h1:OTyCOPRA2IgIlWxVYxBee2F5Gr4kF2zd2J5cFRaIDN0= -github.com/Azure/go-autorest/autorest/to v0.2.0/go.mod h1:GunWKJp1AEqgMaGLV+iocmRAJWqST1wQYhyyjXJ3SJc= -github.com/Azure/go-autorest/autorest/validation v0.1.0/go.mod h1:Ha3z/SqBeaalWQvokg3NZAlQTalVMtOIAs1aGK7G6u8= +github.com/Azure/go-autorest/autorest/mocks v0.3.0 h1:qJumjCaCudz+OcqE9/XtEPfvtOjOmKaui4EOpFI6zZc= +github.com/Azure/go-autorest/autorest/mocks v0.3.0/go.mod h1:a8FDP3DYzQ4RYfVAxAN3SVSiiO77gL2j2ronKKP0syM= +github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= github.com/Azure/go-autorest/logger v0.1.0 h1:ruG4BSDXONFRrZZJ2GUXDiUyVpayPmb1GnWeHDdaNKY= github.com/Azure/go-autorest/logger v0.1.0/go.mod h1:oExouG+K6PryycPJfVSxi/koC6LSNgds39diKLz7Vrc= +github.com/Azure/go-autorest/logger v0.2.0 h1:e4RVHVZKC5p6UANLJHkM4OfR1UKZPj8Wt8Pcx+3oqrE= +github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= github.com/Azure/go-autorest/tracing v0.5.0 h1:TRn4WjSnkcSy5AEG3pnbtFSwNtwzjr4VYyQflFE619k= github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbtp2fGCgRFtBroKn4Dk= -github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo= +github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/DATA-DOG/go-sqlmock v1.4.1/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157 h1:hY39LwQHh+1kaovmIjOrlqnXNX6tygSRfLkkK33IkZU= -github.com/Djarvur/go-err113 v0.0.0-20200410182137-af658d038157/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= -github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= -github.com/MakeNowJust/heredoc v0.0.0-20171113091838-e9091a26100e/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.0.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig/v3 v3.0.0/go.mod h1:NEUY/Qq8Gdm2xgYA+NwJM6wmfdRV9xkh8h/Rld20R0U= -github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= -github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= +github.com/Masterminds/squirrel v1.4.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= github.com/Masterminds/vcs v1.13.1/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.12/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/hcsshim v0.0.0-20190417211021-672e52e9209d/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/NYTimes/gziphandler v1.0.1/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenPeeDeeP/depguard v1.0.1 h1:VlW4R6jmBIv3/u1JNlawEvJMM4J+dPORPaZasQee8Us= -github.com/OpenPeeDeeP/depguard v1.0.1/go.mod h1:xsIw86fROiiwelg+jB2uM9PiKihMMmUx/1V+TNhjQvM= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/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/Rican7/retry v0.1.0/go.mod h1:FgOROf8P5bebcC1DS0PdOQiqGUridaZvikzUmkFW6gg= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= 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/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= -github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= +github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/aws/aws-sdk-go v1.16.26/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/bazelbuild/bazel-gazelle v0.0.0-20181012220611-c728ce9f663e/go.mod h1:uHBSeeATKpVazAACZBDPL/Nk/UhQDDsJWDlqYJo8/Us= -github.com/bazelbuild/buildtools v0.0.0-20180226164855-80c7f0d45d7e/go.mod h1:5JP0TXzWDHXv8qvxRC4InIazwdyDseBDbzESUMKk1yU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bndr/gojenkins v0.0.0-20181125150310-de43c03cf849 h1:nV1eKvUlIKhH4/atJIT9tpClzSRTs7TIHS/dRY1EQCw= -github.com/bndr/gojenkins v0.0.0-20181125150310-de43c03cf849/go.mod h1:J2FxlujWW87NJJrdysyctcDllRVYUONGGlHX16134P4= -github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= -github.com/bombsimon/wsl/v3 v3.0.0 h1:w9f49xQatuaeTJFaNP4SpiWSR5vfT6IstPtM62JjcqA= -github.com/bombsimon/wsl/v3 v3.0.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= -github.com/brancz/gojsontoyaml v0.0.0-20190425155809-e8bd32d46b3d/go.mod h1:IyUJYN1gvWjtLF5ZuygmxbnsAyP3aJS6cHzIuZY50B0= +github.com/bndr/gojenkins v1.0.1 h1:DFIuamRSmXoI/CwB44txuRf8xaHZNejZge/Lui4RYD4= +github.com/bndr/gojenkins v1.0.1/go.mod h1:J2FxlujWW87NJJrdysyctcDllRVYUONGGlHX16134P4= github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/bugsnag-go v1.5.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= +github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E= -github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= -github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY= github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.0 h1:yTUvW7Vhb89inJ+8irsUqiWjh8iT6sQPZiQzI6ReGkA= -github.com/cespare/xxhash/v2 v2.1.0/go.mod h1:dgIUBU3pDso/gPgZ1osOZ0iQf77oPR28Tjxl5dIMyVM= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= -github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= -github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/cfssl v0.0.0-20180726162950-56268a613adf/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= -github.com/clusterhq/flocker-go v0.0.0-20160920122132-2b8b7259d313/go.mod h1:P1wt9Z3DP8O6W3rvwCt0REIlshg1InHImaLW0t3ObY0= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= -github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= -github.com/container-storage-interface/spec v1.1.0/go.mod h1:6URME8mwIBbpVyZV93Ce5St17xBiQJQY67NDsuohiy4= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/console v0.0.0-20170925154832-84eeaae905fa/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/containerd v1.0.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190823190603-4a2f61c4f2b4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/continuity v0.0.0-20181203112020-004b46473808/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= +github.com/containerd/containerd v1.3.4/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= +github.com/containerd/continuity v0.0.0-20200413184840-d3ef23f19fbb/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= +github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190228175220-2a93cfde8c20/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/coredns/corefile-migration v1.0.2/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E= -github.com/coreos/bbolt v1.3.1-coreos.6/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/etcd v3.3.15+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/prometheus-operator v0.35.1 h1:Cl/0axMpifGsKTNMwQB5PWciOSkNFa/LOdCVQJ9n/Jc= -github.com/coreos/prometheus-operator v0.35.1/go.mod h1:XHYZUStZWcwd1yk/1DjZv/fywqKIyAJ6pSwvIr+v9BQ= -github.com/coreos/rkt v1.30.0/go.mod h1:O634mlH6U7qk87poQifK6M2rsFNt+FyUTWNMnP1hF1U= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= @@ -180,9 +200,9 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= -github.com/deislabs/oras v0.7.0/go.mod h1:sqMKPG3tMyIX9xwXUBRLhZ24o+uT4y6jgBD2RzUTKDM= github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= @@ -190,28 +210,26 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/cli v0.0.0-20190506213505-d88565df0c2d/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v0.0.0-20200130152716-5d0cf8839492/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v0.0.0-20191216044856-a8371794149d/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker-credential-helpers v0.6.1/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= +github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v1.4.2-0.20200203170920-46ec8731fbce/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= -github.com/docker/go-connections v0.3.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.0-20181218153428-b84716841b82/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libnetwork v0.0.0-20180830151422-a9cd636e3789/go.mod h1:93m0aTqz6z+g32wla4l4WxTrdtvBRmVzYRkYvasA5Z8= github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96 h1:cenwrSVm+Z7QLSV/BsnenAOcDXdX4cMv4wP0B/5QbPg= github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c h1:ZfSZ3P3BedhKGUhzj7BQlPSU4OvT6tfOKe3DVHzOA7s= -github.com/docker/spdystream v0.0.0-20181023171402-6480d4af844c/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= @@ -219,83 +237,91 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m 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/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153 h1:yUdfgN0XgIJw7foRItutHYUIhlcKzcSf5vDpdhQAKTc= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +github.com/emersion/go-sasl v0.0.0-20190704090222-36b50694675c h1:Spm8jy+jWYG/Dn6ygbq/LBW/6M27kg59GK+FkKjexuw= github.com/emersion/go-sasl v0.0.0-20190704090222-36b50694675c/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k= -github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e h1:ba7YsgX5OV8FjGi5ZWml8Jng6oBrJAb3ahqWMJ5Ce8Q= -github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 h1:OJyUGMJTzHTd1XQp98QTaHernxMYzRaOasRir9hUlFQ= +github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21/go.mod h1:iL2twTeMvZnrg54ZoPDNfJaJaqy0xIQFuBdrLsmspwQ= github.com/emersion/go-smtp v0.11.2 h1:5PO2Kwsx+HXuytntCfMvcworC/iq45TPGkwjnaBZFSg= github.com/emersion/go-smtp v0.11.2/go.mod h1:byi9Y32SuKwjTJt9DO2tTWYjtF3lEh154tE1AcaJQSY= +github.com/emersion/go-smtp v0.14.0 h1:RYW203p+EcPjL8Z/ZpT9lZ6iOc8MG1MQzEx1UKEkXlA= +github.com/emersion/go-smtp v0.14.0/go.mod h1:qm27SGYgoIPRot6ubfQ/GpiPy/g3PaZAVRxiO/sDUgQ= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao2r4iyvLdACqsl/Ljk= github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.11.1+incompatible h1:CjKsv3uWcCMvySPQYKxO8XX3f9zD4FeZRsW4G0B4ffE= -github.com/emicklei/go-restful v2.11.1+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw= -github.com/evanphx/json-patch v4.1.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 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/evanphx/json-patch v4.9.0+incompatible h1:kLcOMZeuLAJvL2BPWLMIj5oaZQobrkAqrL+WFZwQses= +github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= +github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ= github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51/go.mod h1:Yg+htXGokKKdzcwhuNDwVvN+uBxDGXJ7G/VN1d8fa64= +github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojtoVVWjGfOF9635RETekkoH6Cc9SX0A= github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg= +github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y= github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= -github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structtag v1.1.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= -github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible h1:TcekIExNqud5crz4xD2pavyTgWiPvpYe4Xau31I0PRk= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32 h1:Mn26/9ZMNWSw9C9ERFA1PUxfmGpolnw2v0bKOREu5ew= -github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32/go.mod h1:GIjDIg/heH5DOkXY3YJ/wNhfHsQHoXGjl8G8amsYQ1I= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= -github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= -github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= -github.com/go-bindata/go-bindata v3.1.2+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= +github.com/go-bindata/go-bindata/v3 v3.1.3/go.mod h1:1/zrpXsLD8YDIbhZRqXzm1Ghc7NhEvIN9+Z6R5/xH4I= github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuYrgaRcnW4= github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= -github.com/go-critic/go-critic v0.4.1 h1:4DTQfT1wWwLg/hzxwD9bkdhDQrdJtxe6DUTadPlrIeE= -github.com/go-critic/go-critic v0.4.1/go.mod h1:7/14rZGnZbY6E38VEGk2kVhoq6itzc1E68facVDK23g= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-lintpack/lintpack v0.5.2 h1:DI5mA3+eKdWeJ40nU4d6Wc26qmdG8RCi/btYq0TuRN0= -github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= +github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/logr v0.3.0 h1:q4c+kbcR0d5rSurhBR8dIgieOaYpXtsdTYfx22Cu6rs= +github.com/go-logr/logr v0.3.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-logr/zapr v0.1.1 h1:qXBXPDdNncunGs7XeEpsJt8wCjYBygluzfdLO0G5baE= -github.com/go-logr/zapr v0.1.1/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= -github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= +github.com/go-logr/zapr v0.2.0 h1:v6Ji8yBW77pva6NkJKQdHLAJKrIJKRHz0RXwPqCHSR4= +github.com/go-logr/zapr v0.2.0/go.mod h1:qhKdvif7YF5GI9NWEpyxTSSBdGmzkNguibrdCNVPunU= github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI= github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.18.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik= github.com/go-openapi/analysis v0.19.2/go.mod h1:3P1osvZa9jKjb8ed2TPng3f0i/UY9snX6gxi44djMjk= +github.com/go-openapi/analysis v0.19.5/go.mod h1:hkEAkxagaIvIP7VTn8ygJNkd4kAYON2rCu0v0ObL0AU= github.com/go-openapi/errors v0.17.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.18.0/go.mod h1:LcZQpmvG4wyF5j4IhA73wkLFQg+QJXOQHVjmcZxhka0= github.com/go-openapi/errors v0.19.2/go.mod h1:qX0BLWsyaKfvhluLejVpVNwNRdXZhEbTA4kxxpKBC94= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= github.com/go-openapi/jsonreference v0.17.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= github.com/go-openapi/jsonreference v0.18.0/go.mod h1:g4xxGn04lDIRh0GJb5QlpE3HfopLOL6uZrK/VgnsK9I= -github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= github.com/go-openapi/jsonreference v0.19.3 h1:5cxNfTy0UVC3X8JL5ymxzyoUZmo8iZb+jeTWn7tUa8o= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= @@ -303,547 +329,466 @@ github.com/go-openapi/loads v0.17.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf github.com/go-openapi/loads v0.18.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.0/go.mod h1:72tmFy5wsWx89uEVddd0RjRWPZm92WRLhf7AC+0+OOU= github.com/go-openapi/loads v0.19.2/go.mod h1:QAskZPMX5V0C2gvfkGZzJlINuP7Hx/4+ix5jWFxsNPs= +github.com/go-openapi/loads v0.19.4/go.mod h1:zZVHonKd8DXyxyw4yfnVjPzBjIQcLt0CCsn0N0ZrQsk= github.com/go-openapi/runtime v0.0.0-20180920151709-4f900dc2ade9/go.mod h1:6v9a6LTXWQCdL8k1AO3cvqx5OtZY/Y9wKTgaoP6YRfA= github.com/go-openapi/runtime v0.19.0/go.mod h1:OwNfisksmmaZse4+gpV3Ne9AyMOlP1lt4sK4FXt0O64= +github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29gLDlFGtJ8NL4= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.18.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI= github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= -github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= -github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.3 h1:0XRyw8kguri6Yw4SxhsQA/atC88yqrk0+G4YhI2wabc= +github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= github.com/go-openapi/strfmt v0.17.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.18.0/go.mod h1:P82hnJI0CXkErkXi8IKjPbNBM6lV6+5pLP5l494TcyU= github.com/go-openapi/strfmt v0.19.0/go.mod h1:+uW+93UVvGGq2qGaZxdDeJqSAqBqBdl+ZPMF/cC8nDY= +github.com/go-openapi/strfmt v0.19.3/go.mod h1:0yX7dbo8mKIvc3XSKp7MNfxw4JytCfCD6+bY1AVL9LU= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= github.com/go-openapi/swag v0.17.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.18.0/go.mod h1:AByQ+nYG6gQg71GINrmuDXCPWdL640yX49/kXLo40Tg= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.4/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/validate v0.18.0/go.mod h1:Uh4HdOzKt19xGIGm1qHf/ofbX1YQ4Y+MYsct2VUrAJ4= github.com/go-openapi/validate v0.19.2/go.mod h1:1tRCw7m3jtI8eNWEEliiAqUIcBztB2KDnRCRMUi7GTA= -github.com/go-ozzo/ozzo-validation v3.5.0+incompatible/go.mod h1:gsEKFIVnabGBt6mXmxK0MoFy+cZoTJY6mu5Ll3LVLBU= +github.com/go-openapi/validate v0.19.5/go.mod h1:8DJv2CVJQ6kGNpFW6eV9N3JviE1C85nY1c2z52x1Gk4= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= -github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= -github.com/go-toolsmith/astcopy v1.0.0 h1:OMgl1b1MEpjFQ1m5ztEO06rz5CUd3oBv9RF7+DyvdG8= -github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ= -github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astequal v1.0.0 h1:4zxD8j3JRFNyLN46lodQuqz3xdKSrur7U/sr0SDS/gQ= -github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg= -github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= -github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= -github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU= -github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk= -github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= -github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= -github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks= -github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc= -github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/typep v1.0.0 h1:zKymWyA1TRYvqYrYDrfEMZULyrhcnGY3x7LDKU2XQaA= -github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= -github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= -github.com/gobuffalo/envy v1.6.15/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= -github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/envy v1.7.1/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= +github.com/gobuffalo/flect v0.2.0/go.mod h1:W3K3X9ksuZfir8f/LrfVtWmCDQFfayuylOJ7sz/Fj80= +github.com/gobuffalo/flect v0.2.2/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= +github.com/gobuffalo/logger v1.0.1/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= -github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= -github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobuffalo/packr/v2 v2.7.1/go.mod h1:qYEvAazPaVxy7Y7KR0W8qYEE+RymX74kETFqjFoFlOc= github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus v4.1.0+incompatible/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/gofrs/flock v0.0.0-20190320160742-5135e617513b/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/flock v0.7.1 h1:DP+LD/t0njgoPBvT5MJLeliUIVQR03hiKR6vezdwHlc= -github.com/gofrs/flock v0.7.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/godror/godror v0.13.3/go.mod h1:2ouUT4kdhUBk7TAkHWD4SN0CdI0pgEQbo8FVHhbSKWg= +github.com/gofrs/flock v0.8.0/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/uuid v3.3.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.2.2-0.20190730201129-28a6bbf47e48/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/golang-migrate/migrate/v4 v4.6.2/go.mod h1:JYi6reN3+Z734VZ0akNuyOJNcrg45ZL7LDBMW3WGJL0= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20181024230925-c65c006176ff/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9 h1:uHTyIjqVhYRhLbJ8nIiOJHkEZZ+5YoOsAbD3sk82NiE= -github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1 h1:ocYkMQY5RrXTYgXl7ICpV0IXwlEQGwKIsery4gyXa1U= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6 h1:YYWNAGTKWhKpcLLt7aSj/odlKrSrelQwlovBpDuf19w= -github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613 h1:9kfjN3AdxcbsZBf8NjltjWihK2QfBBBZuv91cMFfDHw= -github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8= -github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3 h1:pe9JHs3cHHDQgOFXJJdYkK6fLz2PWyYtP4hthoCMvs8= -github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o= -github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee h1:J2XAy40+7yz70uaOiMbNnluTg7gyQhtGqLQncQh+4J8= -github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU= -github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a h1:iR3fYXUjHCR97qWS8ch1y9zPNsgXThGwjKPrYfqMPks= -github.com/golangci/gofmt v0.0.0-20190930125516-244bba706f1a/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU= -github.com/golangci/golangci-lint v1.26.0 h1:CLLGRSA9BLMiNvsWPXHioYAdfIx9tkgdVWyA6bIdYCo= -github.com/golangci/golangci-lint v1.26.0/go.mod h1:tefbO6RcigFzvTnDC+Y51kntVGgkuCAVsC+mnfbPruc= -github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc h1:gLLhTLMk2/SutryVJ6D4VZCU3CUqr8YloG7FPIBWFpI= -github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770 h1:EL/O5HGrF7Jaq0yNhBLucz9hTuRzj2LdwGBOaENgxIk= -github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= -github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21 h1:leSNB7iYzLYSSx3J/s5sVf4Drkc68W2wm4Ixh/mr0us= -github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI= -github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0 h1:HVfrLniijszjS1aiNg8JbBMO2+E1WIQ+j/gL4SQqGPg= -github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho= github.com/golangplus/fmt v0.0.0-20150411045040-2a5d6d7d2995/go.mod h1:lJgMEyOkYFkPcDKwRXegd+iM6E7matEszMG5HhwytU8= github.com/golangplus/testing v0.0.0-20180327235837-af21d9c3145e/go.mod h1:0AA//k/eakGydO4jKRoRL2j92ZKSzTgj9tclaCrvXHk= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/cadvisor v0.34.0/go.mod h1:1nql6U13uTHaLYB8rLS5x9IJc2qT6Xd/Tr1sTX6NE48= -github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= -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/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= +github.com/google/gofuzz v1.1.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/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/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/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.2.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= +github.com/googleapis/gnostic v0.1.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= +github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= +github.com/googleapis/gnostic v0.5.1 h1:A8Yhf6EtqTv9RMsU6MQTyrtV1TjWlR6xU9BsZIwuTCM= +github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= -github.com/gophercloud/gophercloud v0.2.0 h1:lD2Bce2xBAMNNcFZ0dObTpXkGLlVIb33RPVUNVpw6ic= -github.com/gophercloud/gophercloud v0.2.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/handlers v1.4.0/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3 h1:JVnpOZS+qxli+rgVl98ILOXVNbW+kb5wcxeGx8ShUIw= -github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gosuri/uitable v0.0.1/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gosuri/uitable v0.0.4/go.mod h1:tKR86bXuXPZazfOTG1FIzvjIdXzd0mo4Vtn16vt0PJo= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7 h1:6TSoaYExHper8PYsJu23GWVNOyYRCSnIFyxKgLSZ54w= -github.com/gregjones/httpcache v0.0.0-20170728041850-787624de3eb7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/gregjones/httpcache v0.0.0-20181110185634-c63ab54fda8f/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17 h1:prg2TTpTOcJF1jRWL2zSU1FQNgB0STAFNux8GK82y8k= -github.com/gregjones/httpcache v0.0.0-20190203031600-7a902570cb17/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v0.0.0-20190222133341-cfaf5686ec79/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= +github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.3.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.4/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-health-probe v0.2.1-0.20181220223928-2bf0a5b182db/go.mod h1:uBKkC2RbarFsvS5jMJHpVhTLvGlGQj9JJwkaePE3FWI= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-health-probe v0.3.2/go.mod h1:izVOQ4RWbjUR6lm4nn+VLJyQ+FyaiGmprEYgI04Gs7U= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3 h1:YPkqC67at8FYaadspW/6uE0COsBxS2656RLEr8Bppgk= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/heketi/heketi v9.0.0+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= -github.com/heketi/rest v0.0.0-20180404230133-aa6a65207413/go.mod h1:BeS3M108VzVlmAue3lv2WcGuPAX94/KN63MUURzbYSI= -github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= -github.com/heketi/utils v0.0.0-20170317161834-435bc5bdfa64/go.mod h1:RYlF4ghFZPPmk2TC5REt5OFwvfb6lzxFWrTWB+qs28s= -github.com/helm/helm-2to3 v0.2.0/go.mod h1:jQUVAWB0bM7zNIqKPIfHFzuFSK0kHYovJrjO+hqcvRk= -github.com/helm/helm-2to3 v0.5.1/go.mod h1:AXFpQX2cSQpss+47ROPEeu7Sm4+CRJ1jKWCEQdHP3/c= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= -github.com/iancoleman/strcase v0.0.0-20190422225806-e506e3ef7365/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= +github.com/iancoleman/strcase v0.0.0-20191112232945-16388991a334/go.mod h1:SK73tn/9oHe+/Y0h39VT4UCxmurVJkR5NA7kMEAOgSE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/improbable-eng/thanos v0.3.2/go.mod h1:GZewVGILKuJVPNRn7L4Zw+7X96qzFOwj63b22xYGXBE= +github.com/imdario/mergo v0.3.10 h1:6q5mVkdH/vYmqngx7kZQTjJ5HRsx+ImorDIEQ+beJgc= +github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= -github.com/jimstudt/http-authentication v0.0.0-20140401203705-3eca13d6893a/go.mod h1:wK6yTYYcgjHE1Z1QtXACPDjcFJyBskHEdagmnq3vsP8= -github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk= -github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a/go.mod h1:xRskid8CManxVta/ALEhJha/pweKBaVG6fWgc0yH25s= -github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3 h1:jNYPNLe3d8smommaoQlK7LOA5ESyUJJ+Wf79ZtA7Vp4= -github.com/jirfag/go-printf-func-name v0.0.0-20191110105641-45db9963cdd3/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jmoiron/sqlx v1.2.1-0.20190826204134-d7d95172beb5/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= github.com/joefitzgerald/rainbow-reporter v0.1.0/go.mod h1:481CNgqmVHQZzdIbN52CupLJyoVwB10FQ/IQlF1pdL8= -github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jsonnet-bundler/jsonnet-bundler v0.1.0/go.mod h1:YKsSFc9VFhhLITkJS3X2PrRqWG9u2Jq99udTdDjQLfM= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= -github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= -github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.4/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +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/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o= +github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/libopenstorage/openstorage v1.0.0/go.mod h1:Sp1sIObHjat1BeXhfMqLZ14wnOzEhNx2YQedreMcUyc= +github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z9BP0jIOc= -github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= -github.com/lpabon/godbc v0.1.1/go.mod h1:Jo9QV0cf3U6jZABgiJ2skINAXb9j8m51r07g4KI92ZA= -github.com/lucas-clemente/aes12 v0.0.0-20171027163421-cd47fb39b79f/go.mod h1:JpH9J1c9oX6otFSgdUHwUBUizmKlrMjxWnIAjff4m04= -github.com/lucas-clemente/quic-clients v0.1.0/go.mod h1:y5xVIEoObKqULIKivu+gD/LU90pL73bTdtQjPBvtCBk= -github.com/lucas-clemente/quic-go v0.10.2/go.mod h1:hvaRS9IHjFLMq76puFJeWNfmn+H70QZ/CXoxqw9bzao= -github.com/lucas-clemente/quic-go-certificates v0.0.0-20160823095156-d2f86524cced/go.mod h1:NCcRLrOTZbzhZvixZLlERbJtDtYsmMw8Jc4vS8Z0g58= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailgun/mailgun-go/v3 v3.6.0 h1:oQWhyDTFjSiuO6vx1PRlfLZ7Fu+oK0Axn0UTREh3k/g= -github.com/mailgun/mailgun-go/v3 v3.6.0/go.mod h1:E81I5Agcfi/u1szdehi6p6ttdRX/UD3Rq2SrUzwyFIU= +github.com/mailgun/mailgun-go/v3 v3.6.4 h1:+cvbZRgLSHivbz/w1iWLmxVl6Bqf4geD2D7QMj4+8PE= +github.com/mailgun/mailgun-go/v3 v3.6.4/go.mod h1:ZjVnH8S0dR2BLjvkZc/rxwerdcirzlA12LQDuGAadR0= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0 h1:aizVhC/NAAcKWb+5QsU1iNOZb4Yws5UO2I+aIprQITM= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/maorfr/helm-plugin-utils v0.0.0-20181205064038-588190cb5e3b/go.mod h1:p3gwmRSFqbWw6plBpR0sKl3n3vpu8kX70gvCJKMvvCA= -github.com/maorfr/helm-plugin-utils v0.0.0-20200216074820-36d2fcf6ae86/go.mod h1:p3gwmRSFqbWw6plBpR0sKl3n3vpu8kX70gvCJKMvvCA= -github.com/maratori/testpackage v1.0.1 h1:QtJ5ZjqapShm0w5DosRjg0PRlSdAdlx+W6cCKoALdbQ= -github.com/maratori/testpackage v1.0.1/go.mod h1:ddKdw+XG0Phzhx8BFDTKgpWP4i7MpApTE5fXSKAqwDU= -github.com/markbates/inflect v1.0.4 h1:5fh1gzTFhfae06u3hzHYO9xe3l3v3nW5Pwt3naLTP5g= github.com/markbates/inflect v1.0.4/go.mod h1:1fR9+pO2KHEO9ZRtto13gDwwZaAKstQzferVeWqbgNs= github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/marten-seemann/qtls v0.2.3/go.mod h1:xzjG7avBwGGbdZ8dTGxlBnLArsVKLvwmjgmPuiQEcYk= -github.com/martinlindhe/base36 v1.0.0/go.mod h1:+AtEs8xrBpCeYgSLoY/aJ6Wf37jtBuR0s35750M27+8= -github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb h1:RHba4YImhrUVQDHUCe2BNSOz4tVy2yGyXhvYDvxGgeE= -github.com/matoous/godox v0.0.0-20190911065817-5d6d842e92eb/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/mattbaird/jsonpatch v0.0.0-20171005235357-81af80346b1a/go.mod h1:M1qoD/MqPgTZIk0EWKB38wE28ACRfVcn+cU08jyArI0= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= -github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.6/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-oci8 v0.0.7/go.mod h1:wjDx6Xm9q7dFtHJvIlrI99JytznLw5wQ4R+9mNXJwGI= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-shellwords v1.0.5/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/mattn/go-shellwords v1.0.9/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= +github.com/mattn/go-sqlite3 v1.12.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/maxbrunsfeld/counterfeiter/v6 v6.2.1/go.mod h1:F9YacGpnZbLQMzuPI0rR6op21YvNu/RjL705LJJpM3k= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI= +github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY= -github.com/mesos/mesos-go v0.0.9/go.mod h1:kPYCMQ9gsOXVAle1OsoY4I1+9kPu8GHkf88aV59fDr4= -github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= -github.com/miekg/dns v0.0.0-20181005163659-0d29b283ac0f/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mikefarah/yaml/v2 v2.4.0/go.mod h1:ahVqZF4n1W4NqwvVnZzC4es67xsW9uR/RRf2RRxieJU= github.com/mikefarah/yq/v2 v2.4.1/go.mod h1:i8SYf1XdgUvY2OFwSqGAtWOOgimD2McJ6iutoxRm4k0= -github.com/mindprince/gonvml v0.0.0-20171110221305-fee913ce8fb2/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= -github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-ps v0.0.0-20190716172923-621e5597135b/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= -github.com/mitchellh/hashstructure v1.0.0/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= -github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/moby v0.7.3-0.20190826074503-38ab9da00309/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mohae/deepcopy v0.0.0-20170603005431-491d3605edfb/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mozilla/tls-observatory v0.0.0-20200220173314-aae45faa4006/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk= -github.com/mrunalp/fileutils v0.0.0-20160930181131-4ee1cc9a8058/go.mod h1:x8F1gnqOkIEiO4rqoeEEEqQbo7HjGMTvyoq3gej4iT0= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20190414153302-2ae31c8b6b30/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mvdan/xurls v1.1.0/go.mod h1:tQlNn3BED8bE/15hnSL2HLkDeLWpNPAwtw7wkEq44oU= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nakabonne/nestif v0.3.0 h1:+yOViDGhg8ygGrmII72nV9B/zGxY188TYpfolntsaPw= -github.com/nakabonne/nestif v0.3.0/go.mod h1:dI314BppzXjJ4HsCnbo7XzrJHPszZsjnk5wEBSYHI2c= github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= -github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= -github.com/naoina/toml v0.1.1/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d h1:AREM5mwr4u1ORQBMvzfzBgpsctsbQikCVpvC+tX285E= -github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/onsi/ginkgo v1.4.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/olekukonko/tablewriter v0.0.2/go.mod h1:rSAaSIOAGT9odnlyGlUfAJaoc5w2fSBUmeGDbRWPxyQ= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg= -github.com/onsi/gomega v1.3.0/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.1 h1:jMU0WaQrP0a/YAEq8eJmJKjBoMs+pClEr1vDMlM/Do4= +github.com/onsi/ginkgo v1.14.1/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.10.2 h1:aY/nuoWlKJud2J6U0E3NWsjlg+0GtwXxgEqthRdzlcs= +github.com/onsi/gomega v1.10.2/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.2-0.20190823105129-775207bd45b6/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc2.0.20190611121236-6cc515888830/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.2.2/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= -github.com/openshift/api v0.0.0-20200205133042-34f0ec8dab87/go.mod h1:fT6U/JfG8uZzemTRwZA2kBDJP5nWz7v05UHnty/D+pk= -github.com/openshift/api v3.9.1-0.20190924102528-32369d4db2ad+incompatible h1:6il8W875Oq9vycPkRV5TteLP9IfMEX3lyOl5yN+CtdI= -github.com/openshift/api v3.9.1-0.20190924102528-32369d4db2ad+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= -github.com/openshift/client-go v0.0.0-20190923180330-3b6373338c9b/go.mod h1:6rzn+JTr7+WYS2E1TExP4gByoABxMznR6y2SnUIkmxk= -github.com/openshift/origin v0.0.0-20160503220234-8f127d736703/go.mod h1:0Rox5r9C8aQn6j1oAOQ0c1uC86mYbUFObzjBRvUKHII= -github.com/openshift/prom-label-proxy v0.1.1-0.20191016113035-b8153a7f39f1/go.mod h1:p5MuxzsYP1JPsNGwtjtcgRHHlGziCJJfztff91nNixw= +github.com/openshift/api v3.9.0+incompatible h1:fJ/KsefYuZAjmrr3+5U9yZIZbTOpVkDDLDLFresAeYs= +github.com/openshift/api v3.9.0+incompatible/go.mod h1:dh9o4Fs58gpFXGSYfnVxGR9PnV53I8TW84pQaJDdGiY= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/operator-framework/api v0.0.0-20200120235816-80fd2f1a09c9/go.mod h1:S5IdlJvmKkF84K2tBvsrqJbI2FVy03P88R75snpRxJo= -github.com/operator-framework/api v0.1.1/go.mod h1:yzNYR7qyJqRGOOp+bT6Z/iYSbSPNxeh3Si93Gx/3OBY= -github.com/operator-framework/operator-lifecycle-manager v0.0.0-20191115003340-16619cd27fa5/go.mod h1:zL34MNy92LPutBH5gQK+gGhtgTUlZZX03I2G12vWHF4= -github.com/operator-framework/operator-lifecycle-manager v0.0.0-20200321030439-57b580e57e88/go.mod h1:7Ut8p9jJ8C6RZyyhZfZypmlibCIJwK5Wcc+WZDgLkOA= -github.com/operator-framework/operator-registry v1.5.1/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY= -github.com/operator-framework/operator-registry v1.5.3/go.mod h1:agrQlkWOo1q8U1SAaLSS2WQ+Z9vswNT2M2HFib9iuLY= -github.com/operator-framework/operator-registry v1.5.7-0.20200121213444-d8e2ec52c19a/go.mod h1:ekexcV4O8YMxdQuPb+Xco7MHfVmRIq7Jvj5e6NU7dHI= -github.com/operator-framework/operator-registry v1.6.1/go.mod h1:sx4wWMiZtYhlUiaKscg3QQUPPM/c1bkrAs4n4KipDb4= -github.com/operator-framework/operator-registry v1.6.2-0.20200330184612-11867930adb5/go.mod h1:SHff373z8asEkPo6aWpN0qId4Y/feQTjZxRF8PRhti8= -github.com/operator-framework/operator-sdk v0.17.0 h1:+TTrGjXa+lm7g7Cm0UtFcgOjnw1x9/lBorydpsIIhOY= -github.com/operator-framework/operator-sdk v0.17.0/go.mod h1:wmYi08aoUmtgfoUamURmssI4dkdFGNtSI1Egj+ZfBnk= -github.com/otiai10/copy v1.0.1/go.mod h1:8bMCJrAqOtN/d9oyh5HR7HhLQMvcGMpGdwRDYsfOCHc= -github.com/otiai10/copy v1.0.2/go.mod h1:c7RpqBkwMom4bYTSkLSym4VSJz/XtncWRAj/J4PEIMY= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/operator-framework/api v0.3.22/go.mod h1:GVNiB6AQucwdZz3ZFXNv9HtcLOzcFnr6O/QldzKG93g= +github.com/operator-framework/api v0.4.0/go.mod h1:xXYReW8+PpSBHMxsf0e7uhtfQTLqIM1iz4X6zUs20+c= +github.com/operator-framework/operator-lib v0.3.0/go.mod h1:LTp5UQd8ivq4MXqm/W/XHulHQ0RRoZXsAj73sNMAQxc= +github.com/operator-framework/operator-registry v1.15.3/go.mod h1:CI7cu5ANoSQB54XWr/hXefm1PbKNJztSrhGBEDLCNCg= +github.com/operator-framework/operator-sdk v1.3.0 h1:IjkCopydSvUgNA02mH5pHIiUJzuMxPpsjBUo3xghckY= +github.com/operator-framework/operator-sdk v1.3.0/go.mod h1:uBStSTOWTHxWcrzKjseSxfPFBiKpU3tHziIDTPo60iM= +github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v0.0.0-20190513014714-f5a3d24e5776/go.mod h1:3HNVkVOU7vZeFXocWuvtcS0XSFLcf2XUSDHkq9t1jU4= -github.com/otiai10/mint v1.2.3/go.mod h1:YnfyPNhBvnY8bW4SGQHCs/aAFhkgySlMZbrF5U0bOVw= -github.com/otiai10/mint v1.2.4/go.mod h1:d+b7n/0R3tdyUYYylALXpWQ/kTN+QobSq/4SRGBkR3M= +github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= +github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= -github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvlsMzzNayLOI= -github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U= -github.com/prometheus/client_golang v1.5.1 h1:bdHYieyGlH+6OLEk2YQha8THib30KP0/yD0YH9m6xcA= -github.com/prometheus/client_golang v1.5.1/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_golang v1.7.1 h1:NTGy1Ja9pByO+xAeH/qiWnLrKtr3hJPNjaVUwnjpdpA= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U= -github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190129233650-316cf8ccfec5/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8= github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/prometheus/prometheus v2.3.2+incompatible/go.mod h1:oAIUtOny2rjMX0OWN5vPR5/q/twIROJvdqnQKDdil/s= +github.com/prometheus/procfs v0.1.3 h1:F0+tqvhOksq22sc6iCHF5WGlWjdwj92p0udFh1VFBS8= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/prometheus/tsdb v0.8.0/go.mod h1:fSI0j+IUQrDd7+ZtR9WKIGtoYAYAJUKcKhYLG25tN4g= -github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI= -github.com/quobyte/api v0.1.2/go.mod h1:jL7lIHrmqQ7yh05OJ+eEEdHr0u/kmT1Ff9iHd+4H6VI= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= -github.com/robfig/cron v0.0.0-20170526150127-736158dc09e1/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= -github.com/robfig/cron v1.1.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.5.0 h1:Usqs0/lDK/NqTkvrmKSwA/3XkZAs7ZAW/eLeQ2MVBTw= -github.com/rogpeppe/go-internal v1.5.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/rubenv/sql-migrate v0.0.0-20191025130928-9355dd04f4b3/go.mod h1:WS0rl9eEliYI8DPnr3TOwz4439pay+qNgzJoVya/DmY= -github.com/rubiojr/go-vhd v0.0.0-20160810183302-0bfd3b39853c/go.mod h1:DM5xW0nvfNNm2uytzsvhI3OnX8uzaRAg8UX/CnDqbto= -github.com/russross/blackfriday v0.0.0-20170610170232-067529f716f4/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.4.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rubenv/sql-migrate v0.0.0-20200616145509-8d140a17f351/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/ryancurrah/gomodguard v1.0.4 h1:oCreMAt9GuFXDe9jW4HBpc3GjdX3R/sUEcLAGh1zPx8= -github.com/ryancurrah/gomodguard v1.0.4/go.mod h1:9T/Cfuxs5StfsocWr4WzDL36HqnX0fVb9d5fSEaLhoE= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/securego/gosec v0.0.0-20200316084457-7da9f46445fd h1:qB+l4fYZsH78xORC1aqVS0zNmgkQp4rkj2rvfxQMtzc= -github.com/securego/gosec v0.0.0-20200316084457-7da9f46445fd/go.mod h1:NurAFZsWJAEZjogSwdVPlHkOZB3DOAU7gsPP8VFZCHc= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shirou/gopsutil v0.0.0-20190901111213-e4ec7b275ada/go.mod h1:WWnYX4lzhCH5h/3YBfyVA3VbLYjlMZZAQcW9ojMexNc= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= -github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= +github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.3/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sourcegraph/go-diff v0.5.1 h1:gO6i5zugwzo1RVTvgvfwCOSVegNuvnNi6bAD1QCmkHs= -github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/cobra v0.0.7/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -851,154 +796,157 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk= -github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= -github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/syndtr/gocapability v0.0.0-20160928074757-e7cb7fa329f4/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2 h1:Xr9gkxfOP0KQWXKNqmwe8vEeSUiUj4Rlee9CMVX2ZUQ= -github.com/tdakkota/asciicheck v0.0.0-20200416190851-d7f85be797a2/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= -github.com/tetafro/godot v0.3.3 h1:uJjg8N+Ee10rAnaqJGet1WeI0YW4fiX9pKbwqnsqH6k= -github.com/tetafro/godot v0.3.3/go.mod h1:pT6/T8+h6//L/LwQcFc4C0xpfy1euZwzS1sHdrFCms0= -github.com/thecodeteam/goscaleio v0.1.0/go.mod h1:68sdkZAsK8bvEwBlbQnlLS+xU+hvLYM/iQ8KXej1AwM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e h1:RumXZ56IrCj4CL+g1b9OL/oH0QnsF976bC8xQFYUD5Q= -github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa h1:RC4maTWLKKwb7p1cnoygsbKIgNlJqSYBeAFON3Ar8As= -github.com/tommy-muehle/go-mnd v1.3.1-0.20200224220436-e6f9a994e8fa/go.mod h1:dSUh0FtTP8VhvkL1S+gUR1OKd9ZnSaozuI6r3m6wOig= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/ultraware/funlen v0.0.2 h1:Av96YVBwwNSe4MLR7iI/BIa3VyI7/djnto/pK3Uxbdo= -github.com/ultraware/funlen v0.0.2/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.4 h1:If7Va4cM03mpgrNH9k49/VOicWpGoG70XPBFFODYDsg= -github.com/ultraware/whitespace v0.0.4/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= -github.com/uudashr/gocognit v1.0.1 h1:MoG2fZ0b/Eo7NXoIwCVFLG5JED3qgQz5/NEE+rOsjPs= -github.com/uudashr/gocognit v1.0.1/go.mod h1:j44Ayx2KW4+oB6SWMv8KsmHzZrOInQav7D3cQMJ5JUM= -github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s= -github.com/valyala/quicktemplate v1.2.0/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/vishvananda/netlink v0.0.0-20171020171820-b2de5d10e38e/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netns v0.0.0-20171111001504-be1fbeda1936/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vmware/govmomi v0.20.1/go.mod h1:URlwyTFZX72RmxtxuaFL2Uj3fD1JTvZdx59bHWk6aFU= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xeipuuv/gojsonschema v1.1.0/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xenolf/lego v0.0.0-20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY= -github.com/xenolf/lego v0.3.2-0.20160613233155-a9d8cec0e656/go.mod h1:fwiGnfsIjG7OHPfOvgK7Y/Qo6+2Ox0iozjNTkZICKbY= -github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/handysort v0.0.0-20150421192137-fb3537ed64a1/go.mod h1:QcJo0QPSfTONNIgpN5RA8prR7fF8nkF6cTWTcNerRO8= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/go-metrics v0.0.0-20150112132944-c25f46c4b940/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/gorelic v0.0.6/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= +github.com/yvasiyarov/gorelic v0.0.7/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= +github.com/yvasiyarov/newrelic_platform_go v0.0.0-20160601141957-9c099fbc30e9/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.etcd.io/etcd v0.5.0-alpha.5.0.20200819165624-17cef6e3e9d5/go.mod h1:skWido08r9w6Lq/w70DO5XYIKMu4QFu1+4VsqLQuJy8= +go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= +go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.uber.org/atomic v0.0.0-20181018215023-8dc6146f7569/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v0.0.0-20180122172545-ddea229ff1df/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= +go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= +go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v0.0.0-20180814183419-67bc79d13d15/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= +go.uber.org/zap v1.8.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.14.1 h1:nYDKopTbvAPq/NrUVZwT15y2lpROBiLLyoRTbXOYWOo= -go.uber.org/zap v1.14.1/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= +go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152 h1:ZC1Xn5A1nlpSmQCIva4bZ3ob3lmhYIefc+GU+DLg1Ow= -golang.org/x/crypto v0.0.0-20191028145041-f83a4685e152/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d h1:9FCpayM9Egr1baVnV1SX0H87m+XB0B8S0hAMi99X/3U= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200128174031-69ecbb4d6d5d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee h1:WG0RUwxtNT4qqaXX3DPA8zHFNm/D9xaBpxzHt1WcA/E= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20180112015858-5ccada7d0a7b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/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-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/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-20181201002055-351d144fa1fc/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= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1009,42 +957,55 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271 h1:N66aaryRB3Ax92gH0v3hp1QYZ3zWWCCUR/j8Ifh45Ss= -golang.org/x/net v0.0.0-20191028085509-fe3aa8a45271/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191021144547-ec77196f6094/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6 h1:pE8b58s1HRDMi8RDc79m0HISf9D4TzseP40cEA6IGfs= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181004145325-8469e314837c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190124100055-b90733256f2e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190209173611-3b5209105503/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190228124157-a34e9553db1e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190310054646-10058d7d4faa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1054,249 +1015,361 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934 h1:u/E0NqCIWRDAo9WCFo6Ko49njPFDLSd3z+X1HgWDMpE= -golang.org/x/sys v0.0.0-20191028164358-195ce5e7f934/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82 h1:ywK/j/KkyTHcdyYSZNXGjMwgmDSfjglYZ3vStQ/gSCU= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4 h1:5/PjkGUjvEU5Gl6BxmvKRPpqo2uNMv4rcHBMwzk/st8= +golang.org/x/sys v0.0.0-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= +golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20171227012246-e19ae1496984/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20170824195420-5d2fd3ccab98/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e h1:EHBhcS0mlXEAVwNyO2dLfjToGsyY4j24pTs2ScHnX7s= +golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190213015956-f7e1b50d2251/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190706070813-72ffa07ba3db/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190719005602-e377ae9d6386/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191018212557-ed542cd5b28a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191030203535-5e247c9ad0a0/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200115044656-831fdb1e1868/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17 h1:a/Fd23DJvg1CaeDH0dYHahE+hCI0v9rFgxSNIThoUcM= -golang.org/x/tools v0.0.0-20200210192313-1ace956b0e17/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200228224639-71482053b885/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200327195553-82bb89366a1e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200422022333-3d57cf2e726e/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b h1:zSzQJAznWxAh9fZxiPy2FZo+ZZEYoYFYYDYdOrU7AaM= -golang.org/x/tools v0.0.0-20200426102838-f3a5411a4c3b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200504022951-6b6965ac5dd1 h1:C8rdnd6KieI73Z2Av0sS0t4kW+geIH/M8kNX8Hmvn9E= -golang.org/x/tools v0.0.0-20200504022951-6b6965ac5dd1/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 h1:BMFHd4OFnFtWX46Xj4DN6vvT1btiBxyq+s0orYBqcQY= -golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200403190813-44a64ad78b9b/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054 h1:HHeAlu5H9b71C+Fx0K+1dGgVFN1DM1/wz4aoGOA5qS8= +golang.org/x/tools v0.0.0-20200616133436-c1934b75d054/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5 h1:UaoXseXAWUJUcuJ2E2oczJdLxAJXL0lOmVaBl7kuk+I= +golang.org/x/tools v0.0.0-20200616195046-dc31b401abb5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201014231627-1610a49f37af/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 h1:/atklqdjdhuosWIl6AIbOeHJjicWYPqR9bpxqxYG2pA= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= +gomodules.xyz/jsonpatch/v2 v2.1.0 h1:Phva6wqu+xR//Njw6iorylFFgn/z547tw5Ne3HZPQ+k= +gomodules.xyz/jsonpatch/v2 v2.1.0/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= gomodules.xyz/jsonpatch/v3 v3.0.1/go.mod h1:CBhndykehEwTOlEfnsfJwvkFQbSN8YZFr9M+cIHAJto= gomodules.xyz/orderedmap v0.1.0/go.mod h1:g9/TPUCm1t2gwD3j3zfV8uylyYhVdCNSi+xCEIu7yTU= -gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0= -gonum.org/v1/gonum v0.0.0-20190710053202-4340aa3071a0/go.mod h1:03dgh78c4UvU1WksguQ/lvJQXbezKQGJSrwwRq5MraQ= -gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190128161407-8ac453e89fca/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20191028173616-919d9bdd9fe6/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191009194640-548a555dbc03/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200701001935-0939c5918c31/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200709232328-d8193ee9cc3e/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= +gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.0/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/gorp.v1 v1.7.2/go.mod h1:Wo3h+DBQZIxATwftsglhdD/62zRFPhGhTiu5jUJmCaw= gopkg.in/imdario/mergo.v0 v0.3.7/go.mod h1:9qPP6AGrlC1G2PTNXko614FwGZvorN7MiBU0Eppok+U= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473/go.mod h1:N1eN2tsCx0Ydtgjl4cqmbRCsY4/+z4cYDeqwZTk6zog= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v1 v1.1.2/go.mod h1:QpYS+a4WhS+DTlyQIi6Ka7MS3SuR9a055rgXNEe6EiA= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.1/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.0.0/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.1.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gotest.tools v2.1.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20190905181640-827449938966/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= -gotest.tools/gotestsum v0.3.5/go.mod h1:Mnf3e5FUzXbkCfynWBGOwLssY7gTQgCHObK9tMpAriY= -helm.sh/helm/v3 v3.0.0/go.mod h1:sI7B9yfvMgxtTPMWdk1jSKJ2aa59UyP9qhPydqW6mgo= -helm.sh/helm/v3 v3.0.1/go.mod h1:sI7B9yfvMgxtTPMWdk1jSKJ2aa59UyP9qhPydqW6mgo= -helm.sh/helm/v3 v3.1.0/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g= -helm.sh/helm/v3 v3.1.2/go.mod h1:WYsFJuMASa/4XUqLyv54s0U/f3mlAaRErGmyy4z921g= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +helm.sh/helm/v3 v3.4.1/go.mod h1:MeRlXlmCr5CWYKvqIPgXrSmcIXJpv7qcsKV3uTvcZSM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.2/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.0.0-20191016110408-35e52d86657a h1:VVUE9xTCXP6KUPMf92cQmN88orz600ebexcRRaBTepQ= -k8s.io/api v0.0.0-20191016110408-35e52d86657a/go.mod h1:/L5qH+AD540e7Cetbui1tuJeXdmNhO8jM6VkXeDdDhQ= -k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65 h1:kThoiqgMsSwBdMK/lPgjtYTsEjbUU9nXCA9DyU3feok= -k8s.io/apiextensions-apiserver v0.0.0-20191016113550-5357c4baaf65/go.mod h1:5BINdGqggRXXKnDgpwoJ7PyQH8f+Ypp02fvVNcIFy9s= -k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8 h1:Iieh/ZEgT3BWwbLD5qEKcY06jKuPEl6zC7gPSehoLw4= -k8s.io/apimachinery v0.0.0-20191004115801-a2eda9f80ab8/go.mod h1:llRdnznGEAqC3DcNm6yEj472xaFVfLM7hnYofMb12tQ= -k8s.io/apiserver v0.0.0-20191016112112-5190913f932d/go.mod h1:7OqfAolfWxUM/jJ/HBLyE+cdaWFBUoo5Q5pHgJVj2ws= -k8s.io/autoscaler v0.0.0-20190607113959-1b4f1855cb8e/go.mod h1:QEXezc9uKPT91dwqhSJq3GNI3B1HxFRQHiku9kmrsSA= -k8s.io/cli-runtime v0.0.0-20191016114015-74ad18325ed5 h1:8ZfMjkMBzcXEawLsYHg9lDM7aLEVso3NiVKfUTnN56A= -k8s.io/cli-runtime v0.0.0-20191016114015-74ad18325ed5/go.mod h1:sDl6WKSQkDM6zS1u9F49a0VooQ3ycYFBFLqd2jf2Xfo= -k8s.io/client-go v0.0.0-20191016111102-bec269661e48 h1:C2XVy2z0dV94q9hSSoCuTPp1KOG7IegvbdXuz9VGxoU= -k8s.io/client-go v0.0.0-20191016111102-bec269661e48/go.mod h1:hrwktSwYGI4JK+TJA3dMaFyyvHVi/aLarVHpbs8bgCU= -k8s.io/cloud-provider v0.0.0-20191016115326-20453efc2458/go.mod h1:O5SO5xcgxrjJV9EC9R/47RuBpbk5YX9URDBlg++FA5o= -k8s.io/cluster-bootstrap v0.0.0-20191016115129-c07a134afb42/go.mod h1:MzCL6kLExQuHruGaqibd8cugC8nw8QRxm3+lzR5l8SI= -k8s.io/code-generator v0.0.0-20181117043124-c2090bec4d9b h1:KH0fUlgdFZH8UMxJ/FDCYHpczfSQKefetq5NjL6BVF0= -k8s.io/code-generator v0.0.0-20181117043124-c2090bec4d9b/go.mod h1:MYiN+ZJZ9HkETbgVZdWw2AsuAi9PZ4V80cwfuf2axe8= -k8s.io/component-base v0.0.0-20191016111319-039242c015a9/go.mod h1:SuWowIgd/dtU/m/iv8OD9eOxp3QZBBhTIiWMsBQvKjI= -k8s.io/cri-api v0.0.0-20190828162817-608eb1dad4ac/go.mod h1:BvtUaNBr0fEpzb11OfrQiJLsLPtqbmulpo1fPwcpP6Q= -k8s.io/csi-translation-lib v0.0.0-20191016115521-756ffa5af0bd/go.mod h1:lf1VBseeLanBpSXD0N9tuPx1ylI8sA0j6f+rckCKiIk= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.18.0/go.mod h1:q2HRQkfDzHMBZL9l/y9rH63PkQl4vae0xRT+8prbrK8= +k8s.io/api v0.18.2/go.mod h1:SJCWI7OLzhZSvbY7U8zwNl9UA4o1fizoug34OV/2r78= +k8s.io/api v0.19.2 h1:q+/krnHWKsL7OBZg/rxnycsl9569Pud76UJ77MvKXms= +k8s.io/api v0.19.2/go.mod h1:IQpK0zFQ1xc5iNIQPqzgoOwuFugaYHK4iCknlAQP9nI= +k8s.io/api v0.19.3/go.mod h1:VF+5FT1B74Pw3KxMdKyinLo+zynBaMBiAfGMuldcNDs= +k8s.io/api v0.19.4 h1:I+1I4cgJYuCDgiLNjKx7SLmIbwgj9w7N7Zr5vSIdwpo= +k8s.io/api v0.19.4/go.mod h1:SbtJ2aHCItirzdJ36YslycFNzWADYH3tgOhvBEFtZAk= +k8s.io/api v0.20.2 h1:y/HR22XDZY3pniu9hIFDLpUCPq2w5eQ6aV/VFQ7uJMw= +k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8= +k8s.io/apiextensions-apiserver v0.18.2/go.mod h1:q3faSnRGmYimiocj6cHQ1I3WpLqmDgJFlKL37fC4ZvY= +k8s.io/apiextensions-apiserver v0.19.2 h1:oG84UwiDsVDu7dlsGQs5GySmQHCzMhknfhFExJMz9tA= +k8s.io/apiextensions-apiserver v0.19.2/go.mod h1:EYNjpqIAvNZe+svXVx9j4uBaVhTB4C94HkY3w058qcg= +k8s.io/apiextensions-apiserver v0.19.3 h1:WZxBypSHW4SdXHbdPTS/Jy7L2la6Niggs8BuU5o+avo= +k8s.io/apiextensions-apiserver v0.19.3/go.mod h1:igVEkrE9TzInc1tYE7qSqxaLg/rEAp6B5+k9Q7+IC8Q= +k8s.io/apiextensions-apiserver v0.19.4 h1:D9ak9T012tb3vcGFWYmbQuj9SCC8YM4zhA4XZqsAQC4= +k8s.io/apiextensions-apiserver v0.19.4/go.mod h1:B9rpH/nu4JBCtuUp3zTTk8DEjZUupZTBEec7/2zNRYw= +k8s.io/apimachinery v0.18.0/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.18.2/go.mod h1:9SnR/e11v5IbyPCGbvJViimtJ0SwHG4nfZFjU77ftcA= +k8s.io/apimachinery v0.19.2 h1:5Gy9vQpAGTKHPVOh5c4plE274X8D/6cuEiTO2zve7tc= +k8s.io/apimachinery v0.19.2/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.19.3/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.19.4 h1:+ZoddM7nbzrDCp0T3SWnyxqf8cbWPT2fkZImoyvHUG0= +k8s.io/apimachinery v0.19.4/go.mod h1:DnPGDnARWFvYa3pMHgSxtbZb7gpzzAZ1pTfaUNDVlmA= +k8s.io/apimachinery v0.20.2 h1:hFx6Sbt1oG0n6DZ+g4bFt5f6BoMkOjKWsQFu077M3Vg= +k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= +k8s.io/apiserver v0.18.2/go.mod h1:Xbh066NqrZO8cbsoenCwyDJ1OSi8Ag8I2lezeHxzwzw= +k8s.io/apiserver v0.19.2/go.mod h1:FreAq0bJ2vtZFj9Ago/X0oNGC51GfubKK/ViOKfVAOA= +k8s.io/apiserver v0.19.3/go.mod h1:bx6dMm+H6ifgKFpCQT/SAhPwhzoeIMlHIaibomUDec0= +k8s.io/apiserver v0.19.4/go.mod h1:X8WRHCR1UGZDd7HpV0QDc1h/6VbbpAeAGyxSh8yzZXw= +k8s.io/cli-runtime v0.18.0/go.mod h1:1eXfmBsIJosjn9LjEBUd2WVPoPAY9XGTqTFcPMIBsUQ= +k8s.io/cli-runtime v0.19.3/go.mod h1:q+l845i5/uWzcUpCrl+L4f3XLaJi8ZeLVQ/decwty0A= +k8s.io/cli-runtime v0.19.4/go.mod h1:m8G32dVbKOeaX1foGhleLEvNd6REvU7YnZyWn5//9rw= +k8s.io/cli-runtime v0.20.2 h1:W0/FHdbApnl9oB7xdG643c/Zaf7TZT+43I+zKxwqvhU= +k8s.io/cli-runtime v0.20.2/go.mod h1:FjH6uIZZZP3XmwrXWeeYCbgxcrD6YXxoAykBaWH0VdM= +k8s.io/client-go v0.18.0/go.mod h1:uQSYDYs4WhVZ9i6AIoEZuwUggLVEF64HOD37boKAtF8= +k8s.io/client-go v0.18.2/go.mod h1:Xcm5wVGXX9HAA2JJ2sSBUn3tCJ+4SVlCbl2MNNv+CIU= +k8s.io/client-go v0.19.2 h1:gMJuU3xJZs86L1oQ99R4EViAADUPMHHtS9jFshasHSc= +k8s.io/client-go v0.19.2/go.mod h1:S5wPhCqyDNAlzM9CnEdgTGV4OqhsW3jGO1UM1epwfJA= +k8s.io/client-go v0.19.3/go.mod h1:+eEMktZM+MG0KO+PTkci8xnbCZHvj9TqR6Q1XDUIJOM= +k8s.io/client-go v0.19.4 h1:85D3mDNoLF+xqpyE9Dh/OtrJDyJrSRKkHmDXIbEzer8= +k8s.io/client-go v0.19.4/go.mod h1:ZrEy7+wj9PjH5VMBCuu/BDlvtUAku0oVFk4MmnW9mWA= +k8s.io/client-go v0.20.2 h1:uuf+iIAbfnCSw8IGAv/Rg0giM+2bOzHLOsbbrwrdhNQ= +k8s.io/client-go v0.20.2/go.mod h1:kH5brqWqp7HDxUFKoEgiI4v8G1xzbe9giaCenUWJzgE= +k8s.io/code-generator v0.18.0/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.18.2/go.mod h1:+UHX5rSbxmR8kzS+FAv7um6dtYrZokQvjHpDSYRVkTc= +k8s.io/code-generator v0.19.2 h1:7uaWJll6fyCPj2j3sfNN1AiY2gZU1VFN2dFR2uoxGWI= +k8s.io/code-generator v0.19.2/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/code-generator v0.19.3/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/code-generator v0.19.4/go.mod h1:moqLn7w0t9cMs4+5CQyxnfA/HV8MF6aAVENF+WZZhgk= +k8s.io/component-base v0.18.0/go.mod h1:u3BCg0z1uskkzrnAKFzulmYaEpZF7XC9Pf/uFyb1v2c= +k8s.io/component-base v0.18.2/go.mod h1:kqLlMuhJNHQ9lz8Z7V5bxUUtjFZnrypArGl58gmDfUM= +k8s.io/component-base v0.19.2 h1:jW5Y9RcZTb79liEhW3XDVTW7MuvEGP0tQZnfSX6/+gs= +k8s.io/component-base v0.19.2/go.mod h1:g5LrsiTiabMLZ40AR6Hl45f088DevyGY+cCE2agEIVo= +k8s.io/component-base v0.19.3/go.mod h1:WhLWSIefQn8W8jxSLl5WNiR6z8oyMe/8Zywg7alOkRc= +k8s.io/component-base v0.19.4 h1:HobPRToQ8KJ9ubRju6PUAk9I5V1GNMJZ4PyWbiWA0uI= +k8s.io/component-base v0.19.4/go.mod h1:ZzuSLlsWhajIDEkKF73j64Gz/5o0AgON08FgRbEPI70= k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20191010091904-7fa3014cb28f h1:eW/6wVuHNZgQJmFesyAxu0cvj0WAHHUuGaLbPcmNY3Q= -k8s.io/gengo v0.0.0-20191010091904-7fa3014cb28f/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/heapster v1.2.0-beta.1/go.mod h1:h1uhptVXMwC8xtZBYsPXKVi8fpdlYkTs6k949KozGrM= -k8s.io/helm v2.16.1+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/helm v2.16.3+incompatible/go.mod h1:LZzlS4LQBHfciFOurYBFkCMTaZ0D1l+p0teMg7TSULI= -k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/gengo v0.0.0-20200114144118-36b2048a9120/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14 h1:t4L10Qfx/p7ASH3gXCdIUtPbbIuegCoUJf3TMSFekjw= +k8s.io/gengo v0.0.0-20200428234225-8167cfdcfc14/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= -k8s.io/kube-aggregator v0.0.0-20191016112429-9587704a8ad4/go.mod h1:+aW0UZgSXdTSHTIFnWnueEuXjOqerDUxGIw6Ygr+vYY= -k8s.io/kube-controller-manager v0.0.0-20191016114939-2b2b218dc1df/go.mod h1:WgrTcPKYAfNa9C0LV1UeK+XqfbSOUH1WGq/vX5UiW40= -k8s.io/kube-openapi v0.0.0-20180711000925-0cf8f7e6ed1d h1:mn2F9UzCk6KGa7M/d2ibLyRtBQm7n6QvbCjDe/cDWSg= -k8s.io/kube-openapi v0.0.0-20180711000925-0cf8f7e6ed1d/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= -k8s.io/kube-proxy v0.0.0-20191016114407-2e83b6f20229/go.mod h1:2Hxci1uzXO5ipP0h9n2+h18fvNkBTpYlckk5dOPu8zg= -k8s.io/kube-scheduler v0.0.0-20191016114748-65049c67a58b/go.mod h1:BgDUHHC5Wl0xcBUQgo2XEprE5nG5i9tlRR4iNgEFbL0= -k8s.io/kube-state-metrics v1.7.2 h1:6vdtgXrrRRMSgnyDmgua+qvgCYv954JNfxXAtDkeLVQ= -k8s.io/kube-state-metrics v1.7.2/go.mod h1:U2Y6DRi07sS85rmVPmBFlmv+2peBcL8IWGjM+IjYA/E= -k8s.io/kubectl v0.0.0-20191016120415-2ed914427d51/go.mod h1:gL826ZTIfD4vXTGlmzgTbliCAT9NGiqpCqK2aNYv5MQ= -k8s.io/kubelet v0.0.0-20191016114556-7841ed97f1b2/go.mod h1:SBvrtLbuePbJygVXGGCMtWKH07+qrN2dE1iMnteSG8E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0 h1:XRvcwJozkgZ1UQJmfMGpvRthQHOvihEhYtDfAaxMz/A= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.4.0 h1:7+X0fUguPyrKEC4WjH8iGDg3laWgMo5tMnRTIGTTxGQ= +k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6 h1:+WnxoVtG8TMiudHBSEtrVL1egv36TkkJm+bA8AxicmQ= +k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c= +k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= +k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU= +k8s.io/kubectl v0.19.3/go.mod h1:t5cscfrAuHUvEGNyNJjPKt+rGlaJzk8jrKYHXxEsANE= +k8s.io/kubectl v0.19.4/go.mod h1:XPmlu4DJEYgD83pvZFeKF8+MSvGnYGqunbFSrJsqHv0= k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/kubernetes v1.16.0/go.mod h1:nlP2zevWKRGKuaaVbKIwozU0Rjg9leVDXkL4YTtjmVs= -k8s.io/kubernetes v1.16.2/go.mod h1:SmhGgKfQ30imqjFVj8AI+iW+zSyFsswNErKYeTfgoH0= -k8s.io/legacy-cloud-providers v0.0.0-20191016115753-cf0698c3a16b/go.mod h1:tKW3pKqdRW8pMveUTpF5pJuCjQxg6a25iLo+Z9BXVH0= -k8s.io/metrics v0.0.0-20191016113814-3b1a734dba6e/go.mod h1:ve7/vMWeY5lEBkZf6Bt5TTbGS3b8wAxwGbdXAsufjRs= -k8s.io/repo-infra v0.0.0-20181204233714-00fe14e3d1a3/go.mod h1:+G1xBfZDfVFsm1Tj/HNCvg4QqWx8rJ2Fxpqr1rqp/gQ= -k8s.io/sample-apiserver v0.0.0-20191016112829-06bb3c9d77c9/go.mod h1:sXltHZrQa4jdKL14nOFRRUhhzpmbnRF0qGuAhRQbaxc= -k8s.io/utils v0.0.0-20190308190857-21c4ce38f2a7/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1 h1:+ySTxfHnfzZb9ys375PXNlLhkJPLKgHajBU0N62BDvE= -k8s.io/utils v0.0.0-20190801114015-581e00157fb1/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -k8s.io/utils v0.0.0-20191010214722-8d271d903fe4 h1:Gi+/O1saihwDqnlmC8Vhv1M5Sp4+rbOmK9TbsLn8ZEA= -k8s.io/utils v0.0.0-20191010214722-8d271d903fe4/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f h1:Cq7MalBHYACRd6EesksG1Q8EoIAKOsiZviGKbOLIej4= -mvdan.cc/unparam v0.0.0-20190720180237-d51796306d8f/go.mod h1:4G1h5nDURzA3bwVMZIVpwbkw+04kSxk3rAtzlimaUJw= -rsc.io/letsencrypt v0.0.1/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= +k8s.io/metrics v0.18.0/go.mod h1:8aYTW18koXqjLVKL7Ds05RPMX9ipJZI3mywYvBOxXd4= +k8s.io/metrics v0.19.3/go.mod h1:Eap/Lk1FiAIjkaArFuv41v+ph6dbDpVGwAg7jMI+4vg= +k8s.io/metrics v0.19.4/go.mod h1:a0gvAzrxQPw2ouBqnXI7X9qlggpPkKAFgWU/Py+KZiU= +k8s.io/utils v0.0.0-20200324210504-a9aa75ae1b89/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= +k8s.io/utils v0.0.0-20200729134348-d5654de09c73/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20200912215256-4140de9c8800 h1:9ZNvfPvVIEsp/T1ez4GQuzCcCTEQWhovSofhqR73A6g= +k8s.io/utils v0.0.0-20200912215256-4140de9c8800/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920 h1:CbnUZsM497iRC5QMVkHwyl8s2tB3g7yaSHkYPkpgelw= +k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/letsencrypt v0.0.3/go.mod h1:buyQKZ6IXrRnB7TdkHP0RyEybLx18HHyOSoTyoOLqNY= -sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg= -sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= -sigs.k8s.io/controller-tools v0.1.11-0.20190411181648-9d55346c2bde h1:ZkaHf5rNYzIB6CB82keKMQNv7xxkqT0ylOBdfJPfi+k= -sigs.k8s.io/controller-tools v0.1.11-0.20190411181648-9d55346c2bde/go.mod h1:ATWLRP3WGxuAN9HcT2LaKHReXIH+EZGzRuMHuxjXfhQ= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.7/go.mod h1:PHgbrJT7lCHcxMU+mDHEm+nx46H4zuuHZkDP6icnhu0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.9/go.mod h1:dzAXnQbTRyDlZPJX2SUPEqvnB+j7AJjtlox7PEwigU0= +sigs.k8s.io/controller-runtime v0.6.0/go.mod h1:CpYf5pdNY/B352A1TFLAS2JVSlnGQ5O2cftPHndTroo= +sigs.k8s.io/controller-runtime v0.7.0 h1:bU20IBBEPccWz5+zXpLnpVsgBYxqclaHu1pVDl/gEt8= +sigs.k8s.io/controller-runtime v0.7.0/go.mod h1:pJ3YBrJiAqMAZKi6UVGuE98ZrroV1p+pIhoHsMm9wdU= +sigs.k8s.io/controller-tools v0.3.0/go.mod h1:enhtKGfxZD1GFEoMgP8Fdbu+uKQ/cq1/WGJhdVChfvI= +sigs.k8s.io/controller-tools v0.4.1 h1:VkuV0MxlRPmRu5iTgBZU4UxUX2LiR99n3sdQGRxZF4w= +sigs.k8s.io/controller-tools v0.4.1/go.mod h1:G9rHdZMVlBDocIxGkK3jHLWqcTMNvveypYJwrvYKjWU= +sigs.k8s.io/kubebuilder/v2 v2.3.2-0.20201214213149-0a807f4e9428/go.mod h1:J/D/179LBZhQOhRvmMRNbje/Bk+PjbN0/fzUupmO7+U= sigs.k8s.io/kustomize v2.0.3+incompatible h1:JUufWFNlI44MdtnjUqVnvh29rR37PQFzPbLXqhyOyX0= sigs.k8s.io/kustomize v2.0.3+incompatible/go.mod h1:MkjgH3RdOWrievjo6c9T245dYlB5QeXV4WCbnt/PEpU= -sigs.k8s.io/structured-merge-diff v0.0.0-20190817042607-6149e4549fca/go.mod h1:IIgPezJWb76P0hotTxzDbWsMYB8APh18qZnxkomBpxA= -sigs.k8s.io/testing_frameworks v0.1.2/go.mod h1:ToQrwSC3s8Xf/lADdZp3Mktcql9CG0UAmdJG9th5i0w= -sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0-20200116222232-67a7b8c61874/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v3 v3.0.0/go.mod h1:PlARxl6Hbt/+BC80dRLi1qAmnMqwqDg62YvvVkZjemw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1 h1:YXTMot5Qz/X1iBRJhAt+vI+HVttY0WkSqqhKxQ0xVbA= +sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2 h1:YHQV7Dajm86OuqnIR6zAelnDWBRjo+YhYV9PmGrh1s8= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4 h1:JPJh2pk3+X4lXAkZIk2RuE/7/FoK9maXw+TNPJhVS/c= -sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= vbom.ml/util v0.0.0-20160121211510-db5cfe13f5cc/go.mod h1:so/NYdZXCz+E3ZpW0uAoCj6uzU2+8OWDFv/HxUSs7kI= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 00000000..45dbbbbc --- /dev/null +++ b/hack/boilerplate.go.txt @@ -0,0 +1,15 @@ +/* +Copyright 2021. + +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. +*/ \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 00000000..48b41a9f --- /dev/null +++ b/main.go @@ -0,0 +1,208 @@ +/* +Copyright 2021. + +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 main + +import ( + "flag" + "fmt" + "os" + r "runtime" + + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/controllers" + "github.com/jenkinsci/kubernetes-operator/pkg/client" + "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" + "github.com/jenkinsci/kubernetes-operator/pkg/constants" + "github.com/jenkinsci/kubernetes-operator/pkg/event" + "github.com/jenkinsci/kubernetes-operator/pkg/notifications" + e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" + "github.com/jenkinsci/kubernetes-operator/version" + + routev1 "github.com/openshift/api/route/v1" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/config" + + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) + // to ensure that exec-entrypoint and run can make use of them. + _ "k8s.io/client-go/plugin/pkg/client/auth" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + // +kubebuilder:scaffold:imports +) + +var ( + metricsHost = "0.0.0.0" + metricsPort int32 = 8383 + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) +var logger = logf.Log.WithName("cmd") + +func printInfo() { + logger.Info(fmt.Sprintf("Version: %s", version.Version)) + logger.Info(fmt.Sprintf("Git commit: %s", version.GitCommit)) + logger.Info(fmt.Sprintf("Go Version: %s", r.Version())) + logger.Info(fmt.Sprintf("Go OS/Arch: %s/%s", r.GOOS, r.GOARCH)) +} + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(v1alpha2.AddToScheme(scheme)) + utilruntime.Must(routev1.AddToScheme(scheme)) // FIXME optional + utilruntime.Must(corev1.AddToScheme(scheme)) + // +kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + + isRunningInCluster, err := resources.IsRunningInCluster() + if err != nil { + fatal(errors.Wrap(err, "failed to get watch namespace"), true) + } + + flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") + flag.BoolVar(&enableLeaderElection, "leader-elect", isRunningInCluster, "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + hostname := flag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") + port := flag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") + useNodePort := flag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") + debug := flag.Bool("debug", false, "Set log level to debug") + kubernetesClusterDomain := flag.String("cluster-domain", "cluster.local", "Use custom domain name instead of 'cluster.local'.") + //TODO fix logs + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + printInfo() + + namespace, found := os.LookupEnv("WATCH_NAMESPACE") + if !found { + var err error + fatal(errors.Wrap(err, "failed to get watch namespace, please set up WATCH_NAMESPACE environment variable"), *debug) + } + logger.Info(fmt.Sprintf("Watch namespace: %v", namespace)) + + //Config + // get a config to talk to the API server + cfg, err := config.GetConfig() + if err != nil { + fatal(errors.Wrap(err, "failed to get config"), *debug) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: fmt.Sprintf("%s:%d", metricsHost, metricsPort), + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "c674355f.jenkins.io", + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + // setup events + events, err := event.New(cfg, constants.OperatorName) + if err != nil { + fatal(errors.Wrap(err, "failed to setup events"), *debug) + } + + //Setup controller + clientSet, err := kubernetes.NewForConfig(cfg) + if err != nil { + fatal(errors.Wrap(err, "failed to create Kubernetes client set"), *debug) + } + + if resources.IsRouteAPIAvailable(clientSet) { + logger.Info("Route API found: Route creation will be performed") + } + notificationEvents := make(chan e.Event) + go notifications.Listen(notificationEvents, events, mgr.GetClient()) + + // validate jenkins API connection + jenkinsAPIConnectionSettings := client.JenkinsAPIConnectionSettings{Hostname: *hostname, Port: *port, UseNodePort: *useNodePort} + if err := jenkinsAPIConnectionSettings.Validate(); err != nil { + fatal(errors.Wrap(err, "invalid command line parameters"), *debug) + } + + // validate kubernetes cluster domain + if *kubernetesClusterDomain == "" { + fatal(errors.Wrap(err, "Kubernetes cluster domain can't be empty"), *debug) + } + + if err = (&controllers.JenkinsReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + JenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings, + ClientSet: *clientSet, + Config: *cfg, + NotificationEvents: ¬ificationEvents, + KubernetesClusterDomain: *kubernetesClusterDomain, + }).SetupWithManager(mgr); err != nil { + fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) + } + + if err = (&controllers.JenkinsImageReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) + } + + // +kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + logger.Info("Starting the Cmd.") + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} + +func fatal(err error, debug bool) { + if debug { + logger.Error(nil, fmt.Sprintf("%+v", err)) + } else { + logger.Error(nil, fmt.Sprintf("%s", err)) + } + os.Exit(-1) +} diff --git a/operator-sdk.mk b/operator-sdk.mk new file mode 100644 index 00000000..18567712 --- /dev/null +++ b/operator-sdk.mk @@ -0,0 +1,94 @@ +all: manager +#TODO for removing after Makefile fix +# Run tests +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin +test: generate fmt vet manifests + mkdir -p ${ENVTEST_ASSETS_DIR} + test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh + source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out + +# Build manager binary +manager: generate fmt vet + go build -o bin/manager main.go + +# Run against the configured Kubernetes cluster in ~/.kube/config +run: generate fmt vet manifests + go run ./main.go + +# Install CRDs into a cluster +install: manifests kustomize + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +# Uninstall CRDs from a cluster +uninstall: manifests kustomize + $(KUSTOMIZE) build config/crd | kubectl delete -f - + +# Deploy controller in the configured Kubernetes cluster in ~/.kube/config +deploy: manifests kustomize + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - + +# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config +undeploy: + $(KUSTOMIZE) build config/default | kubectl delete -f - + +# Generate manifests e.g. CRD, RBAC etc. +manifests: controller-gen + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +# Run go fmt against code +fmt: + go fmt ./... + +# Run go vet against code +vet: + go vet ./... + +# Generate code +generate: controller-gen + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +# Build the docker image +docker-build: test + docker build -t ${IMG} . + +# Push the docker image +docker-push: + docker push ${IMG} + +# Download controller-gen locally if necessary +CONTROLLER_GEN = $(shell pwd)/bin/controller-gen +controller-gen: + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) + +# Download kustomize locally if necessary +KUSTOMIZE = $(shell pwd)/bin/kustomize +kustomize: + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) + +# go-get-tool will 'go get' any package $2 and install it to $1. +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) +define go-get-tool +@[ -f $(1) ] || { \ +set -e ;\ +TMP_DIR=$$(mktemp -d) ;\ +cd $$TMP_DIR ;\ +go mod init tmp ;\ +echo "Downloading $(2)" ;\ +GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ +rm -rf $$TMP_DIR ;\ +} +endef + +# Generate bundle manifests and metadata, then validate generated files. +.PHONY: bundle +bundle: manifests kustomize + operator-sdk generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) + $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + operator-sdk bundle validate ./bundle + +# Build the bundle image. +.PHONY: bundle-build +bundle-build: + docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . diff --git a/pkg/apis/apis.go b/pkg/apis/apis.go deleted file mode 100644 index e2d7b6af..00000000 --- a/pkg/apis/apis.go +++ /dev/null @@ -1,24 +0,0 @@ -package apis - -import ( - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - routev1 "github.com/openshift/api/route/v1" - appsv1 "k8s.io/api/apps/v1" - - "k8s.io/apimachinery/pkg/runtime" -) - -// AddToSchemes may be used to add all resources defined in the project to a Scheme. -var AddToSchemes runtime.SchemeBuilder - -// AddToScheme adds all Resources to the Scheme. -func AddToScheme(s *runtime.Scheme) error { - return AddToSchemes.AddToScheme(s) -} - -func init() { - // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back - AddToSchemes = append(AddToSchemes, v1alpha2.SchemeBuilder.AddToScheme) - AddToSchemes = append(AddToSchemes, routev1.Install) - AddToSchemes = append(AddToSchemes, appsv1.AddToScheme) -} diff --git a/pkg/apis/jenkins/v1alpha2/doc.go b/pkg/apis/jenkins/v1alpha2/doc.go deleted file mode 100644 index 5f149aee..00000000 --- a/pkg/apis/jenkins/v1alpha2/doc.go +++ /dev/null @@ -1,4 +0,0 @@ -// Package v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group -// +k8s:deepcopy-gen=package,register -// +groupName=jenkins.io -package v1alpha2 diff --git a/pkg/apis/jenkins/v1alpha2/zz_generated.openapi.go b/pkg/apis/jenkins/v1alpha2/zz_generated.openapi.go deleted file mode 100644 index 77ada573..00000000 --- a/pkg/apis/jenkins/v1alpha2/zz_generated.openapi.go +++ /dev/null @@ -1,275 +0,0 @@ -// +build !ignore_autogenerated - -// This file was autogenerated by openapi-gen. Do not edit it manually! - -package v1alpha2 - -import ( - spec "github.com/go-openapi/spec" - common "k8s.io/kube-openapi/pkg/common" -) - -func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { - return map[string]common.OpenAPIDefinition{ - "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Jenkins": schema_pkg_apis_jenkins_v1alpha2_Jenkins(ref), - "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsSpec": schema_pkg_apis_jenkins_v1alpha2_JenkinsSpec(ref), - "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsStatus": schema_pkg_apis_jenkins_v1alpha2_JenkinsStatus(ref), - } -} - -func schema_pkg_apis_jenkins_v1alpha2_Jenkins(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Jenkins is the Schema for the jenkins API", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - 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/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - 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/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - "spec": { - SchemaProps: spec.SchemaProps{ - Description: "Spec defines the desired state of the Jenkins", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsSpec"), - }, - }, - "status": { - SchemaProps: spec.SchemaProps{ - Description: "Status defines the observed state of Jenkins", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsStatus"), - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsSpec", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, - } -} - -func schema_pkg_apis_jenkins_v1alpha2_JenkinsSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "JenkinsSpec defines the desired state of the Jenkins", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "master": { - SchemaProps: spec.SchemaProps{ - Description: "Master represents Jenkins master pod properties and Jenkins plugins. Every single change here requires a pod restart.", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsMaster"), - }, - }, - "seedJobs": { - SchemaProps: spec.SchemaProps{ - Description: "SeedJobs defines list of Jenkins Seed Job configurations More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.SeedJob"), - }, - }, - }, - }, - }, - "notifications": { - SchemaProps: spec.SchemaProps{ - Description: "Notifications defines list of a services which are used to inform about Jenkins status Can be used to integrate chat services like Slack, Microsoft Teams or Mailgun", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Notification"), - }, - }, - }, - }, - }, - "service": { - SchemaProps: spec.SchemaProps{ - Description: "Service is Kubernetes service of Jenkins master HTTP pod Defaults to : port: 8080 type: ClusterIP", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Service"), - }, - }, - "slaveService": { - SchemaProps: spec.SchemaProps{ - Description: "Service is Kubernetes service of Jenkins slave pods Defaults to : port: 50000 type: ClusterIP", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Service"), - }, - }, - "backup": { - SchemaProps: spec.SchemaProps{ - Description: "Backup defines configuration of Jenkins backup More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Backup"), - }, - }, - "restore": { - SchemaProps: spec.SchemaProps{ - Description: "Backup defines configuration of Jenkins backup restore More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Restore"), - }, - }, - "groovyScripts": { - SchemaProps: spec.SchemaProps{ - Description: "GroovyScripts defines configuration of Jenkins customization via groovy scripts", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.GroovyScripts"), - }, - }, - "configurationAsCode": { - SchemaProps: spec.SchemaProps{ - Description: "ConfigurationAsCode defines configuration of Jenkins customization via Configuration as Code Jenkins plugin", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.ConfigurationAsCode"), - }, - }, - "roles": { - SchemaProps: spec.SchemaProps{ - Description: "Roles defines list of extra RBAC roles for the Jenkins Master pod service account", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: ref("k8s.io/api/rbac/v1.RoleRef"), - }, - }, - }, - }, - }, - "serviceAccount": { - SchemaProps: spec.SchemaProps{ - Description: "ServiceAccount defines Jenkins master service account attributes", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.ServiceAccount"), - }, - }, - "jenkinsAPISettings": { - SchemaProps: spec.SchemaProps{ - Description: "JenkinsAPISettings defines configuration used by the operator to gain admin access to the Jenkins API", - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsAPISettings"), - }, - }, - }, - Required: []string{"master", "jenkinsAPISettings"}, - }, - }, - Dependencies: []string{ - "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Backup", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.ConfigurationAsCode", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.GroovyScripts", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsAPISettings", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsMaster", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Notification", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Restore", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.SeedJob", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Service", "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.ServiceAccount", "k8s.io/api/rbac/v1.RoleRef"}, - } -} - -func schema_pkg_apis_jenkins_v1alpha2_JenkinsStatus(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "JenkinsStatus defines the observed state of Jenkins", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "operatorVersion": { - SchemaProps: spec.SchemaProps{ - Description: "OperatorVersion is the operator version which manages this CR", - Type: []string{"string"}, - Format: "", - }, - }, - "provisionStartTime": { - SchemaProps: spec.SchemaProps{ - Description: "ProvisionStartTime is a time when Jenkins master pod has been created", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), - }, - }, - "baseConfigurationCompletedTime": { - SchemaProps: spec.SchemaProps{ - Description: "BaseConfigurationCompletedTime is a time when Jenkins base configuration phase has been completed", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), - }, - }, - "userConfigurationCompletedTime": { - SchemaProps: spec.SchemaProps{ - Description: "UserConfigurationCompletedTime is a time when Jenkins user configuration phase has been completed", - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Time"), - }, - }, - "restoredBackup": { - SchemaProps: spec.SchemaProps{ - Description: "RestoredBackup is the restored backup number after Jenkins master pod restart", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "lastBackup": { - SchemaProps: spec.SchemaProps{ - Description: "LastBackup is the latest backup number", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "pendingBackup": { - SchemaProps: spec.SchemaProps{ - Description: "PendingBackup is the pending backup number", - Type: []string{"integer"}, - Format: "int64", - }, - }, - "backupDoneBeforePodDeletion": { - SchemaProps: spec.SchemaProps{ - Description: "BackupDoneBeforePodDeletion tells if backup before pod deletion has been made", - Type: []string{"boolean"}, - Format: "", - }, - }, - "userAndPasswordHash": { - SchemaProps: spec.SchemaProps{ - Description: "UserAndPasswordHash is a SHA256 hash made from user and password", - Type: []string{"string"}, - Format: "", - }, - }, - "createdSeedJobs": { - SchemaProps: spec.SchemaProps{ - Description: "CreatedSeedJobs contains list of seed job id already created in Jenkins", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "", - }, - }, - }, - }, - }, - "appliedGroovyScripts": { - SchemaProps: spec.SchemaProps{ - Description: "AppliedGroovyScripts is a list with all applied groovy scripts in Jenkins by the operator", - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Ref: ref("github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.AppliedGroovyScript"), - }, - }, - }, - }, - }, - }, - }, - }, - Dependencies: []string{ - "github.com/kubernetes-operator/pkg/apis/jenkins/v1alpha2.AppliedGroovyScript", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, - } -} diff --git a/pkg/configuration/backuprestore/backuprestore.go b/pkg/configuration/backuprestore/backuprestore.go index 3805443d..1ee8b9d7 100644 --- a/pkg/configuration/backuprestore/backuprestore.go +++ b/pkg/configuration/backuprestore/backuprestore.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" @@ -132,7 +132,7 @@ func (bar *BackupAndRestore) Restore(jenkinsClient jenkinsclient.Jenkins) error bar.logger.V(log.VDebug).Info("Skipping restore backup") if jenkins.Status.PendingBackup == 0 { jenkins.Status.PendingBackup = 1 - return bar.Client.Update(context.TODO(), jenkins) + return bar.Client.Status().Update(context.TODO(), jenkins) } return nil } @@ -152,7 +152,7 @@ func (bar *BackupAndRestore) Restore(jenkinsClient jenkinsclient.Jenkins) error bar.logger.V(log.VDebug).Info("Skipping restore backup, get latest action returned -1") jenkins.Status.LastBackup = 0 jenkins.Status.PendingBackup = 1 - return bar.Client.Update(context.TODO(), jenkins) + return bar.Client.Status().Update(context.TODO(), jenkins) } backupNumber, err = strconv.ParseUint(backupNumberString, 10, 64) @@ -180,11 +180,25 @@ func (bar *BackupAndRestore) Restore(jenkinsClient jenkinsclient.Jenkins) error if err != nil { return err } - + //TODO fix me because we're doing two saves unatomically jenkins.Spec.Restore.RecoveryOnce = 0 + err = bar.Client.Update(context.TODO(), jenkins) + if err != nil { + return err + } + key := types.NamespacedName{ + Namespace: jenkins.Namespace, + Name: jenkins.Name, + } + err = bar.Client.Get(context.TODO(), key, jenkins) + if err != nil { + return err + } + bar.Configuration.Jenkins = jenkins + jenkins.Status.RestoredBackup = backupNumber jenkins.Status.PendingBackup = backupNumber + 1 - return bar.Client.Update(context.TODO(), jenkins) + return bar.Client.Status().Update(context.TODO(), jenkins) } return err @@ -216,7 +230,7 @@ func (bar *BackupAndRestore) Backup(setBackupDoneBeforePodDeletion bool) error { jenkins.Status.LastBackup = backupNumber jenkins.Status.PendingBackup = backupNumber jenkins.Status.BackupDoneBeforePodDeletion = setBackupDoneBeforePodDeletion - return bar.Client.Update(context.TODO(), jenkins) + return bar.Client.Status().Update(context.TODO(), jenkins) } return err @@ -234,7 +248,7 @@ func triggerBackup(ticker *time.Ticker, k8sClient k8s.Client, logger logr.Logger } if jenkins.Status.LastBackup == jenkins.Status.PendingBackup { jenkins.Status.PendingBackup++ - err = k8sClient.Update(context.TODO(), jenkins) + err = k8sClient.Status().Update(context.TODO(), jenkins) if err != nil { logger.V(log.VWarn).Info(fmt.Sprintf("backup trigger, error when updating CR: %s", err)) } diff --git a/pkg/configuration/base/configmap.go b/pkg/configuration/base/configmap.go index fb3185e1..236afe86 100644 --- a/pkg/configuration/base/configmap.go +++ b/pkg/configuration/base/configmap.go @@ -7,7 +7,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func (r *ReconcileJenkinsBaseConfiguration) createScriptsConfigMap(meta metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) createScriptsConfigMap(meta metav1.ObjectMeta) error { configMap, err := resources.NewScriptsConfigMap(meta, r.Configuration.Jenkins) if err != nil { return err @@ -15,7 +15,7 @@ func (r *ReconcileJenkinsBaseConfiguration) createScriptsConfigMap(meta metav1.O return stackerr.WithStack(r.CreateOrUpdateResource(configMap)) } -func (r *ReconcileJenkinsBaseConfiguration) createInitConfigurationConfigMap(meta metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) createInitConfigurationConfigMap(meta metav1.ObjectMeta) error { configMap, err := resources.NewInitConfigurationConfigMap(meta, r.Configuration.Jenkins) if err != nil { return err @@ -23,7 +23,7 @@ func (r *ReconcileJenkinsBaseConfiguration) createInitConfigurationConfigMap(met return stackerr.WithStack(r.CreateOrUpdateResource(configMap)) } -func (r *ReconcileJenkinsBaseConfiguration) createBaseConfigurationConfigMap(meta metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) createBaseConfigurationConfigMap(meta metav1.ObjectMeta) error { configMap, err := resources.NewBaseConfigurationConfigMap(meta, r.Configuration.Jenkins, r.KubernetesClusterDomain) if err != nil { return err diff --git a/pkg/configuration/base/container.go b/pkg/configuration/base/container.go index 7ca65856..99d7f436 100644 --- a/pkg/configuration/base/container.go +++ b/pkg/configuration/base/container.go @@ -7,7 +7,7 @@ import ( corev1 "k8s.io/api/core/v1" ) -func (r *ReconcileJenkinsBaseConfiguration) compareContainers(expected corev1.Container, actual corev1.Container) (messages []string, verbose []string) { +func (r *JenkinsBaseConfigurationReconciler) compareContainers(expected corev1.Container, actual corev1.Container) (messages []string, verbose []string) { if !reflect.DeepEqual(expected.Args, actual.Args) { messages = append(messages, "Arguments have changed") verbose = append(verbose, fmt.Sprintf("Arguments have changed to '%+v' in container '%s'", expected.Args, expected.Name)) diff --git a/pkg/configuration/base/deployment.go b/pkg/configuration/base/deployment.go index 0e63e1a1..533a3a61 100644 --- a/pkg/configuration/base/deployment.go +++ b/pkg/configuration/base/deployment.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" @@ -16,7 +16,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsDeployment(meta metav1.ObjectMeta) (reconcile.Result, error) { +func (r *JenkinsBaseConfigurationReconciler) ensureJenkinsDeployment(meta metav1.ObjectMeta) (reconcile.Result, error) { userAndPasswordHash, err := r.calculateUserAndPasswordHash() if err != nil { return reconcile.Result{}, err diff --git a/pkg/configuration/base/label.go b/pkg/configuration/base/label.go index 2f92a641..415d5ecb 100644 --- a/pkg/configuration/base/label.go +++ b/pkg/configuration/base/label.go @@ -3,7 +3,7 @@ package base import ( "context" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" stackerr "github.com/pkg/errors" @@ -11,7 +11,7 @@ import ( "k8s.io/apimachinery/pkg/types" ) -func (r *ReconcileJenkinsBaseConfiguration) addLabelForWatchesResources(customization v1alpha2.Customization) error { +func (r *JenkinsBaseConfigurationReconciler) addLabelForWatchesResources(customization v1alpha2.Customization) error { labelsForWatchedResources := resources.BuildLabelsForWatchedResources(*r.Configuration.Jenkins) if len(customization.Secret.Name) > 0 { diff --git a/pkg/configuration/base/plugin.go b/pkg/configuration/base/plugin.go index 4847f6c5..b396c3f2 100644 --- a/pkg/configuration/base/plugin.go +++ b/pkg/configuration/base/plugin.go @@ -3,16 +3,16 @@ package base import ( "fmt" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" - "github.com/jenkinsci/kubernetes-operator/pkg/log" - "github.com/jenkinsci/kubernetes-operator/pkg/plugins" "github.com/bndr/gojenkins" + "github.com/jenkinsci/kubernetes-operator/pkg/log" + "github.com/jenkinsci/kubernetes-operator/pkg/plugins" stackerr "github.com/pkg/errors" ) -func (r *ReconcileJenkinsBaseConfiguration) verifyPlugins(jenkinsClient jenkinsclient.Jenkins) (bool, error) { +func (r *JenkinsBaseConfigurationReconciler) verifyPlugins(jenkinsClient jenkinsclient.Jenkins) (bool, error) { allPluginsInJenkins, err := jenkinsClient.GetPlugins(fetchAllPlugins) if err != nil { return false, stackerr.WithStack(err) diff --git a/pkg/configuration/base/pod.go b/pkg/configuration/base/pod.go index 873f367d..579e085f 100644 --- a/pkg/configuration/base/pod.go +++ b/pkg/configuration/base/pod.go @@ -5,10 +5,9 @@ import ( "fmt" "reflect" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/backuprestore" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" - "github.com/jenkinsci/kubernetes-operator/pkg/log" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" "github.com/jenkinsci/kubernetes-operator/version" @@ -20,7 +19,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -func (r *ReconcileJenkinsBaseConfiguration) checkForPodRecreation(currentJenkinsMasterPod corev1.Pod, userAndPasswordHash string) reason.Reason { +func (r *JenkinsBaseConfigurationReconciler) checkForPodRecreation(currentJenkinsMasterPod corev1.Pod, userAndPasswordHash string) reason.Reason { var messages []string var verbose []string @@ -52,7 +51,14 @@ func (r *ReconcileJenkinsBaseConfiguration) checkForPodRecreation(currentJenkins r.Configuration.Jenkins.Status.OperatorVersion, version.Version)) } - if !reflect.DeepEqual(r.Configuration.Jenkins.Spec.Master.SecurityContext, currentJenkinsMasterPod.Spec.SecurityContext) { + //FIXME too hacky + var jenkinsSecurityContext *corev1.PodSecurityContext + if r.Configuration.Jenkins.Spec.Master.SecurityContext == nil { + jenkinsSecurityContext = &corev1.PodSecurityContext{} + } else { + jenkinsSecurityContext = r.Configuration.Jenkins.Spec.Master.SecurityContext + } + if !reflect.DeepEqual(jenkinsSecurityContext, currentJenkinsMasterPod.Spec.SecurityContext) { messages = append(messages, "Jenkins pod security context has changed") verbose = append(verbose, fmt.Sprintf("Jenkins pod security context has changed, actual '%+v' required '%+v'", currentJenkinsMasterPod.Spec.SecurityContext, r.Configuration.Jenkins.Spec.Master.SecurityContext)) @@ -140,7 +146,7 @@ func (r *ReconcileJenkinsBaseConfiguration) checkForPodRecreation(currentJenkins return reason.NewPodRestart(reason.OperatorSource, messages, verbose...) } -func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.ObjectMeta) (reconcile.Result, error) { +func (r *JenkinsBaseConfigurationReconciler) ensureJenkinsMasterPod(meta metav1.ObjectMeta) (reconcile.Result, error) { userAndPasswordHash, err := r.calculateUserAndPasswordHash() if err != nil { return reconcile.Result{}, err @@ -162,13 +168,6 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O return reconcile.Result{}, stackerr.WithStack(err) } - currentJenkinsMasterPod, err := r.waitUntilCreateJenkinsMasterPod() - if err == nil { - r.handleAdmissionControllerChanges(currentJenkinsMasterPod) - } else { - r.logger.V(log.VWarn).Info(fmt.Sprintf("waitUntilCreateJenkinsMasterPod has failed: %s", err)) - } - now := metav1.Now() r.Configuration.Jenkins.Status = v1alpha2.JenkinsStatus{ OperatorVersion: version.Version, @@ -177,7 +176,7 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureJenkinsMasterPod(meta metav1.O PendingBackup: r.Configuration.Jenkins.Status.LastBackup, UserAndPasswordHash: userAndPasswordHash, } - return reconcile.Result{Requeue: true}, r.Client.Update(context.TODO(), r.Configuration.Jenkins) + return reconcile.Result{Requeue: true}, r.Client.Status().Update(context.TODO(), r.Configuration.Jenkins) } else if err != nil && !apierrors.IsNotFound(err) { return reconcile.Result{}, stackerr.WithStack(err) } diff --git a/pkg/configuration/base/rbac.go b/pkg/configuration/base/rbac.go index e7525118..43a8db5c 100644 --- a/pkg/configuration/base/rbac.go +++ b/pkg/configuration/base/rbac.go @@ -6,14 +6,13 @@ import ( "strings" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" - stackerr "github.com/pkg/errors" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) -func (r *ReconcileJenkinsBaseConfiguration) createRBAC(meta metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) createRBAC(meta metav1.ObjectMeta) error { err := r.createServiceAccount(meta) if err != nil { return err @@ -38,7 +37,7 @@ func (r *ReconcileJenkinsBaseConfiguration) createRBAC(meta metav1.ObjectMeta) e return nil } -func (r *ReconcileJenkinsBaseConfiguration) ensureExtraRBAC(meta metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) ensureExtraRBAC(meta metav1.ObjectMeta) error { var err error var name string for _, roleRef := range r.Configuration.Jenkins.Spec.Roles { diff --git a/pkg/configuration/base/reconcile_test.go b/pkg/configuration/base/reconcile_test.go index fbf53341..556e7c54 100644 --- a/pkg/configuration/base/reconcile_test.go +++ b/pkg/configuration/base/reconcile_test.go @@ -4,13 +4,14 @@ import ( "context" "testing" - "github.com/bndr/gojenkins" - "github.com/golang/mock/gomock" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/log" + + "github.com/bndr/gojenkins" + "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" @@ -161,12 +162,12 @@ func TestCompareVolumes(t *testing.T) { }) } -func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { +func TestJenkinsBaseConfigurationReconciler_verifyPlugins(t *testing.T) { log.SetupLogger(true) t.Run("happy, empty base and user plugins", func(t *testing.T) { jenkins := &v1alpha2.Jenkins{} - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -194,7 +195,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { }, }, } - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -238,7 +239,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { }, }, } - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -275,7 +276,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { }, }, } - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -312,7 +313,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { }, }, } - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -349,7 +350,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { }, }, } - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -386,7 +387,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { }, }, } - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -415,7 +416,7 @@ func TestReconcileJenkinsBaseConfiguration_verifyPlugins(t *testing.T) { }, }, } - r := ReconcileJenkinsBaseConfiguration{ + r := JenkinsBaseConfigurationReconciler{ logger: log.Log, Configuration: configuration.Configuration{ Jenkins: jenkins, @@ -618,7 +619,7 @@ func TestEnsureExtraRBAC(t *testing.T) { t.Run("empty", func(t *testing.T) { // given - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) @@ -654,7 +655,7 @@ func TestEnsureExtraRBAC(t *testing.T) { clusterRoleKind := "ClusterRole" t.Run("one extra", func(t *testing.T) { // given - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) @@ -695,7 +696,7 @@ func TestEnsureExtraRBAC(t *testing.T) { }) t.Run("two extra", func(t *testing.T) { // given - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) @@ -742,8 +743,9 @@ func TestEnsureExtraRBAC(t *testing.T) { assert.Equal(t, jenkins.Spec.Roles[1], roleBindings.Items[2].RoleRef) }) t.Run("delete one extra", func(t *testing.T) { + t.Skip() //FIXME // given - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) diff --git a/pkg/configuration/base/reconcile.go b/pkg/configuration/base/reconciler.go similarity index 80% rename from pkg/configuration/base/reconcile.go rename to pkg/configuration/base/reconciler.go index 2f07becf..e314ad08 100644 --- a/pkg/configuration/base/reconcile.go +++ b/pkg/configuration/base/reconciler.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" @@ -33,15 +33,15 @@ const ( ) // ReconcileJenkinsBaseConfiguration defines values required for Jenkins base configuration. -type ReconcileJenkinsBaseConfiguration struct { +type JenkinsBaseConfigurationReconciler struct { configuration.Configuration logger logr.Logger jenkinsAPIConnectionSettings jenkinsclient.JenkinsAPIConnectionSettings } // New create structure which takes care of base configuration -func New(config configuration.Configuration, jenkinsAPIConnectionSettings jenkinsclient.JenkinsAPIConnectionSettings) *ReconcileJenkinsBaseConfiguration { - return &ReconcileJenkinsBaseConfiguration{ +func New(config configuration.Configuration, jenkinsAPIConnectionSettings jenkinsclient.JenkinsAPIConnectionSettings) *JenkinsBaseConfigurationReconciler { + return &JenkinsBaseConfigurationReconciler{ Configuration: config, logger: log.Log.WithValues("cr", config.Jenkins.Name), jenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings, @@ -49,7 +49,7 @@ func New(config configuration.Configuration, jenkinsAPIConnectionSettings jenkin } // Reconcile takes care of base configuration. -func (r *ReconcileJenkinsBaseConfiguration) Reconcile() (reconcile.Result, jenkinsclient.Jenkins, error) { +func (r *JenkinsBaseConfigurationReconciler) Reconcile() (reconcile.Result, jenkinsclient.Jenkins, error) { metaObject := resources.NewResourceObjectMeta(r.Configuration.Jenkins) // Create Necessary Resources @@ -134,7 +134,7 @@ func useDeploymentForJenkinsMaster(jenkins *v1alpha2.Jenkins) bool { return false } -func (r *ReconcileJenkinsBaseConfiguration) ensureResourcesRequiredForJenkinsPod(metaObject metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) ensureResourcesRequiredForJenkinsPod(metaObject metav1.ObjectMeta) error { if err := r.createOperatorCredentialsSecret(metaObject); err != nil { return err } @@ -197,7 +197,7 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureResourcesRequiredForJenkinsPod return nil } -func (r *ReconcileJenkinsBaseConfiguration) createOperatorCredentialsSecret(meta metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) createOperatorCredentialsSecret(meta metav1.ObjectMeta) error { found := &corev1.Secret{} err := r.Configuration.Client.Get(context.TODO(), types.NamespacedName{Name: resources.GetOperatorCredentialsSecretName(r.Configuration.Jenkins), Namespace: r.Configuration.Jenkins.ObjectMeta.Namespace}, found) @@ -214,7 +214,7 @@ func (r *ReconcileJenkinsBaseConfiguration) createOperatorCredentialsSecret(meta return stackerr.WithStack(r.UpdateResource(resources.NewOperatorCredentialsSecret(meta, r.Configuration.Jenkins))) } -func (r *ReconcileJenkinsBaseConfiguration) calculateUserAndPasswordHash() (string, error) { +func (r *JenkinsBaseConfigurationReconciler) calculateUserAndPasswordHash() (string, error) { credentialsSecret := &corev1.Secret{} err := r.Client.Get(context.TODO(), types.NamespacedName{Name: resources.GetOperatorCredentialsSecretName(r.Configuration.Jenkins), Namespace: r.Configuration.Jenkins.ObjectMeta.Namespace}, credentialsSecret) if err != nil { @@ -222,8 +222,14 @@ func (r *ReconcileJenkinsBaseConfiguration) calculateUserAndPasswordHash() (stri } hash := sha256.New() - hash.Write(credentialsSecret.Data[resources.OperatorCredentialsSecretUserNameKey]) - hash.Write(credentialsSecret.Data[resources.OperatorCredentialsSecretPasswordKey]) + _, err = hash.Write(credentialsSecret.Data[resources.OperatorCredentialsSecretUserNameKey]) + if err != nil { + return "", stackerr.WithStack(err) + } + _, err = hash.Write(credentialsSecret.Data[resources.OperatorCredentialsSecretPasswordKey]) + if err != nil { + return "", stackerr.WithStack(err) + } return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil } @@ -282,7 +288,7 @@ func CompareContainerVolumeMounts(expected corev1.Container, actual corev1.Conta } // compareVolumes returns true if Jenkins pod and Jenkins CR volumes are the same -func (r *ReconcileJenkinsBaseConfiguration) compareVolumes(actualPod corev1.Pod) bool { +func (r *JenkinsBaseConfigurationReconciler) compareVolumes(actualPod corev1.Pod) bool { var withoutServiceAccount []corev1.Volume for _, volume := range actualPod.Spec.Volumes { if !strings.HasPrefix(volume.Name, actualPod.Spec.ServiceAccountName) { @@ -296,7 +302,7 @@ func (r *ReconcileJenkinsBaseConfiguration) compareVolumes(actualPod corev1.Pod) ) } -func (r *ReconcileJenkinsBaseConfiguration) detectJenkinsMasterPodStartingIssues() (stopReconcileLoop bool, err error) { +func (r *JenkinsBaseConfigurationReconciler) detectJenkinsMasterPodStartingIssues() (stopReconcileLoop bool, err error) { jenkinsMasterPod, err := r.Configuration.GetJenkinsMasterPod() if err != nil { return false, err @@ -330,7 +336,7 @@ func (r *ReconcileJenkinsBaseConfiguration) detectJenkinsMasterPodStartingIssues return false, nil } -func (r *ReconcileJenkinsBaseConfiguration) filterEvents(source corev1.EventList, jenkinsMasterPod corev1.Pod) []string { +func (r *JenkinsBaseConfigurationReconciler) filterEvents(source corev1.EventList, jenkinsMasterPod corev1.Pod) []string { events := []string{} for _, eventItem := range source.Items { if r.Configuration.Jenkins.Status.ProvisionStartTime.UTC().After(eventItem.LastTimestamp.UTC()) { @@ -347,7 +353,7 @@ func (r *ReconcileJenkinsBaseConfiguration) filterEvents(source corev1.EventList return events } -func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins() (reconcile.Result, error) { +func (r *JenkinsBaseConfigurationReconciler) waitForJenkins() (reconcile.Result, error) { jenkinsMasterPod, err := r.Configuration.GetJenkinsMasterPod() if err != nil { return reconcile.Result{}, err @@ -388,7 +394,7 @@ func (r *ReconcileJenkinsBaseConfiguration) waitForJenkins() (reconcile.Result, return reconcile.Result{}, nil } -func (r *ReconcileJenkinsBaseConfiguration) ensureBaseConfiguration(jenkinsClient jenkinsclient.Jenkins) (reconcile.Result, error) { +func (r *JenkinsBaseConfigurationReconciler) ensureBaseConfiguration(jenkinsClient jenkinsclient.Jenkins) (reconcile.Result, error) { customization := v1alpha2.GroovyScripts{ Customization: v1alpha2.Customization{ Secret: v1alpha2.SecretRef{Name: ""}, @@ -403,30 +409,3 @@ func (r *ReconcileJenkinsBaseConfiguration) ensureBaseConfiguration(jenkinsClien }) return reconcile.Result{Requeue: requeue}, err } - -func (r *ReconcileJenkinsBaseConfiguration) waitUntilCreateJenkinsMasterPod() (currentJenkinsMasterPod *corev1.Pod, err error) { - currentJenkinsMasterPod, err = r.Configuration.GetJenkinsMasterPod() - for { - if err != nil && !apierrors.IsNotFound(err) { - return nil, stackerr.WithStack(err) - } else if err == nil { - break - } - currentJenkinsMasterPod, err = r.Configuration.GetJenkinsMasterPod() - time.Sleep(time.Millisecond * 10) - } - return -} - -func (r *ReconcileJenkinsBaseConfiguration) handleAdmissionControllerChanges(currentJenkinsMasterPod *corev1.Pod) { - if !reflect.DeepEqual(r.Configuration.Jenkins.Spec.Master.SecurityContext, currentJenkinsMasterPod.Spec.SecurityContext) { - r.Configuration.Jenkins.Spec.Master.SecurityContext = currentJenkinsMasterPod.Spec.SecurityContext - r.logger.Info(fmt.Sprintf("The Admission controller has changed the Jenkins master pod spec.securityContext, changing the Jenkinc CR spec.master.securityContext to '%+v'", currentJenkinsMasterPod.Spec.SecurityContext)) - } - for i, container := range r.Configuration.Jenkins.Spec.Master.Containers { - if !reflect.DeepEqual(container.SecurityContext, currentJenkinsMasterPod.Spec.Containers[i].SecurityContext) { - r.Configuration.Jenkins.Spec.Master.Containers[i].SecurityContext = currentJenkinsMasterPod.Spec.Containers[i].SecurityContext - r.logger.Info(fmt.Sprintf("The Admission controller has changed the securityContext, changing the Jenkins CR spec.master.containers[%s].securityContext to '+%v'", container.Name, currentJenkinsMasterPod.Spec.Containers[i].SecurityContext)) - } - } -} diff --git a/pkg/configuration/base/resources/base_configuration_configmap.go b/pkg/configuration/base/resources/base_configuration_configmap.go index ef16c63e..1e6e1983 100644 --- a/pkg/configuration/base/resources/base_configuration_configmap.go +++ b/pkg/configuration/base/resources/base_configuration_configmap.go @@ -3,7 +3,7 @@ package resources import ( "fmt" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" corev1 "k8s.io/api/core/v1" diff --git a/pkg/configuration/base/resources/builder.go b/pkg/configuration/base/resources/builder.go index 9ad27de8..181a6292 100644 --- a/pkg/configuration/base/resources/builder.go +++ b/pkg/configuration/base/resources/builder.go @@ -3,7 +3,8 @@ package resources import ( "fmt" - jenkinsv1alpha2 "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" logf "sigs.k8s.io/controller-runtime/pkg/log" @@ -32,7 +33,7 @@ RUN install-plugins.sh %s ` var log = logf.Log.WithName("controller_jenkinsimage") // NewBuilderPod returns a busybox pod with the same name/namespace as the cr. -func NewBuilderPod(cr *jenkinsv1alpha2.JenkinsImage) *corev1.Pod { +func NewBuilderPod(cr *v1alpha2.JenkinsImage) *corev1.Pod { name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, BuilderSuffix) args := []string{BuilderDockerfileArg, BuilderContextDirArg, BuilderPushArg, BuilderDigestFileArg} volumes := getVolumes(cr) @@ -59,7 +60,7 @@ func NewBuilderPod(cr *jenkinsv1alpha2.JenkinsImage) *corev1.Pod { } // NewDockerfileConfigMap returns a busybox pod with the same name/namespace as the cr. -func NewDockerfileConfigMap(cr *jenkinsv1alpha2.JenkinsImage) *corev1.ConfigMap { +func NewDockerfileConfigMap(cr *v1alpha2.JenkinsImage) *corev1.ConfigMap { dockerfileContent := fmt.Sprintf(DockerfileTemplate, getDefaultedBaseImage(cr), getPluginsList(cr)) name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileNameSuffix) data := map[string]string{DockerfileName: dockerfileContent} @@ -73,7 +74,7 @@ func NewDockerfileConfigMap(cr *jenkinsv1alpha2.JenkinsImage) *corev1.ConfigMap return dockerfile } -func getPluginsList(cr *jenkinsv1alpha2.JenkinsImage) string { +func getPluginsList(cr *v1alpha2.JenkinsImage) string { logger := log.WithName("jenkinsimage_getPluginsList") plugins := "" for _, v := range cr.Spec.Plugins { @@ -83,14 +84,14 @@ func getPluginsList(cr *jenkinsv1alpha2.JenkinsImage) string { return plugins } -func getDefaultedBaseImage(cr *jenkinsv1alpha2.JenkinsImage) string { +func getDefaultedBaseImage(cr *v1alpha2.JenkinsImage) string { if len(cr.Spec.BaseImage.Name) != 0 { return cr.Spec.BaseImage.Name } return JenkinsImageDefaultBaseImage } -func getVolumes(cr *jenkinsv1alpha2.JenkinsImage) []corev1.Volume { +func getVolumes(cr *v1alpha2.JenkinsImage) []corev1.Volume { name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileStorageSuffix) storage := corev1.Volume{ Name: name, @@ -112,7 +113,7 @@ func getVolumes(cr *jenkinsv1alpha2.JenkinsImage) []corev1.Volume { return volumes } -func getVolumesMounts(cr *jenkinsv1alpha2.JenkinsImage) []corev1.VolumeMount { +func getVolumesMounts(cr *v1alpha2.JenkinsImage) []corev1.VolumeMount { name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileStorageSuffix) storage := corev1.VolumeMount{ Name: name, diff --git a/pkg/configuration/base/resources/deployment.go b/pkg/configuration/base/resources/deployment.go index 00b46a47..8c8d2a44 100644 --- a/pkg/configuration/base/resources/deployment.go +++ b/pkg/configuration/base/resources/deployment.go @@ -3,7 +3,7 @@ package resources import ( "fmt" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" diff --git a/pkg/configuration/base/resources/init_configuration_configmap.go b/pkg/configuration/base/resources/init_configuration_configmap.go index 9a3f4db6..bebed79f 100644 --- a/pkg/configuration/base/resources/init_configuration_configmap.go +++ b/pkg/configuration/base/resources/init_configuration_configmap.go @@ -4,8 +4,8 @@ import ( "fmt" "text/template" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/internal/render" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" corev1 "k8s.io/api/core/v1" diff --git a/pkg/configuration/base/resources/meta.go b/pkg/configuration/base/resources/meta.go index 91b3d9e4..5e6af861 100644 --- a/pkg/configuration/base/resources/meta.go +++ b/pkg/configuration/base/resources/meta.go @@ -3,7 +3,7 @@ package resources import ( "fmt" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/configuration/base/resources/operator_credentials_secret.go b/pkg/configuration/base/resources/operator_credentials_secret.go index 34ea194d..babd8276 100644 --- a/pkg/configuration/base/resources/operator_credentials_secret.go +++ b/pkg/configuration/base/resources/operator_credentials_secret.go @@ -3,7 +3,7 @@ package resources import ( "fmt" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" corev1 "k8s.io/api/core/v1" diff --git a/pkg/configuration/base/resources/pod.go b/pkg/configuration/base/resources/pod.go index 265a75ab..9ee2ef0c 100644 --- a/pkg/configuration/base/resources/pod.go +++ b/pkg/configuration/base/resources/pod.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" corev1 "k8s.io/api/core/v1" diff --git a/pkg/configuration/base/resources/pod_test.go b/pkg/configuration/base/resources/pod_test.go index bb23047f..988c9fb7 100644 --- a/pkg/configuration/base/resources/pod_test.go +++ b/pkg/configuration/base/resources/pod_test.go @@ -3,7 +3,8 @@ package resources import ( "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/stretchr/testify/assert" ) diff --git a/pkg/configuration/base/resources/resources_test.go b/pkg/configuration/base/resources/resources_test.go index 64742f77..aa5f9c1b 100644 --- a/pkg/configuration/base/resources/resources_test.go +++ b/pkg/configuration/base/resources/resources_test.go @@ -3,9 +3,9 @@ package resources import ( "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - "github.com/stretchr/testify/assert" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/stretchr/testify/assert" corev1 "k8s.io/api/core/v1" ) diff --git a/pkg/configuration/base/resources/route.go b/pkg/configuration/base/resources/route.go index e5299279..dbcc5215 100644 --- a/pkg/configuration/base/resources/route.go +++ b/pkg/configuration/base/resources/route.go @@ -1,17 +1,15 @@ package resources import ( - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" routev1 "github.com/openshift/api/route/v1" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/discovery" "k8s.io/client-go/kubernetes" ) -//RouteKind the kind name for route -const RouteKind = "Route" - var isRouteAPIAvailable = false var routeAPIChecked = false diff --git a/pkg/configuration/base/resources/scripts_configmap.go b/pkg/configuration/base/resources/scripts_configmap.go index 14e0b36f..2014a42e 100644 --- a/pkg/configuration/base/resources/scripts_configmap.go +++ b/pkg/configuration/base/resources/scripts_configmap.go @@ -4,8 +4,8 @@ import ( "fmt" "text/template" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/internal/render" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" corev1 "k8s.io/api/core/v1" diff --git a/pkg/configuration/base/resources/service.go b/pkg/configuration/base/resources/service.go index 507b206b..92b07308 100644 --- a/pkg/configuration/base/resources/service.go +++ b/pkg/configuration/base/resources/service.go @@ -2,17 +2,16 @@ package resources import ( "fmt" + "net" + "os" + "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" - "github.com/operator-framework/operator-sdk/pkg/k8sutil" - stackerr "github.com/pkg/errors" + stackerr "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/intstr" - - "net" - "strings" ) //ServiceKind the kind name for Service @@ -71,7 +70,7 @@ func GetJenkinsSlavesServiceFQDN(jenkins *v1alpha2.Jenkins, kubernetesClusterDom // GetClusterDomain returns Kubernetes cluster domain, default to "cluster.local" func getClusterDomain(kubernetesClusterDomain string) (string, error) { - isRunningInCluster, err := isRunningInCluster() + isRunningInCluster, err := IsRunningInCluster() if !isRunningInCluster { return kubernetesClusterDomain, nil } @@ -93,13 +92,13 @@ func getClusterDomain(kubernetesClusterDomain string) (string, error) { return kubernetesClusterDomain, nil } -func isRunningInCluster() (bool, error) { - _, err := k8sutil.GetOperatorNamespace() - if err != nil { - if err == k8sutil.ErrNoNamespace || err == k8sutil.ErrRunLocal { - return false, nil - } - return false, stackerr.WithStack(err) +func IsRunningInCluster() (bool, error) { + const inClusterNamespacePath = "/var/run/secrets/kubernetes.io/serviceaccount/namespace" + _, err := os.Stat(inClusterNamespacePath) + if os.IsNotExist(err) { + return false, nil + } else if err == nil { + return true, nil } - return true, nil + return false, err } diff --git a/pkg/configuration/base/route.go b/pkg/configuration/base/route.go index 585b178f..f55a15d6 100644 --- a/pkg/configuration/base/route.go +++ b/pkg/configuration/base/route.go @@ -4,19 +4,19 @@ import ( "context" "fmt" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - "k8s.io/apimachinery/pkg/util/intstr" - + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" + routev1 "github.com/openshift/api/route/v1" stackerr "github.com/pkg/errors" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" ) // createRoute takes the ServiceName and Creates the Route based on it -func (r *ReconcileJenkinsBaseConfiguration) createRoute(meta metav1.ObjectMeta, serviceName string, config *v1alpha2.Jenkins) error { +func (r *JenkinsBaseConfigurationReconciler) createRoute(meta metav1.ObjectMeta, serviceName string, config *v1alpha2.Jenkins) error { route := routev1.Route{} name := fmt.Sprintf("jenkins-%s", config.ObjectMeta.Name) err := r.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: meta.Namespace}, &route) diff --git a/pkg/configuration/base/service.go b/pkg/configuration/base/service.go index 3c9f2130..7112e20d 100644 --- a/pkg/configuration/base/service.go +++ b/pkg/configuration/base/service.go @@ -3,7 +3,7 @@ package base import ( "context" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" stackerr "github.com/pkg/errors" @@ -13,7 +13,7 @@ import ( "k8s.io/apimachinery/pkg/types" ) -func (r *ReconcileJenkinsBaseConfiguration) createService(meta metav1.ObjectMeta, name string, config v1alpha2.Service, targetPort int32) error { +func (r *JenkinsBaseConfigurationReconciler) createService(meta metav1.ObjectMeta, name string, config v1alpha2.Service, targetPort int32) error { service := corev1.Service{} err := r.Client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: meta.Namespace}, &service) if err != nil && apierrors.IsNotFound(err) { diff --git a/pkg/configuration/base/serviceaccount.go b/pkg/configuration/base/serviceaccount.go index 2002a766..ea30d29f 100644 --- a/pkg/configuration/base/serviceaccount.go +++ b/pkg/configuration/base/serviceaccount.go @@ -14,7 +14,7 @@ import ( "k8s.io/apimachinery/pkg/types" ) -func (r *ReconcileJenkinsBaseConfiguration) createServiceAccount(meta metav1.ObjectMeta) error { +func (r *JenkinsBaseConfigurationReconciler) createServiceAccount(meta metav1.ObjectMeta) error { serviceAccount := &corev1.ServiceAccount{} err := r.Client.Get(context.TODO(), types.NamespacedName{Name: meta.Name, Namespace: meta.Namespace}, serviceAccount) annotations := r.Configuration.Jenkins.Spec.ServiceAccount.Annotations diff --git a/pkg/configuration/base/validate.go b/pkg/configuration/base/validate.go index 85911d79..ae3bf622 100644 --- a/pkg/configuration/base/validate.go +++ b/pkg/configuration/base/validate.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/constants" "github.com/jenkinsci/kubernetes-operator/pkg/plugins" @@ -23,7 +23,7 @@ var ( ) // Validate validates Jenkins CR Spec.master section -func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins) ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) Validate(jenkins *v1alpha2.Jenkins) ([]string, error) { var messages []string if msg := r.validateReservedVolumes(); len(msg) > 0 { @@ -70,7 +70,7 @@ func (r *ReconcileJenkinsBaseConfiguration) Validate(jenkins *v1alpha2.Jenkins) return messages, nil } -func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterContainerCommand() []string { +func (r *JenkinsBaseConfigurationReconciler) validateJenkinsMasterContainerCommand() []string { masterContainer := r.Configuration.GetJenkinsMasterContainer() if masterContainer == nil { return []string{} @@ -102,7 +102,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterContainerComman return []string{} } -func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecrets() ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) validateImagePullSecrets() ([]string, error) { var messages []string for _, sr := range r.Configuration.Jenkins.Spec.Master.ImagePullSecrets { msg, err := r.validateImagePullSecret(sr.Name) @@ -116,7 +116,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecrets() ([]string return messages, nil } -func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecret(secretName string) ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) validateImagePullSecret(secretName string) ([]string, error) { var messages []string secret := &corev1.Secret{} err := r.Client.Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: r.Configuration.Jenkins.ObjectMeta.Namespace}, secret) @@ -142,7 +142,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateImagePullSecret(secretName s return messages, nil } -func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) validateVolumes() ([]string, error) { var messages []string for _, volume := range r.Configuration.Jenkins.Spec.Master.Volumes { switch { @@ -170,7 +170,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateVolumes() ([]string, error) return messages, nil } -func (r *ReconcileJenkinsBaseConfiguration) validatePersistentVolumeClaim(volume corev1.Volume) ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) validatePersistentVolumeClaim(volume corev1.Volume) ([]string, error) { var messages []string pvc := &corev1.PersistentVolumeClaim{} @@ -184,7 +184,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validatePersistentVolumeClaim(volume return messages, nil } -func (r *ReconcileJenkinsBaseConfiguration) validateConfigMapVolume(volume corev1.Volume) ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) validateConfigMapVolume(volume corev1.Volume) ([]string, error) { var messages []string if volume.ConfigMap.Optional != nil && *volume.ConfigMap.Optional { return nil, nil @@ -201,7 +201,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateConfigMapVolume(volume corev return messages, nil } -func (r *ReconcileJenkinsBaseConfiguration) validateSecretVolume(volume corev1.Volume) ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) validateSecretVolume(volume corev1.Volume) ([]string, error) { var messages []string if volume.Secret.Optional != nil && *volume.Secret.Optional { return nil, nil @@ -218,7 +218,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateSecretVolume(volume corev1.V return messages, nil } -func (r *ReconcileJenkinsBaseConfiguration) validateReservedVolumes() []string { +func (r *JenkinsBaseConfigurationReconciler) validateReservedVolumes() []string { var messages []string for _, baseVolume := range resources.GetJenkinsMasterPodBaseVolumes(r.Configuration.Jenkins) { @@ -232,7 +232,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateReservedVolumes() []string { return messages } -func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha2.Container) []string { +func (r *JenkinsBaseConfigurationReconciler) validateContainer(container v1alpha2.Container) []string { var messages []string if container.Image == "" { messages = append(messages, "Image not set") @@ -253,7 +253,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateContainer(container v1alpha2 return messages } -func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(container v1alpha2.Container) []string { +func (r *JenkinsBaseConfigurationReconciler) validateContainerVolumeMounts(container v1alpha2.Container) []string { var messages []string allVolumes := append(resources.GetJenkinsMasterPodBaseVolumes(r.Configuration.Jenkins), r.Configuration.Jenkins.Spec.Master.Volumes...) @@ -277,7 +277,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateContainerVolumeMounts(contai return messages } -func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() []string { +func (r *JenkinsBaseConfigurationReconciler) validateJenkinsMasterPodEnvs() []string { var messages []string baseEnvs := resources.GetJenkinsMasterContainerBaseEnvs(r.Configuration.Jenkins) baseEnvNames := map[string]string{} @@ -316,7 +316,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validateJenkinsMasterPodEnvs() []str return messages } -func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins []plugins.Plugin, basePlugins, userPlugins []v1alpha2.Plugin) []string { +func (r *JenkinsBaseConfigurationReconciler) validatePlugins(requiredBasePlugins []plugins.Plugin, basePlugins, userPlugins []v1alpha2.Plugin) []string { var messages []string allPlugins := map[plugins.Plugin][]plugins.Plugin{} @@ -353,7 +353,7 @@ func (r *ReconcileJenkinsBaseConfiguration) validatePlugins(requiredBasePlugins return messages } -func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(requiredBasePlugins []plugins.Plugin, basePlugins []v1alpha2.Plugin) []string { +func (r *JenkinsBaseConfigurationReconciler) verifyBasePlugins(requiredBasePlugins []plugins.Plugin, basePlugins []v1alpha2.Plugin) []string { var messages []string for _, requiredBasePlugin := range requiredBasePlugins { @@ -372,7 +372,7 @@ func (r *ReconcileJenkinsBaseConfiguration) verifyBasePlugins(requiredBasePlugin return messages } -func (r *ReconcileJenkinsBaseConfiguration) validateCustomization(customization v1alpha2.Customization, name string) ([]string, error) { +func (r *JenkinsBaseConfigurationReconciler) validateCustomization(customization v1alpha2.Customization, name string) ([]string, error) { var messages []string if len(customization.Secret.Name) == 0 && len(customization.Configurations) == 0 { return nil, nil diff --git a/pkg/configuration/base/validate_test.go b/pkg/configuration/base/validate_test.go index 96eb0183..d585e545 100644 --- a/pkg/configuration/base/validate_test.go +++ b/pkg/configuration/base/validate_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" @@ -161,7 +161,7 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -187,7 +187,7 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Client: fakeClient, @@ -221,7 +221,7 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -257,7 +257,7 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -293,7 +293,7 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -329,7 +329,7 @@ func TestReconcileJenkinsBaseConfiguration_validateImagePullSecrets(t *testing.T }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -569,7 +569,7 @@ func TestValidateConfigMapVolume(t *testing.T) { }, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Jenkins: &v1alpha2.Jenkins{ObjectMeta: metav1.ObjectMeta{Name: "example"}}, @@ -596,7 +596,7 @@ func TestValidateConfigMapVolume(t *testing.T) { }, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), &configMap) assert.NoError(t, err) baseReconcileLoop := New(configuration.Configuration{ @@ -624,7 +624,7 @@ func TestValidateConfigMapVolume(t *testing.T) { }, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Client: fakeClient, Jenkins: jenkins, @@ -634,7 +634,7 @@ func TestValidateConfigMapVolume(t *testing.T) { assert.NoError(t, err) - assert.Equal(t, got, []string{"ConfigMap 'configmap-name' not found for volume '{volume-name {nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil &ConfigMapVolumeSource{LocalObjectReference:LocalObjectReference{Name:configmap-name,},Items:[]KeyToPath{},DefaultMode:nil,Optional:*false,} nil nil nil nil nil nil nil nil nil}}'"}) + assert.Equal(t, got, []string{"ConfigMap 'configmap-name' not found for volume '{volume-name {nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil &ConfigMapVolumeSource{LocalObjectReference:LocalObjectReference{Name:configmap-name,},Items:[]KeyToPath{},DefaultMode:nil,Optional:*false,} nil nil nil nil nil nil nil nil nil nil}}'"}) }) } @@ -649,7 +649,7 @@ func TestValidateSecretVolume(t *testing.T) { }, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Jenkins: &v1alpha2.Jenkins{ObjectMeta: metav1.ObjectMeta{Name: "example"}}, Client: fakeClient, @@ -673,7 +673,7 @@ func TestValidateSecretVolume(t *testing.T) { }, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), &secret) assert.NoError(t, err) baseReconcileLoop := New(configuration.Configuration{ @@ -699,7 +699,7 @@ func TestValidateSecretVolume(t *testing.T) { }, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Client: fakeClient, Jenkins: jenkins, @@ -708,7 +708,7 @@ func TestValidateSecretVolume(t *testing.T) { assert.NoError(t, err) - assert.Equal(t, got, []string{"Secret 'secret-name' not found for volume '{volume-name {nil nil nil nil nil &SecretVolumeSource{SecretName:secret-name,Items:[]KeyToPath{},DefaultMode:nil,Optional:*false,} nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil}}'"}) + assert.Equal(t, got, []string{"Secret 'secret-name' not found for volume '{volume-name {nil nil nil nil nil &SecretVolumeSource{SecretName:secret-name,Items:[]KeyToPath{},DefaultMode:nil,Optional:*false,} nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil nil}}'"}) }) } @@ -722,7 +722,7 @@ func TestValidateCustomization(t *testing.T) { } t.Run("empty", func(t *testing.T) { customization := v1alpha2.Customization{} - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Jenkins: jenkins, Client: fakeClient, @@ -744,7 +744,7 @@ func TestValidateCustomization(t *testing.T) { Namespace: defaultNamespace, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Jenkins: jenkins, Client: fakeClient, @@ -775,7 +775,7 @@ func TestValidateCustomization(t *testing.T) { Namespace: defaultNamespace, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Jenkins: jenkins, Client: fakeClient, @@ -802,7 +802,7 @@ func TestValidateCustomization(t *testing.T) { Namespace: defaultNamespace, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Jenkins: jenkins, Client: fakeClient, @@ -827,7 +827,7 @@ func TestValidateCustomization(t *testing.T) { Namespace: defaultNamespace, }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() baseReconcileLoop := New(configuration.Configuration{ Jenkins: jenkins, Client: fakeClient, diff --git a/pkg/configuration/configuration.go b/pkg/configuration/configuration.go index 53416583..1f651b83 100644 --- a/pkg/configuration/configuration.go +++ b/pkg/configuration/configuration.go @@ -3,10 +3,9 @@ package configuration import ( "bytes" "context" - "strings" "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" @@ -89,7 +88,7 @@ func (c *Configuration) IsJenkinsTerminating(pod corev1.Pod) bool { // CreateResource is creating kubernetes resource and references it to Jenkins CR func (c *Configuration) CreateResource(obj metav1.Object) error { - runtimeObj, ok := obj.(runtime.Object) + clientObj, ok := obj.(client.Object) if !ok { return stackerr.Errorf("is not a %T a runtime.Object", obj) } @@ -99,25 +98,25 @@ func (c *Configuration) CreateResource(obj metav1.Object) error { return stackerr.WithStack(err) } - return c.Client.Create(context.TODO(), runtimeObj) // don't wrap error + return c.Client.Create(context.TODO(), clientObj) // don't wrap error } // UpdateResource is updating kubernetes resource and references it to Jenkins CR. func (c *Configuration) UpdateResource(obj metav1.Object) error { - runtimeObj, ok := obj.(runtime.Object) + clientObj, ok := obj.(client.Object) if !ok { return stackerr.Errorf("is not a %T a runtime.Object", obj) } - // set Jenkins instance as the owner and controller, don't check error(can be already set) + // set Jenkins instance as the owner and controller, don't check errors(can be already set) _ = controllerutil.SetControllerReference(c.Jenkins, obj, c.Scheme) - return c.Client.Update(context.TODO(), runtimeObj) // don't wrap error + return c.Client.Update(context.TODO(), clientObj) // don't wrap error } // CreateOrUpdateResource is creating or updating kubernetes resource and references it to Jenkins CR. func (c *Configuration) CreateOrUpdateResource(obj metav1.Object) error { - runtimeObj, ok := obj.(runtime.Object) + clientObj, ok := obj.(client.Object) if !ok { return stackerr.Errorf("is not a %T a runtime.Object", obj) } @@ -125,7 +124,7 @@ func (c *Configuration) CreateOrUpdateResource(obj metav1.Object) error { // set Jenkins instance as the owner and controller, don't check error(can be already set) _ = controllerutil.SetControllerReference(c.Jenkins, obj, c.Scheme) - err := c.Client.Create(context.TODO(), runtimeObj) + err := c.Client.Create(context.TODO(), clientObj) if err != nil && errors.IsAlreadyExists(err) { return c.UpdateResource(obj) } else if err != nil && !errors.IsAlreadyExists(err) { @@ -202,7 +201,7 @@ func (c *Configuration) getJenkinsAPIUrl() (string, error) { return "", err } jenkinsURL := c.JenkinsAPIConnectionSettings.BuildJenkinsAPIUrl(service.Name, service.Namespace, service.Spec.Ports[0].Port, service.Spec.Ports[0].NodePort) - if prefix, ok := GetJenkinsOpts(*c.Jenkins)["prefix"]; ok { + if prefix, ok := resources.GetJenkinsOpts(*c.Jenkins)["prefix"]; ok { jenkinsURL += prefix } return jenkinsURL, nil @@ -278,29 +277,3 @@ func (c *Configuration) GetJenkinsClientFromSecret() (jenkinsclient.Jenkins, err string(credentialsSecret.Data[resources.OperatorCredentialsSecretUserNameKey]), string(credentialsSecret.Data[resources.OperatorCredentialsSecretTokenKey])) } - -// GetJenkinsOpts gets JENKINS_OPTS env parameter, parses it's values and returns it as a map` -func GetJenkinsOpts(jenkins v1alpha2.Jenkins) map[string]string { - envs := jenkins.Spec.Master.Containers[0].Env - jenkinsOpts := make(map[string]string) - - for key, value := range envs { - if value.Name == "JENKINS_OPTS" { - jenkinsOptsEnv := envs[key] - jenkinsOptsWithDashes := jenkinsOptsEnv.Value - if len(jenkinsOptsWithDashes) == 0 { - return nil - } - - jenkinsOptsWithEqOperators := strings.Split(jenkinsOptsWithDashes, " ") - - for _, vx := range jenkinsOptsWithEqOperators { - opt := strings.Split(vx, "=") - jenkinsOpts[strings.ReplaceAll(opt[0], "--", "")] = opt[1] - } - - return jenkinsOpts - } - } - return nil -} diff --git a/pkg/configuration/user/casc/casc.go b/pkg/configuration/user/casc/casc.go index 0d06e284..6e7bff4c 100644 --- a/pkg/configuration/user/casc/casc.go +++ b/pkg/configuration/user/casc/casc.go @@ -4,7 +4,7 @@ import ( "fmt" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/groovy" @@ -31,7 +31,7 @@ func New(jenkinsClient jenkinsclient.Jenkins, k8sClient k8s.Client, jenkins *v1a } // Ensure configures Jenkins with help Configuration as a code plugin -func (c *configurationAsCode) Ensure(jenkins *v1alpha2.Jenkins) (requeue bool, err error) { +func (c *configurationAsCode) Ensure(_ *v1alpha2.Jenkins) (requeue bool, err error) { requeue, err = c.groovyClient.WaitForSecretSynchronization(resources.ConfigurationAsCodeSecretVolumePath) if err != nil || requeue { return requeue, err diff --git a/pkg/configuration/user/reconcile.go b/pkg/configuration/user/reconcile.go index cb9710f8..d5671e2a 100644 --- a/pkg/configuration/user/reconcile.go +++ b/pkg/configuration/user/reconcile.go @@ -3,9 +3,7 @@ package user import ( "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - - "github.com/go-logr/logr" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/backuprestore" @@ -14,6 +12,8 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/configuration/user/seedjobs" "github.com/jenkinsci/kubernetes-operator/pkg/groovy" "github.com/jenkinsci/kubernetes-operator/pkg/log" + + "github.com/go-logr/logr" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) diff --git a/pkg/configuration/user/seedjobs/seedjobs.go b/pkg/configuration/user/seedjobs/seedjobs.go index 3707a786..cb9b3725 100644 --- a/pkg/configuration/user/seedjobs/seedjobs.go +++ b/pkg/configuration/user/seedjobs/seedjobs.go @@ -8,9 +8,8 @@ import ( "reflect" "text/template" - "github.com/go-logr/logr" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/internal/render" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" @@ -18,6 +17,8 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/groovy" "github.com/jenkinsci/kubernetes-operator/pkg/log" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" + + "github.com/go-logr/logr" stackerr "github.com/pkg/errors" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -228,7 +229,7 @@ func (s *seedJobs) EnsureSeedJobs(jenkins *v1alpha2.Jenkins) (done bool, err err seedJobIDs := s.getAllSeedJobIDs(*jenkins) if !reflect.DeepEqual(seedJobIDs, jenkins.Status.CreatedSeedJobs) { jenkins.Status.CreatedSeedJobs = seedJobIDs - return false, stackerr.WithStack(s.Client.Update(context.TODO(), jenkins)) + return false, stackerr.WithStack(s.Client.Status().Update(context.TODO(), jenkins)) } return true, nil @@ -267,8 +268,15 @@ func (s *seedJobs) createJobs(jenkins *v1alpha2.Jenkins) (requeue bool, err erro } hash := sha256.New() - hash.Write([]byte(groovyScript)) - hash.Write([]byte(credentialValue)) + _, err = hash.Write([]byte(groovyScript)) + if err != nil { + return true, err + } + _, err = hash.Write([]byte(credentialValue)) + if err != nil { + return true, err + } + requeue, err := groovyClient.EnsureSingle(seedJob.ID, fmt.Sprintf("%s.groovy", seedJob.ID), base64.URLEncoding.EncodeToString(hash.Sum(nil)), groovyScript) if err != nil { return true, err diff --git a/pkg/configuration/user/seedjobs/seedjobs_test.go b/pkg/configuration/user/seedjobs/seedjobs_test.go index d0fb3776..45582e72 100644 --- a/pkg/configuration/user/seedjobs/seedjobs_test.go +++ b/pkg/configuration/user/seedjobs/seedjobs_test.go @@ -4,9 +4,7 @@ import ( "context" "testing" - "k8s.io/apimachinery/pkg/api/errors" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" @@ -16,6 +14,7 @@ import ( "github.com/stretchr/testify/assert" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -74,7 +73,7 @@ func TestEnsureSeedJobs(t *testing.T) { defer ctrl.Finish() jenkinsClient := jenkinsclient.NewMockJenkins(ctrl) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) @@ -126,7 +125,7 @@ func TestEnsureSeedJobs(t *testing.T) { jenkins.Spec.SeedJobs = []v1alpha2.SeedJob{} jenkinsClient := jenkinsclient.NewMockJenkins(ctrl) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) @@ -174,7 +173,7 @@ func TestCreateAgent(t *testing.T) { jenkins := jenkinsCustomResource() jenkinsClient := jenkinsclient.NewMockJenkins(ctrl) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) assert.NoError(t, err) diff --git a/pkg/configuration/user/seedjobs/validate.go b/pkg/configuration/user/seedjobs/validate.go index 8e52529f..84c78ad4 100644 --- a/pkg/configuration/user/seedjobs/validate.go +++ b/pkg/configuration/user/seedjobs/validate.go @@ -7,7 +7,8 @@ import ( "fmt" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + stackerr "github.com/pkg/errors" "github.com/robfig/cron" v1 "k8s.io/api/core/v1" diff --git a/pkg/configuration/user/seedjobs/validate_test.go b/pkg/configuration/user/seedjobs/validate_test.go index 02bfbbe9..22007a3e 100644 --- a/pkg/configuration/user/seedjobs/validate_test.go +++ b/pkg/configuration/user/seedjobs/validate_test.go @@ -4,7 +4,7 @@ import ( "context" "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/stretchr/testify/assert" @@ -57,6 +57,10 @@ func TestValidateSeedJobs(t *testing.T) { Name: "deploy-keys", Namespace: "default", } + jenkinsObjectMeta := metav1.ObjectMeta{ + Name: "cr", + Namespace: "default", + } t.Run("Valid with public repository and without private key", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ Spec: v1alpha2.JenkinsSpec{ @@ -73,7 +77,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -102,7 +106,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -120,6 +124,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Valid with private key and secret", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -141,7 +146,7 @@ func TestValidateSeedJobs(t *testing.T) { PrivateKeySecretKey: []byte(fakePrivateKey), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -160,6 +165,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Invalid private key in secret", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -181,7 +187,7 @@ func TestValidateSeedJobs(t *testing.T) { PrivateKeySecretKey: []byte(fakeInvalidPrivateKey), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -201,6 +207,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Invalid with PrivateKey and empty Secret data", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -222,7 +229,7 @@ func TestValidateSeedJobs(t *testing.T) { PrivateKeySecretKey: []byte(""), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -256,7 +263,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -286,7 +293,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -316,7 +323,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -346,7 +353,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -364,6 +371,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Valid with username and password", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -385,7 +393,7 @@ func TestValidateSeedJobs(t *testing.T) { PasswordSecretKey: []byte("some-password"), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -404,6 +412,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Invalid with empty username", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -425,7 +434,7 @@ func TestValidateSeedJobs(t *testing.T) { PasswordSecretKey: []byte("some-password"), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -445,6 +454,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Invalid with empty password", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -466,7 +476,7 @@ func TestValidateSeedJobs(t *testing.T) { PasswordSecretKey: []byte(""), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -486,6 +496,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Invalid without username", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -506,7 +517,7 @@ func TestValidateSeedJobs(t *testing.T) { PasswordSecretKey: []byte("some-password"), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -526,6 +537,7 @@ func TestValidateSeedJobs(t *testing.T) { }) t.Run("Invalid without password", func(t *testing.T) { jenkins := v1alpha2.Jenkins{ + ObjectMeta: jenkinsObjectMeta, Spec: v1alpha2.JenkinsSpec{ SeedJobs: []v1alpha2.SeedJob{ { @@ -546,7 +558,7 @@ func TestValidateSeedJobs(t *testing.T) { UsernameSecretKey: []byte("some-username"), }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) @@ -581,7 +593,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -615,7 +627,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -647,7 +659,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -685,7 +697,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -717,7 +729,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, @@ -755,7 +767,7 @@ func TestValidateSeedJobs(t *testing.T) { }, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() config := configuration.Configuration{ Client: fakeClient, diff --git a/pkg/configuration/user/validate.go b/pkg/configuration/user/validate.go index af0b61ff..d004fa37 100644 --- a/pkg/configuration/user/validate.go +++ b/pkg/configuration/user/validate.go @@ -1,7 +1,7 @@ package user import ( - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/backuprestore" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/user/seedjobs" ) diff --git a/pkg/controller/jenkins/reconciler.go b/pkg/controller/jenkins/reconciler.go deleted file mode 100644 index 9fdd30e1..00000000 --- a/pkg/controller/jenkins/reconciler.go +++ /dev/null @@ -1,52 +0,0 @@ -package jenkins - -import ( - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" - "github.com/jenkinsci/kubernetes-operator/pkg/configuration" - "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/manager" - "sigs.k8s.io/controller-runtime/pkg/reconcile" -) - -// ReconcileJenkins reconciles a Jenkins object. -type ReconcileJenkins struct { - client client.Client - scheme *runtime.Scheme - jenkinsAPIConnectionSettings jenkinsclient.JenkinsAPIConnectionSettings - clientSet kubernetes.Clientset - config rest.Config - notificationEvents *chan event.Event - KubernetesClusterDomain string -} - -func (r *ReconcileJenkins) newReconcilierConfiguration(jenkins *v1alpha2.Jenkins) configuration.Configuration { - config := configuration.Configuration{ - Client: r.client, - ClientSet: r.clientSet, - Notifications: r.notificationEvents, - Jenkins: jenkins, - Scheme: r.scheme, - Config: &r.config, - JenkinsAPIConnectionSettings: r.jenkinsAPIConnectionSettings, - KubernetesClusterDomain: r.KubernetesClusterDomain, - } - return config -} - -// newReconciler returns a newReconcilierConfiguration reconcile.Reconciler. -func newReconciler(mgr manager.Manager, jenkinsAPIConnectionSettings jenkinsclient.JenkinsAPIConnectionSettings, kubernetesClusterDomain string, clientSet kubernetes.Clientset, config rest.Config, notificationEvents *chan event.Event) reconcile.Reconciler { - return &ReconcileJenkins{ - client: mgr.GetClient(), - scheme: mgr.GetScheme(), - jenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings, - clientSet: clientSet, - config: config, - notificationEvents: notificationEvents, - KubernetesClusterDomain: kubernetesClusterDomain, - } -} diff --git a/pkg/groovy/groovy.go b/pkg/groovy/groovy.go index 9244575b..ae4f2249 100644 --- a/pkg/groovy/groovy.go +++ b/pkg/groovy/groovy.go @@ -8,7 +8,7 @@ import ( "sort" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/log" @@ -77,7 +77,7 @@ func (g *Groovy) EnsureSingle(source, name, hash, groovyScript string) (requeue g.jenkins.Status.AppliedGroovyScripts = appliedGroovyScripts - return true, g.k8sClient.Update(context.TODO(), g.jenkins) + return true, g.k8sClient.Status().Update(context.TODO(), g.jenkins) } // WaitForSecretSynchronization runs groovy script which waits to synchronize secrets in pod by k8s @@ -96,7 +96,10 @@ func (g *Groovy) WaitForSecretSynchronization(secretsPath string) (requeue bool, for secretKey, secretValue := range secret.Data { toCalculate[secretKey] = string(secretValue) } - hash := g.calculateHash(toCalculate) + hash, err := g.calculateHash(toCalculate) + if err != nil { + return true, errors.WithStack(err) + } name := "synchronizing-secret.groovy" if g.isGroovyScriptAlreadyApplied(g.customization.Secret.Name, name, hash) { @@ -137,7 +140,10 @@ func (g *Groovy) Ensure(filter func(name string) bool, updateGroovyScript func(g continue } - hash := g.calculateCustomizationHash(*secret, name, groovyScript) + hash, err := g.calculateCustomizationHash(*secret, name, groovyScript) + if err != nil { + return true, errors.WithStack(err) + } if g.isGroovyScriptAlreadyApplied(configMap.Name, name, hash) { continue } @@ -153,7 +159,7 @@ func (g *Groovy) Ensure(filter func(name string) bool, updateGroovyScript func(g return false, nil } -func (g *Groovy) calculateCustomizationHash(secret corev1.Secret, key, groovyScript string) string { +func (g *Groovy) calculateCustomizationHash(secret corev1.Secret, key, groovyScript string) (string, error) { toCalculate := map[string]string{} for secretKey, secretValue := range secret.Data { toCalculate[secretKey] = string(secretValue) @@ -173,7 +179,7 @@ func (g *Groovy) isGroovyScriptAlreadyApplied(source, name, hash string) bool { return false } -func (g *Groovy) calculateHash(data map[string]string) string { +func (g *Groovy) calculateHash(data map[string]string) (string, error) { hash := sha256.New() var keys []string @@ -182,10 +188,16 @@ func (g *Groovy) calculateHash(data map[string]string) string { } sort.Strings(keys) for _, key := range keys { - hash.Write([]byte(key)) - hash.Write([]byte(data[key])) + _, err := hash.Write([]byte(key)) + if err != nil { + return "", err + } + _, err = hash.Write([]byte(data[key])) + if err != nil { + return "", err + } } - return base64.StdEncoding.EncodeToString(hash.Sum(nil)) + return base64.StdEncoding.EncodeToString(hash.Sum(nil)), nil } // AddSecretsLoaderToGroovyScript modify groovy scripts to load Kubernetes secrets into groovy map diff --git a/pkg/groovy/groovy_test.go b/pkg/groovy/groovy_test.go index 94fe20b1..8a97f637 100644 --- a/pkg/groovy/groovy_test.go +++ b/pkg/groovy/groovy_test.go @@ -6,11 +6,11 @@ import ( "strings" "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" + "github.com/jenkinsci/kubernetes-operator/pkg/log" "github.com/golang/mock/gomock" - "github.com/jenkinsci/kubernetes-operator/pkg/log" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -43,7 +43,7 @@ func TestGroovy_EnsureSingle(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) @@ -89,7 +89,7 @@ func TestGroovy_EnsureSingle(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) @@ -125,7 +125,7 @@ func TestGroovy_EnsureSingle(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) @@ -197,7 +197,7 @@ func TestGroovy_EnsureSingle(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) @@ -257,7 +257,7 @@ func TestGroovy_EnsureSingle(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) @@ -289,7 +289,7 @@ func TestGroovy_EnsureSingle(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) @@ -323,7 +323,7 @@ func TestGroovy_EnsureSingle(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) @@ -392,7 +392,7 @@ func TestGroovy_Ensure(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) err = fakeClient.Create(ctx, configMap) @@ -452,7 +452,7 @@ func TestGroovy_Ensure(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) err = fakeClient.Create(ctx, configMap) @@ -511,7 +511,7 @@ func TestGroovy_Ensure(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) err = fakeClient.Create(ctx, configMap) @@ -577,7 +577,7 @@ func TestGroovy_Ensure(t *testing.T) { } err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) require.NoError(t, err) - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() err = fakeClient.Create(ctx, jenkins) require.NoError(t, err) err = fakeClient.Create(ctx, secret) diff --git a/pkg/notifications/event/event.go b/pkg/notifications/event/event.go index 6dec551a..ed73389d 100644 --- a/pkg/notifications/event/event.go +++ b/pkg/notifications/event/event.go @@ -1,7 +1,7 @@ package event import ( - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" ) @@ -28,7 +28,4 @@ const ( // PhaseUser is user-defined configuration of Jenkins PhaseUser Phase = "user" - - // PhaseUnknown is untraceable type of configuration - PhaseUnknown Phase = "unknown" ) diff --git a/pkg/notifications/mailgun/mailgun.go b/pkg/notifications/mailgun/mailgun.go index 72729407..41ae54d5 100644 --- a/pkg/notifications/mailgun/mailgun.go +++ b/pkg/notifications/mailgun/mailgun.go @@ -6,7 +6,7 @@ import ( "strings" "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider" diff --git a/pkg/notifications/mailgun/mailgun_test.go b/pkg/notifications/mailgun/mailgun_test.go index b621a651..e7e48795 100644 --- a/pkg/notifications/mailgun/mailgun_test.go +++ b/pkg/notifications/mailgun/mailgun_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" @@ -24,7 +24,7 @@ func TestGenerateMessages(t *testing.T) { res := reason.NewUndefined(reason.KubernetesSource, []string{"test-string"}, "test-verbose") s := MailGun{ - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -65,7 +65,7 @@ func TestGenerateMessages(t *testing.T) { res := reason.NewUndefined(reason.KubernetesSource, []string{"nil"}, "nil") s := MailGun{ - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -106,7 +106,7 @@ func TestGenerateMessages(t *testing.T) { res := reason.NewUndefined(reason.KubernetesSource, []string{""}, "") s := MailGun{ - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -147,7 +147,7 @@ func TestGenerateMessages(t *testing.T) { res := reason.NewUndefined(reason.KubernetesSource, []string{"ąśćńółżź"}, "ąśćńółżź") s := MailGun{ - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, diff --git a/pkg/notifications/msteams/msteams.go b/pkg/notifications/msteams/msteams.go index 1048a4ee..036d8e50 100644 --- a/pkg/notifications/msteams/msteams.go +++ b/pkg/notifications/msteams/msteams.go @@ -8,7 +8,7 @@ import ( "net/http" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider" diff --git a/pkg/notifications/msteams/msteams_test.go b/pkg/notifications/msteams/msteams_test.go index f0e413e5..520af78f 100644 --- a/pkg/notifications/msteams/msteams_test.go +++ b/pkg/notifications/msteams/msteams_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" @@ -32,11 +32,11 @@ var ( ) func TestTeams_Send(t *testing.T) { - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() testURLSelectorKeyName := "test-url-selector" testSecretName := "test-secret" - event := event.Event{ + e := event.Event{ Jenkins: v1alpha2.Jenkins{ ObjectMeta: metav1.ObjectMeta{ Name: testCrName, @@ -66,27 +66,27 @@ func TestTeams_Send(t *testing.T) { t.Fatal(err) } - assert.Equal(t, message.Title, provider.NotificationTitle(event)) - assert.Equal(t, message.ThemeColor, teams.getStatusColor(event.Level)) + assert.Equal(t, message.Title, provider.NotificationTitle(e)) + assert.Equal(t, message.ThemeColor, teams.getStatusColor(e.Level)) mainSection := message.Sections[0] - reason := strings.Join(event.Reason.Short(), "\n\n - ") + reasonString := strings.Join(e.Reason.Short(), "\n\n - ") - assert.Equal(t, mainSection.Text, reason) + assert.Equal(t, mainSection.Text, reasonString) for _, fact := range mainSection.Facts { switch fact.Name { case provider.PhaseFieldName: - assert.Equal(t, fact.Value, string(event.Phase)) + assert.Equal(t, fact.Value, string(e.Phase)) case provider.CrNameFieldName: - assert.Equal(t, fact.Value, event.Jenkins.Name) + assert.Equal(t, fact.Value, e.Jenkins.Name) case provider.MessageFieldName: - assert.Equal(t, fact.Value, reason) + assert.Equal(t, fact.Value, reasonString) case provider.LevelFieldName: - assert.Equal(t, fact.Value, string(event.Level)) + assert.Equal(t, fact.Value, string(e.Level)) case provider.NamespaceFieldName: - assert.Equal(t, fact.Value, event.Jenkins.Namespace) + assert.Equal(t, fact.Value, e.Jenkins.Namespace) default: t.Errorf("Found unexpected '%+v' fact", fact) } @@ -109,7 +109,7 @@ func TestTeams_Send(t *testing.T) { err := fakeClient.Create(context.TODO(), secret) assert.NoError(t, err) - err = teams.Send(event) + err = teams.Send(e) assert.NoError(t, err) } @@ -123,7 +123,7 @@ func TestGenerateMessages(t *testing.T) { s := Teams{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -166,7 +166,7 @@ func TestGenerateMessages(t *testing.T) { s := Teams{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -209,7 +209,7 @@ func TestGenerateMessages(t *testing.T) { s := Teams{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -251,7 +251,7 @@ func TestGenerateMessages(t *testing.T) { res := reason.NewUndefined(reason.KubernetesSource, []string{"ąśćńółżź"}, "ąśćńółżź") s := Teams{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, diff --git a/pkg/notifications/provider/provider.go b/pkg/notifications/provider/provider.go index 36bf7b29..b9b188ff 100644 --- a/pkg/notifications/provider/provider.go +++ b/pkg/notifications/provider/provider.go @@ -1,7 +1,7 @@ package provider import ( - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" ) diff --git a/pkg/notifications/sender.go b/pkg/notifications/sender.go index 4e100c88..6e167b3c 100644 --- a/pkg/notifications/sender.go +++ b/pkg/notifications/sender.go @@ -6,7 +6,7 @@ import ( "reflect" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" k8sevent "github.com/jenkinsci/kubernetes-operator/pkg/event" "github.com/jenkinsci/kubernetes-operator/pkg/log" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" diff --git a/pkg/notifications/slack/slack.go b/pkg/notifications/slack/slack.go index eaeb9f9b..acda1847 100644 --- a/pkg/notifications/slack/slack.go +++ b/pkg/notifications/slack/slack.go @@ -7,7 +7,7 @@ import ( "net/http" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider" diff --git a/pkg/notifications/slack/slack_test.go b/pkg/notifications/slack/slack_test.go index 84e8fc59..e1734088 100644 --- a/pkg/notifications/slack/slack_test.go +++ b/pkg/notifications/slack/slack_test.go @@ -8,7 +8,7 @@ import ( "strings" "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" @@ -32,7 +32,7 @@ var ( ) func TestSlack_Send(t *testing.T) { - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() testURLSelectorKeyName := "test-url-selector" testSecretName := "test-secret" @@ -126,7 +126,7 @@ func TestGenerateMessage(t *testing.T) { s := Slack{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -172,7 +172,7 @@ func TestGenerateMessage(t *testing.T) { s := Slack{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -218,7 +218,7 @@ func TestGenerateMessage(t *testing.T) { s := Slack{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, @@ -264,7 +264,7 @@ func TestGenerateMessage(t *testing.T) { s := Slack{ httpClient: http.Client{}, - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ Verbose: true, }, diff --git a/pkg/notifications/smtp/smtp.go b/pkg/notifications/smtp/smtp.go index 156cc00b..418e8b3a 100644 --- a/pkg/notifications/smtp/smtp.go +++ b/pkg/notifications/smtp/smtp.go @@ -6,7 +6,7 @@ import ( "fmt" "strings" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/provider" diff --git a/pkg/notifications/smtp/smtp_test.go b/pkg/notifications/smtp/smtp_test.go index 2e49123c..fee09c92 100644 --- a/pkg/notifications/smtp/smtp_test.go +++ b/pkg/notifications/smtp/smtp_test.go @@ -12,7 +12,7 @@ import ( "testing" "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/pkg/notifications/reason" @@ -58,7 +58,7 @@ type testServer struct { } // Login handles a login command with username and password. -func (bkd *testServer) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) { +func (bkd *testServer) Login(_ *smtp.ConnectionState, username, password string) (smtp.Session, error) { if username != testSMTPUsername || password != testSMTPPassword { return nil, errors.New("invalid username or password") } @@ -66,7 +66,7 @@ func (bkd *testServer) Login(state *smtp.ConnectionState, username, password str } // AnonymousLogin requires clients to authenticate using SMTP AUTH before sending emails -func (bkd *testServer) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) { +func (bkd *testServer) AnonymousLogin(_ *smtp.ConnectionState) (smtp.Session, error) { return nil, smtp.ErrAuthRequired } @@ -128,7 +128,7 @@ func (s *testSession) Logout() error { } func TestSMTP_Send(t *testing.T) { - event := event.Event{ + e := event.Event{ Jenkins: v1alpha2.Jenkins{ ObjectMeta: metav1.ObjectMeta{ Name: testCrName, @@ -140,7 +140,7 @@ func TestSMTP_Send(t *testing.T) { Reason: testReason, } - fakeClient := fake.NewFakeClient() + fakeClient := fake.NewClientBuilder().Build() testUsernameSelectorKeyName := "test-username-selector" testPasswordSelectorKeyName := "test-password-selector" testSecretName := "test-secret" @@ -167,7 +167,7 @@ func TestSMTP_Send(t *testing.T) { }, }} - ts := &testServer{event: event} + ts := &testServer{event: e} // Create fake SMTP server @@ -205,7 +205,7 @@ func TestSMTP_Send(t *testing.T) { assert.NoError(t, err) }() - err = smtpClient.Send(event) + err = smtpClient.Send(e) assert.NoError(t, err) } @@ -231,7 +231,7 @@ func TestGenerateMessage(t *testing.T) { Reason: res, } s := SMTP{ - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ LoggingLevel: level, SMTP: &v1alpha2.SMTP{ @@ -264,7 +264,7 @@ func TestGenerateMessage(t *testing.T) { Reason: res, } s := SMTP{ - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ LoggingLevel: level, SMTP: &v1alpha2.SMTP{ @@ -297,7 +297,7 @@ func TestGenerateMessage(t *testing.T) { Reason: res, } s := SMTP{ - k8sClient: fake.NewFakeClient(), + k8sClient: fake.NewClientBuilder().Build(), config: v1alpha2.Notification{ LoggingLevel: level, SMTP: &v1alpha2.SMTP{ diff --git a/tools.go b/tools.go deleted file mode 100644 index 4da112f6..00000000 --- a/tools.go +++ /dev/null @@ -1,15 +0,0 @@ -// +build tools - -package tools - -import ( - // Code generators built at runtime. - _ "k8s.io/code-generator/cmd/client-gen" - _ "k8s.io/code-generator/cmd/conversion-gen" - _ "k8s.io/code-generator/cmd/deepcopy-gen" - _ "k8s.io/code-generator/cmd/informer-gen" - _ "k8s.io/code-generator/cmd/lister-gen" - _ "k8s.io/gengo/args" - _ "k8s.io/kube-openapi/cmd/openapi-gen" - _ "sigs.k8s.io/controller-tools/pkg/crd/generator" -) diff --git a/variables.mk b/variables.mk new file mode 100644 index 00000000..6648c14b --- /dev/null +++ b/variables.mk @@ -0,0 +1,96 @@ +# Set POSIX sh for maximum interoperability +SHELL := /bin/sh +PATH := $(GOPATH)/bin:$(PATH) + +OSFLAG := +ifeq ($(OS),Windows_NT) + OSFLAG = WIN32 +else + UNAME_S := $(shell uname -s) + ifeq ($(UNAME_S),Linux) + OSFLAG = LINUX + endif + ifeq ($(UNAME_S),Darwin) + OSFLAG = OSX + endif +endif + +include config.base.env + +# Import config +# You can change the default config with `make config="config_special.env" build` +config ?= config.minikube.env +include $(config) + +# Set an output prefix, which is the local directory if not specified +PREFIX?=$(shell pwd) + +VERSION := $(shell cat VERSION.txt) +GITCOMMIT := $(shell git rev-parse --short HEAD) +GITBRANCH := $(shell git rev-parse --abbrev-ref HEAD) +GITUNTRACKEDCHANGES := $(shell git status --porcelain --untracked-files=no) +GITIGNOREDBUTTRACKEDCHANGES := $(shell git ls-files -i --exclude-standard) +ifneq ($(GITUNTRACKEDCHANGES),) + GITCOMMIT := $(GITCOMMIT)-dirty +endif +ifneq ($(GITIGNOREDBUTTRACKEDCHANGES),) + GITCOMMIT := $(GITCOMMIT)-dirty +endif + +VERSION_TAG := $(VERSION) +LATEST_TAG := latest +BUILD_TAG := $(GITBRANCH)-$(GITCOMMIT) + +BUILD_PATH := ./ + +# CONTAINER_RUNTIME_COMMAND is Container Runtime - it could be docker or podman +CONTAINER_RUNTIME_COMMAND := docker + +# Set any default go build tags +BUILDTAGS := + +# Set the build dir, where built cross-compiled binaries will be output +BUILDDIR := ${PREFIX}/cross + +CTIMEVAR=-X $(PKG)/version.GitCommit=$(GITCOMMIT) -X $(PKG)/version.Version=$(VERSION) +GO_LDFLAGS=-ldflags "-w $(CTIMEVAR)" +GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static" + +# List the GOOS and GOARCH to build +GOOSARCHES = linux/amd64 + +PACKAGES = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor) +PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e) + +# Run all the e2e tests by default +E2E_TEST_SELECTOR ?= .* + +JENKINS_API_HOSTNAME := $(shell $(JENKINS_API_HOSTNAME_COMMAND) 2> /dev/null || echo "" ) +OPERATOR_ARGS ?= --jenkins-api-hostname=$(JENKINS_API_HOSTNAME) --jenkins-api-port=$(JENKINS_API_PORT) --jenkins-api-use-nodeport=$(JENKINS_API_USE_NODEPORT) --cluster-domain=$(CLUSTER_DOMAIN) $(OPERATOR_EXTRA_ARGS) + +.DEFAULT_GOAL := help + +##################### FROM OPERATOR SDK ######################## + +# Default bundle image tag +BUNDLE_IMG ?= controller-bundle:$(VERSION) +# Options for 'bundle-build' +ifneq ($(origin CHANNELS), undefined) +BUNDLE_CHANNELS := --channels=$(CHANNELS) +endif +ifneq ($(origin DEFAULT_CHANNEL), undefined) +BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) +endif +BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) + +# Image URL to use all building/pushing image targets +IMG ?= controller:latest +# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) +CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" + +# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) +ifeq (,$(shell go env GOBIN)) +GOBIN=$(shell go env GOPATH)/bin +else +GOBIN=$(shell go env GOBIN) +endif \ No newline at end of file From b6bf47b949e79130d1456c1bbfcda8cd59fabab5 Mon Sep 17 00:00:00 2001 From: Sylwia Brant Date: Wed, 20 Jan 2021 15:46:39 +0100 Subject: [PATCH 02/15] Added base for envtests --- Makefile | 11 +- controllers/jenkins.go | 170 +++++++++++++++++++++++++ controllers/jenkins_controller_test.go | 40 ++++++ controllers/suite_test.go | 115 +++++++++++++++++ go.mod | 3 +- operator-sdk.mk | 2 +- 6 files changed, 337 insertions(+), 4 deletions(-) create mode 100644 controllers/jenkins.go create mode 100644 controllers/jenkins_controller_test.go create mode 100644 controllers/suite_test.go diff --git a/Makefile b/Makefile index 03a8c6df..9a1e36f4 100644 --- a/Makefile +++ b/Makefile @@ -173,7 +173,7 @@ install: ## Installs the executable .PHONY: run run: export WATCH_NAMESPACE = $(NAMESPACE) run: export OPERATOR_NAME = $(NAME) -run: fmt vet manifests install build ## Run the executable, you can use EXTRA_ARGS +run: fmt vet manifests install-crds build ## Run the executable, you can use EXTRA_ARGS @echo "+ $@" ifeq ($(KUBERNETES_PROVIDER),minikube) kubectl config use-context $(KUBECTL_CONTEXT) @@ -422,7 +422,7 @@ generate-docs: ## Re-generate docs directory from the website directory ##################### FROM OPERATOR SDK ######################## #TODO rename # Install CRDs into a cluster -install: manifests kustomize +install-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - # Uninstall CRDs from a cluster @@ -473,3 +473,10 @@ bundle: manifests kustomize .PHONY: bundle-build bundle-build: docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + +#FIXME temporary target for running tests (test used above for go test) +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin +testing: generate fmt vet manifests + mkdir -p ${ENVTEST_ASSETS_DIR} + test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh + source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out \ No newline at end of file diff --git a/controllers/jenkins.go b/controllers/jenkins.go new file mode 100644 index 00000000..af11d172 --- /dev/null +++ b/controllers/jenkins.go @@ -0,0 +1,170 @@ +package controllers + +import ( + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" + "github.com/jenkinsci/kubernetes-operator/pkg/constants" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" +) + +const ( + userConfigurationConfigMapName = "user-config" + userConfigurationSecretName = "user-secret" +) + +type seedJobConfig struct { + v1alpha2.SeedJob + JobNames []string `json:"jobNames,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + PrivateKey string `json:"privateKey,omitempty"` +} + +var ( + jenkinsCRName = "jenkins-example" + namespace = "default" + priorityClassName = "" + + mySeedJob = seedJobConfig{ + SeedJob: v1alpha2.SeedJob{ + ID: "jenkins-operator", + CredentialID: "jenkins-operator", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/*.jenkins", + Description: "Jenkins Operator repository", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + PollSCM: "1 1 1 1 1", + UnstableOnDeprecation: true, + BuildPeriodically: "1 1 1 1 1", + FailOnMissingPlugin: true, + IgnoreMissingFiles: true, + //AdditionalClasspath: can fail with the seed job agent + GitHubPushTrigger: true, + }, + } + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{ + { + Name: userConfigurationConfigMapName, + }, + }, + Secret: v1alpha2.SecretRef{ + Name: userConfigurationSecretName, + }, + }, + } + + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{ + { + Name: userConfigurationConfigMapName, + }, + }, + Secret: v1alpha2.SecretRef{ + Name: userConfigurationSecretName, + }, + }, + } +) + +func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { + var seedJobs []v1alpha2.SeedJob + if seedJob != nil { + seedJobs = append(seedJobs, *seedJob...) + } + + jenkins := &v1alpha2.Jenkins{ + TypeMeta: v1alpha2.JenkinsTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: v1alpha2.JenkinsSpec{ + GroovyScripts: groovyScripts, + ConfigurationAsCode: casc, + Master: v1alpha2.JenkinsMaster{ + Annotations: map[string]string{"test": "label"}, + Containers: []v1alpha2.Container{ + { + Name: resources.JenkinsMasterContainerName, + Env: []corev1.EnvVar{ + { + Name: "TEST_ENV", + Value: "test_env_value", + }, + }, + ReadinessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/login", + Port: intstr.FromString("http"), + Scheme: corev1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: int32(80), + TimeoutSeconds: int32(4), + FailureThreshold: int32(10), + }, + LivenessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/login", + Port: intstr.FromString("http"), + Scheme: corev1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: int32(80), + TimeoutSeconds: int32(4), + FailureThreshold: int32(10), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins-cache", + MountPath: "/usr/share/jenkins/ref/plugins", + }, + }, + }, + { + Name: "envoyproxy", + Image: "envoyproxy/envoy-alpine:v1.14.1", + }, + }, + Plugins: []v1alpha2.Plugin{ + {Name: "audit-trail", Version: "3.7"}, + {Name: "simple-theme-plugin", Version: "0.6"}, + {Name: "github", Version: "1.32.0"}, + {Name: "devoptics", Version: "1.1905", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1905/devoptics.hpi"}, + }, + PriorityClassName: priorityClassName, + NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, + Volumes: []corev1.Volume{ + { + Name: "plugins-cache", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + SeedJobs: seedJobs, + Service: v1alpha2.Service{ + Type: corev1.ServiceTypeNodePort, + Port: constants.DefaultHTTPPortInt32, + }, + }, + } + jenkins.Spec.Roles = []rbacv1.RoleRef{ + { + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: resources.GetResourceName(jenkins), + }, + } + return jenkins +} diff --git a/controllers/jenkins_controller_test.go b/controllers/jenkins_controller_test.go new file mode 100644 index 00000000..f0c25f5c --- /dev/null +++ b/controllers/jenkins_controller_test.go @@ -0,0 +1,40 @@ +/* +Copyright 2021. + +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" + + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. +var _ = Describe("Jenkins controller", func() { + Describe("deploying Jenkins CR into a cluster", func() { + Context("when deploying CR to cluster", func() { + It("create Jenkins instance", func() { + ctx := context.Background() + jenkins := createJenkinsCR(jenkinsCRName, namespace, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) + Expect(k8sClient.Create(ctx, jenkins)).Should(Succeed()) + }) + }) + }) +}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go new file mode 100644 index 00000000..8c1935be --- /dev/null +++ b/controllers/suite_test.go @@ -0,0 +1,115 @@ +/* +Copyright 2021. + +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 ( + "path/filepath" + "testing" + + jenkinsiov1alpha2 "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + jenkinsClient "github.com/jenkinsci/kubernetes-operator/pkg/client" + e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + // +kubebuilder:scaffold:imports +) + +// These tests use Ginkgo (BDD-style Go testing framework). Refer to +// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. + +var ( + //cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment +) + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func(done Done) { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = jenkinsiov1alpha2.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + // +kubebuilder:scaffold:scheme + + //setup manager + k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme.Scheme, + }) + Expect(err).NotTo(HaveOccurred()) + + //setup controller + clientSet, err := kubernetes.NewForConfig(cfg) + Expect(err).NotTo(HaveOccurred()) + + notificationEvents := make(chan e.Event) + + // validate jenkins API connection + jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{} + + err = (&JenkinsReconciler{ + Client: k8sManager.GetClient(), + Scheme: k8sManager.GetScheme(), + JenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings, + ClientSet: *clientSet, + Config: *cfg, + NotificationEvents: ¬ificationEvents, + KubernetesClusterDomain: "", + }).SetupWithManager(k8sManager) + Expect(err).NotTo(HaveOccurred()) + + go func() { + err = k8sManager.Start(ctrl.SetupSignalHandler()) + Expect(err).NotTo(HaveOccurred()) + }() + + k8sClient = k8sManager.GetClient() + Expect(k8sClient).NotTo(BeNil()) + close(done) + +}, 60) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/go.mod b/go.mod index 4fa84ca9..847dbf14 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( github.com/go-logr/zapr v0.2.0 github.com/golang/mock v1.4.1 github.com/mailgun/mailgun-go/v3 v3.6.4 + github.com/onsi/ginkgo v1.14.1 + github.com/onsi/gomega v1.10.2 github.com/opencontainers/go-digest v1.0.0 // indirect github.com/openshift/api v3.9.0+incompatible github.com/pkg/errors v0.9.1 @@ -20,7 +22,6 @@ require ( gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df k8s.io/api v0.20.2 k8s.io/apimachinery v0.20.2 - k8s.io/cli-runtime v0.20.2 // indirect k8s.io/client-go v0.20.2 k8s.io/utils v0.0.0-20201110183641-67b214c5f920 sigs.k8s.io/controller-runtime v0.7.0 diff --git a/operator-sdk.mk b/operator-sdk.mk index 18567712..10ea4673 100644 --- a/operator-sdk.mk +++ b/operator-sdk.mk @@ -16,7 +16,7 @@ run: generate fmt vet manifests go run ./main.go # Install CRDs into a cluster -install: manifests kustomize +install-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - # Uninstall CRDs from a cluster From 524d0b861f78eda9c0ecf62864fac22f26c3f640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Tue, 26 Jan 2021 10:47:09 +0100 Subject: [PATCH 03/15] Refactor e2e tests to new ginkgo framework --- Makefile | 49 +--- api/v1alpha2/groupversion_info.go | 4 +- api/v1alpha2/jenkins_types.go | 4 - api/v1alpha2/jenkinsimage_types.go | 4 - ...o_jenkins.yaml => jenkins.io_jenkins.yaml} | 56 ++-- ...ges.yaml => jenkins.io_jenkinsimages.yaml} | 4 +- config/crd/kustomization.yaml | 2 +- .../crd/patches/cainjection_in_jenkins.yaml | 2 +- config/crd/patches/webhook_in_jenkins.yaml | 2 +- config/rbac/jenkins_editor_role.yaml | 4 +- config/rbac/jenkins_viewer_role.yaml | 4 +- .../samples/jenkins.io_v1alpha2_jenkins.yaml | 2 +- controllers/jenkins.go | 170 ------------ controllers/jenkins_controller_test.go | 40 --- go.mod | 1 + pkg/configuration/base/resources/probe.go | 2 + test/e2e/configuration_test.go | 254 ++++++------------ test/e2e/helm_test.go | 3 + test/e2e/jenkins_configuration_test.go | 141 ++++++++++ test/e2e/{jenkins.go => jenkins_test.go} | 117 ++++---- test/e2e/main_test.go | 69 ----- test/e2e/mode_kubernetes.go | 11 +- test/e2e/mode_openshift.go | 8 +- test/e2e/mode_openshift_oauth.go | 3 + test/e2e/operator.go | 155 ----------- .../{port_forward.go => port_forward_test.go} | 18 +- test/e2e/restart_test.go | 3 + test/e2e/restorebackup_test.go | 3 + test/e2e/seedjobs_test.go | 40 ++- {controllers => test/e2e}/suite_test.go | 102 ++++--- test/e2e/wait.go | 134 --------- test/e2e/wait_test.go | 87 ++++++ variables.mk | 9 +- 33 files changed, 523 insertions(+), 984 deletions(-) rename config/crd/bases/{jenkins.io.jenkins.io_jenkins.yaml => jenkins.io_jenkins.yaml} (98%) rename config/crd/bases/{jenkins.io.jenkins.io_jenkinsimages.yaml => jenkins.io_jenkinsimages.yaml} (97%) delete mode 100644 controllers/jenkins.go delete mode 100644 controllers/jenkins_controller_test.go create mode 100644 test/e2e/jenkins_configuration_test.go rename test/e2e/{jenkins.go => jenkins_test.go} (62%) delete mode 100644 test/e2e/main_test.go delete mode 100644 test/e2e/operator.go rename test/e2e/{port_forward.go => port_forward_test.go} (85%) rename {controllers => test/e2e}/suite_test.go (52%) delete mode 100644 test/e2e/wait.go create mode 100644 test/e2e/wait_test.go diff --git a/Makefile b/Makefile index 9a1e36f4..3e9a0bff 100644 --- a/Makefile +++ b/Makefile @@ -89,47 +89,10 @@ test: ## Runs the go tests @RUNNING_TESTS=1 go test -tags "$(BUILDTAGS) cgo" $(PACKAGES_FOR_UNIT_TESTS) .PHONY: e2e -CURRENT_DIRECTORY := $(shell pwd) -e2e: container-runtime-build ## Runs e2e tests, you can use EXTRA_ARGS +e2e: deepcopy-gen ## Runs e2e tests, you can use EXTRA_ARGS @echo "+ $@" - @echo "Docker image: $(DOCKER_REGISTRY):$(GITCOMMIT)" -ifeq ($(KUBERNETES_PROVIDER),minikube) - kubectl config use-context $(KUBECTL_CONTEXT) -endif -ifeq ($(KUBERNETES_PROVIDER),crc) - oc project $(CRC_OC_PROJECT) -endif - cp deploy/service_account.yaml deploy/namespace-init.yaml - cat deploy/role.yaml >> deploy/namespace-init.yaml - cat deploy/role_binding.yaml >> deploy/namespace-init.yaml - cat deploy/operator.yaml >> deploy/namespace-init.yaml -ifeq ($(OSFLAG), LINUX) -ifeq ($(IMAGE_PULL_MODE), remote) - sed -i 's|\(image:\).*|\1 $(DOCKER_ORGANIZATION)/$(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml - sed -i 's|\(imagePullPolicy\): IfNotPresent|\1: Always|g' deploy/namespace-init.yaml -else - sed -i 's|\(image:\).*|\1 $(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml -endif -ifeq ($(KUBERNETES_PROVIDER),minikube) - sed -i 's|\(imagePullPolicy\): IfNotPresent|\1: Never|g' deploy/namespace-init.yaml -endif -endif - -ifeq ($(OSFLAG), OSX) -ifeq ($(IMAGE_PULL_MODE), remote) - sed -i '' 's|\(image:\).*|\1 $(DOCKER_ORGANIZATION)/$(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml - sed -i '' 's|\(imagePullPolicy\): IfNotPresent|\1: Always|g' deploy/namespace-init.yaml -else - sed -i '' 's|\(image:\).*|\1 $(DOCKER_REGISTRY):$(GITCOMMIT)|g' deploy/namespace-init.yaml -endif -ifeq ($(KUBERNETES_PROVIDER),minikube) - sed -i '' 's|\(imagePullPolicy\): IfNotPresent|\1: Never|g' deploy/namespace-init.yaml -endif -endif - RUNNING_TESTS=1 go test -parallel=1 "./test/e2e/" -tags "$(BUILDTAGS) cgo" -v -timeout 60m -run "$(E2E_TEST_SELECTOR)" \ - -root=$(CURRENT_DIRECTORY) -kubeconfig=$(HOME)/.kube/config -globalMan deploy/crds/jenkins_$(API_VERSION)_jenkins_crd.yaml \ - -namespacedMan deploy/namespace-init.yaml $(TEST_ARGS) + $(TEST_ARGS) .PHONY: vet vet: ## Verifies `go vet` passes @@ -420,7 +383,6 @@ generate-docs: ## Re-generate docs directory from the website directory hugo -s website -d ../docs ##################### FROM OPERATOR SDK ######################## -#TODO rename # Install CRDs into a cluster install-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - @@ -474,9 +436,8 @@ bundle: manifests kustomize bundle-build: docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . -#FIXME temporary target for running tests (test used above for go test) -ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -testing: generate fmt vet manifests +# Download kubebuilder +kubebuilder: mkdir -p ${ENVTEST_ASSETS_DIR} test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh - source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out \ No newline at end of file + source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); \ No newline at end of file diff --git a/api/v1alpha2/groupversion_info.go b/api/v1alpha2/groupversion_info.go index 0cfebbdc..93674fa3 100644 --- a/api/v1alpha2/groupversion_info.go +++ b/api/v1alpha2/groupversion_info.go @@ -16,7 +16,7 @@ limitations under the License. // Package v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group // +kubebuilder:object:generate=true -// +groupName=jenkins.io.jenkins.io +// +groupName=jenkins.io package v1alpha2 import ( @@ -26,7 +26,7 @@ import ( var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "jenkins.io.jenkins.io", Version: "v1alpha2"} + GroupVersion = schema.GroupVersion{Group: "jenkins.io", Version: "v1alpha2"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} diff --git a/api/v1alpha2/jenkins_types.go b/api/v1alpha2/jenkins_types.go index 7a1461b6..7b130633 100644 --- a/api/v1alpha2/jenkins_types.go +++ b/api/v1alpha2/jenkins_types.go @@ -674,7 +674,3 @@ type GroovyScripts struct { type ConfigurationAsCode struct { Customization `json:",inline"` } - -func init() { - SchemeBuilder.Register(&Jenkins{}, &JenkinsList{}) -} diff --git a/api/v1alpha2/jenkinsimage_types.go b/api/v1alpha2/jenkinsimage_types.go index 1ac64dca..0be257a9 100644 --- a/api/v1alpha2/jenkinsimage_types.go +++ b/api/v1alpha2/jenkinsimage_types.go @@ -47,7 +47,3 @@ type JenkinsImageList struct { metav1.ListMeta `json:"metadata,omitempty"` Items []JenkinsImage `json:"items"` } - -func init() { - SchemeBuilder.Register(&JenkinsImage{}, &JenkinsImageList{}) -} diff --git a/config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml b/config/crd/bases/jenkins.io_jenkins.yaml similarity index 98% rename from config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml rename to config/crd/bases/jenkins.io_jenkins.yaml index 237e7a9c..1f86fe9a 100644 --- a/config/crd/bases/jenkins.io.jenkins.io_jenkins.yaml +++ b/config/crd/bases/jenkins.io_jenkins.yaml @@ -6,9 +6,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null - name: jenkins.jenkins.io.jenkins.io + name: jenkins.jenkins.io spec: - group: jenkins.io.jenkins.io + group: jenkins.io names: kind: Jenkins listKind: JenkinsList @@ -38,7 +38,7 @@ spec: properties: backup: description: 'Backup defines configuration of Jenkins backup More - info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' properties: action: description: Action defines action which performs backup in backup @@ -157,11 +157,11 @@ spec: type: object basePlugins: description: 'BasePlugins contains plugins required by operator - Defaults to : - name: kubernetes version: 1.15.7 - name: workflow-job - version: "2.39" - name: workflow-aggregator version: "2.6" - - name: git version: 3.10.0 - name: job-dsl version: "1.74" - - name: configuration-as-code version: "1.19" - name: kubernetes-credentials-provider - version: 0.12.1' + Defaults to : - name: kubernetes version: "1.28.6" - name: workflow-job + version: "2.40" - name: workflow-aggregator version: "2.6" - + name: git version: "4.5.0" - name: job-dsl version: "1.77" - + name: configuration-as-code version: "1.46" - name: kubernetes-credentials-provider + version: "0.15"' items: description: Plugin defines Jenkins plugin. properties: @@ -1184,7 +1184,7 @@ spec: support fsGroup based ownership(and permissions). It will have no effect on ephemeral volume types such as: secret, configmaps and emptydir. Valid values are "OnRootMismatch" - and "Always". If not specified defaults to "Always".' + and "Always". If not specified, "Always" is used.' type: string runAsGroup: description: The GID to run the entrypoint of the container @@ -1817,22 +1817,16 @@ spec: dataSource: description: 'This field can be used to specify either: * An existing VolumeSnapshot object - (snapshot.storage.k8s.io/VolumeSnapshot - - Beta) * An existing PVC (PersistentVolumeClaim) - * An existing custom resource/object that - implements data population (Alpha) In order - to use VolumeSnapshot object types, the appropriate - feature gate must be enabled (VolumeSnapshotDataSource - or AnyVolumeDataSource) If the provisioner - or an external controller can support the - specified data source, it will create a new - volume based on the contents of the specified - data source. If the specified data source - is not supported, the volume will not be created - and the failure will be reported as an event. - In the future, we plan to support more data - source types and the behavior of the provisioner - may change.' + (snapshot.storage.k8s.io/VolumeSnapshot) * + An existing PVC (PersistentVolumeClaim) * + An existing custom resource that implements + data population (Alpha) In order to use custom + resource types that implement data population, + the AnyVolumeDataSource feature gate must + be enabled. If the provisioner or an external + controller can support the specified data + source, it will create a new volume based + on the contents of the specified data source.' properties: apiGroup: description: APIGroup is the group for the @@ -2560,8 +2554,6 @@ spec: type: object type: object type: array - required: - - sources type: object quobyte: description: Quobyte represents a Quobyte mount on the host @@ -3030,7 +3022,7 @@ spec: type: array restore: description: 'Backup defines configuration of Jenkins backup restore - More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/' properties: action: description: Action defines action which performs restore backup @@ -3112,10 +3104,10 @@ spec: type: array seedJobs: description: 'SeedJobs defines list of Jenkins Seed Job configurations - More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines' + More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration#configure-seed-jobs-and-pipelines' items: description: 'SeedJob defines configuration for seed job More info: - https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines.' + https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration/#configure-seed-jobs-and-pipelines.' properties: additionalClasspath: description: AdditionalClasspath is setting for Job DSL API @@ -3209,7 +3201,7 @@ spec: will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature." - More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + More info: https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#restricting-cloud-metadata-api-access' items: type: string type: array @@ -3290,7 +3282,7 @@ spec: will restrict traffic through the cloud-provider load-balancer will be restricted to the specified client IPs. This field will be ignored if the cloud-provider does not support the feature." - More info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + More info: https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#restricting-cloud-metadata-api-access' items: type: string type: array diff --git a/config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml b/config/crd/bases/jenkins.io_jenkinsimages.yaml similarity index 97% rename from config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml rename to config/crd/bases/jenkins.io_jenkinsimages.yaml index 70c0914a..7c79607a 100644 --- a/config/crd/bases/jenkins.io.jenkins.io_jenkinsimages.yaml +++ b/config/crd/bases/jenkins.io_jenkinsimages.yaml @@ -6,9 +6,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.4.1 creationTimestamp: null - name: jenkinsimages.jenkins.io.jenkins.io + name: jenkinsimages.jenkins.io spec: - group: jenkins.io.jenkins.io + group: jenkins.io names: kind: JenkinsImage listKind: JenkinsImageList diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index d56a15ec..73fdb226 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,7 +2,7 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: -- bases/jenkins.io.jenkins.io_jenkins.yaml +- bases/jenkins.io_jenkins.yaml # +kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/crd/patches/cainjection_in_jenkins.yaml b/config/crd/patches/cainjection_in_jenkins.yaml index 463fc3cb..aa6b4a50 100644 --- a/config/crd/patches/cainjection_in_jenkins.yaml +++ b/config/crd/patches/cainjection_in_jenkins.yaml @@ -4,4 +4,4 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: jenkins.jenkins.io.jenkins.io + name: jenkins.jenkins.io diff --git a/config/crd/patches/webhook_in_jenkins.yaml b/config/crd/patches/webhook_in_jenkins.yaml index 7b4a9a30..37a2d93c 100644 --- a/config/crd/patches/webhook_in_jenkins.yaml +++ b/config/crd/patches/webhook_in_jenkins.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: jenkins.jenkins.io.jenkins.io + name: jenkins.jenkins.io spec: conversion: strategy: Webhook diff --git a/config/rbac/jenkins_editor_role.yaml b/config/rbac/jenkins_editor_role.yaml index 1846db5a..b9ccf6ab 100644 --- a/config/rbac/jenkins_editor_role.yaml +++ b/config/rbac/jenkins_editor_role.yaml @@ -5,7 +5,7 @@ metadata: name: jenkins-editor-role rules: - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins verbs: @@ -17,7 +17,7 @@ rules: - update - watch - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins/status verbs: diff --git a/config/rbac/jenkins_viewer_role.yaml b/config/rbac/jenkins_viewer_role.yaml index d65eb6f5..a4c1ccb1 100644 --- a/config/rbac/jenkins_viewer_role.yaml +++ b/config/rbac/jenkins_viewer_role.yaml @@ -5,7 +5,7 @@ metadata: name: jenkins-viewer-role rules: - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins verbs: @@ -13,7 +13,7 @@ rules: - list - watch - apiGroups: - - jenkins.io.jenkins.io + - jenkins.io resources: - jenkins/status verbs: diff --git a/config/samples/jenkins.io_v1alpha2_jenkins.yaml b/config/samples/jenkins.io_v1alpha2_jenkins.yaml index 970a02b2..68c07cfb 100644 --- a/config/samples/jenkins.io_v1alpha2_jenkins.yaml +++ b/config/samples/jenkins.io_v1alpha2_jenkins.yaml @@ -1,4 +1,4 @@ -apiVersion: jenkins.io.jenkins.io/v1alpha2 +apiVersion: jenkins.io/v1alpha2 kind: Jenkins metadata: name: jenkins-example diff --git a/controllers/jenkins.go b/controllers/jenkins.go deleted file mode 100644 index af11d172..00000000 --- a/controllers/jenkins.go +++ /dev/null @@ -1,170 +0,0 @@ -package controllers - -import ( - "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" - "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" - "github.com/jenkinsci/kubernetes-operator/pkg/constants" - corev1 "k8s.io/api/core/v1" - rbacv1 "k8s.io/api/rbac/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" -) - -const ( - userConfigurationConfigMapName = "user-config" - userConfigurationSecretName = "user-secret" -) - -type seedJobConfig struct { - v1alpha2.SeedJob - JobNames []string `json:"jobNames,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - PrivateKey string `json:"privateKey,omitempty"` -} - -var ( - jenkinsCRName = "jenkins-example" - namespace = "default" - priorityClassName = "" - - mySeedJob = seedJobConfig{ - SeedJob: v1alpha2.SeedJob{ - ID: "jenkins-operator", - CredentialID: "jenkins-operator", - JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, - Targets: "cicd/jobs/*.jenkins", - Description: "Jenkins Operator repository", - RepositoryBranch: "master", - RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", - PollSCM: "1 1 1 1 1", - UnstableOnDeprecation: true, - BuildPeriodically: "1 1 1 1 1", - FailOnMissingPlugin: true, - IgnoreMissingFiles: true, - //AdditionalClasspath: can fail with the seed job agent - GitHubPushTrigger: true, - }, - } - groovyScripts = v1alpha2.GroovyScripts{ - Customization: v1alpha2.Customization{ - Configurations: []v1alpha2.ConfigMapRef{ - { - Name: userConfigurationConfigMapName, - }, - }, - Secret: v1alpha2.SecretRef{ - Name: userConfigurationSecretName, - }, - }, - } - - casc = v1alpha2.ConfigurationAsCode{ - Customization: v1alpha2.Customization{ - Configurations: []v1alpha2.ConfigMapRef{ - { - Name: userConfigurationConfigMapName, - }, - }, - Secret: v1alpha2.SecretRef{ - Name: userConfigurationSecretName, - }, - }, - } -) - -func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { - var seedJobs []v1alpha2.SeedJob - if seedJob != nil { - seedJobs = append(seedJobs, *seedJob...) - } - - jenkins := &v1alpha2.Jenkins{ - TypeMeta: v1alpha2.JenkinsTypeMeta(), - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - }, - Spec: v1alpha2.JenkinsSpec{ - GroovyScripts: groovyScripts, - ConfigurationAsCode: casc, - Master: v1alpha2.JenkinsMaster{ - Annotations: map[string]string{"test": "label"}, - Containers: []v1alpha2.Container{ - { - Name: resources.JenkinsMasterContainerName, - Env: []corev1.EnvVar{ - { - Name: "TEST_ENV", - Value: "test_env_value", - }, - }, - ReadinessProbe: &corev1.Probe{ - Handler: corev1.Handler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/login", - Port: intstr.FromString("http"), - Scheme: corev1.URISchemeHTTP, - }, - }, - InitialDelaySeconds: int32(80), - TimeoutSeconds: int32(4), - FailureThreshold: int32(10), - }, - LivenessProbe: &corev1.Probe{ - Handler: corev1.Handler{ - HTTPGet: &corev1.HTTPGetAction{ - Path: "/login", - Port: intstr.FromString("http"), - Scheme: corev1.URISchemeHTTP, - }, - }, - InitialDelaySeconds: int32(80), - TimeoutSeconds: int32(4), - FailureThreshold: int32(10), - }, - VolumeMounts: []corev1.VolumeMount{ - { - Name: "plugins-cache", - MountPath: "/usr/share/jenkins/ref/plugins", - }, - }, - }, - { - Name: "envoyproxy", - Image: "envoyproxy/envoy-alpine:v1.14.1", - }, - }, - Plugins: []v1alpha2.Plugin{ - {Name: "audit-trail", Version: "3.7"}, - {Name: "simple-theme-plugin", Version: "0.6"}, - {Name: "github", Version: "1.32.0"}, - {Name: "devoptics", Version: "1.1905", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1905/devoptics.hpi"}, - }, - PriorityClassName: priorityClassName, - NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, - Volumes: []corev1.Volume{ - { - Name: "plugins-cache", - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - }, - }, - }, - SeedJobs: seedJobs, - Service: v1alpha2.Service{ - Type: corev1.ServiceTypeNodePort, - Port: constants.DefaultHTTPPortInt32, - }, - }, - } - jenkins.Spec.Roles = []rbacv1.RoleRef{ - { - APIGroup: "rbac.authorization.k8s.io", - Kind: "Role", - Name: resources.GetResourceName(jenkins), - }, - } - return jenkins -} diff --git a/controllers/jenkins_controller_test.go b/controllers/jenkins_controller_test.go deleted file mode 100644 index f0c25f5c..00000000 --- a/controllers/jenkins_controller_test.go +++ /dev/null @@ -1,40 +0,0 @@ -/* -Copyright 2021. - -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" - - "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - // +kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. -var _ = Describe("Jenkins controller", func() { - Describe("deploying Jenkins CR into a cluster", func() { - Context("when deploying CR to cluster", func() { - It("create Jenkins instance", func() { - ctx := context.Background() - jenkins := createJenkinsCR(jenkinsCRName, namespace, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) - Expect(k8sClient.Create(ctx, jenkins)).Should(Succeed()) - }) - }) - }) -}) diff --git a/go.mod b/go.mod index 847dbf14..a274cd35 100644 --- a/go.mod +++ b/go.mod @@ -22,6 +22,7 @@ require ( gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df k8s.io/api v0.20.2 k8s.io/apimachinery v0.20.2 + k8s.io/cli-runtime v0.20.2 k8s.io/client-go v0.20.2 k8s.io/utils v0.0.0-20201110183641-67b214c5f920 sigs.k8s.io/controller-runtime v0.7.0 diff --git a/pkg/configuration/base/resources/probe.go b/pkg/configuration/base/resources/probe.go index 545933ae..41d54dc7 100644 --- a/pkg/configuration/base/resources/probe.go +++ b/pkg/configuration/base/resources/probe.go @@ -15,6 +15,8 @@ func NewSimpleProbe(uri string, port string, scheme corev1.URIScheme, initialDel }, }, InitialDelaySeconds: initialDelaySeconds, + SuccessThreshold: int32(1), + PeriodSeconds: int32(1), } } diff --git a/test/e2e/configuration_test.go b/test/e2e/configuration_test.go index 81a1e033..780a739c 100644 --- a/test/e2e/configuration_test.go +++ b/test/e2e/configuration_test.go @@ -3,10 +3,8 @@ package e2e import ( "context" "fmt" - "testing" - "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" @@ -15,9 +13,8 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/plugins" "github.com/bndr/gojenkins" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -26,86 +23,8 @@ import ( const e2e = "e2e" -func TestConfiguration(t *testing.T) { - t.Parallel() - namespace, ctx := setupTest(t) - - defer showLogsAndCleanup(t, ctx) - - jenkinsCRName := e2e - numberOfExecutors := 6 - numberOfExecutorsEnvName := "NUMBER_OF_EXECUTORS" - systemMessage := "Configuration as Code integration works!!!" - systemMessageEnvName := "SYSTEM_MESSAGE" - priorityClassName := "" - mySeedJob := seedJobConfig{ - SeedJob: v1alpha2.SeedJob{ - ID: "jenkins-operator", - CredentialID: "jenkins-operator", - JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, - Targets: "cicd/jobs/*.jenkins", - Description: "Jenkins Operator repository", - RepositoryBranch: "master", - RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", - PollSCM: "1 1 1 1 1", - UnstableOnDeprecation: true, - BuildPeriodically: "1 1 1 1 1", - FailOnMissingPlugin: true, - IgnoreMissingFiles: true, - //AdditionalClasspath: can fail with the seed job agent - GitHubPushTrigger: true, - }, - } - groovyScripts := v1alpha2.GroovyScripts{ - Customization: v1alpha2.Customization{ - Configurations: []v1alpha2.ConfigMapRef{ - { - Name: userConfigurationConfigMapName, - }, - }, - Secret: v1alpha2.SecretRef{ - Name: userConfigurationSecretName, - }, - }, - } - - casc := v1alpha2.ConfigurationAsCode{ - Customization: v1alpha2.Customization{ - Configurations: []v1alpha2.ConfigMapRef{ - { - Name: userConfigurationConfigMapName, - }, - }, - Secret: v1alpha2.SecretRef{ - Name: userConfigurationSecretName, - }, - }, - } - - stringData := make(map[string]string) - stringData[systemMessageEnvName] = systemMessage - stringData[numberOfExecutorsEnvName] = fmt.Sprintf("%d", numberOfExecutors) - - // base - createUserConfigurationSecret(t, namespace, stringData) - createUserConfigurationConfigMap(t, namespace, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName)) - jenkins := createJenkinsCR(t, jenkinsCRName, namespace, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) - createDefaultLimitsForContainersInNamespace(t, namespace) - createKubernetesCredentialsProviderSecret(t, namespace, mySeedJob) - waitForJenkinsBaseConfigurationToComplete(t, jenkins) - verifyJenkinsMasterPodAttributes(t, jenkins) - verifyServices(t, jenkins) - jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc() - verifyPlugins(t, jenkinsClient, jenkins) - - // user - waitForJenkinsUserConfigurationToComplete(t, jenkins) - verifyUserConfiguration(t, jenkinsClient, numberOfExecutors, systemMessage) - verifyJenkinsSeedJobs(t, jenkinsClient, []seedJobConfig{mySeedJob}) -} - -func TestPlugins(t *testing.T) { +// FIXME +/*func TestPlugins(t *testing.T) { t.Parallel() namespace, ctx := setupTest(t) // Deletes test namespace @@ -145,28 +64,11 @@ func TestPlugins(t *testing.T) { build, err := job.GetLastBuild() require.NoError(t, err) assert.True(t, build.IsGood()) -} +}*/ -func TestPriorityClass(t *testing.T) { - if skipTestPriorityClass { - t.Skip() - } - t.Parallel() - namespace, ctx := setupTest(t) - defer showLogsAndCleanup(t, ctx) +func createUserConfigurationSecret(namespace string, stringData map[string]string) { + By("creating user configuration secret") - jenkinsCRName := "k8s-ete-priority-class-existing" - // One of the existing priority classes - priorityClassName := "system-cluster-critical" - - jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil, v1alpha2.GroovyScripts{}, v1alpha2.ConfigurationAsCode{}, priorityClassName) - waitForJenkinsBaseConfigurationToComplete(t, jenkins) - verifyJenkinsMasterPodAttributes(t, jenkins) - _, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc() -} - -func createUserConfigurationSecret(t *testing.T, namespace string, stringData map[string]string) { userConfiguration := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: userConfigurationSecretName, @@ -175,13 +77,13 @@ func createUserConfigurationSecret(t *testing.T, namespace string, stringData ma StringData: stringData, } - t.Logf("User configuration secret %+v", *userConfiguration) - if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "User configuration secret %+v\n", *userConfiguration) + Expect(k8sClient.Create(context.TODO(), userConfiguration)).Should(Succeed()) } -func createUserConfigurationConfigMap(t *testing.T, namespace string, numberOfExecutorsSecretKeyName string, systemMessage string) { +func createUserConfigurationConfigMap(namespace string, numberOfExecutorsSecretKeyName string, systemMessage string) { + By("creating user configuration config map") + userConfiguration := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: userConfigurationConfigMapName, @@ -203,13 +105,13 @@ unclassified: }, } - t.Logf("User configuration %+v", *userConfiguration) - if err := framework.Global.Client.Create(context.TODO(), userConfiguration, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "User configuration %+v\n", *userConfiguration) + Expect(k8sClient.Create(context.TODO(), userConfiguration)).Should(Succeed()) } -func createDefaultLimitsForContainersInNamespace(t *testing.T, namespace string) { +func createDefaultLimitsForContainersInNamespace(namespace string) { + By("creating default limits for containers in namespace") + limitRange := &corev1.LimitRange{ ObjectMeta: metav1.ObjectMeta{ Name: e2e, @@ -232,31 +134,34 @@ func createDefaultLimitsForContainersInNamespace(t *testing.T, namespace string) }, } - t.Logf("LimitRange %+v", *limitRange) - if err := framework.Global.Client.Create(context.TODO(), limitRange, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "LimitRange %+v\n", *limitRange) + Expect(k8sClient.Create(context.TODO(), limitRange)).Should(Succeed()) } -func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) { - jenkinsPod := getJenkinsMasterPod(t, jenkins) - jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) +func verifyJenkinsMasterPodAttributes(jenkins *v1alpha2.Jenkins) { + By("creating Jenkins master pod properly") - assertMapContainsElementsFromAnotherMap(t, jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations) - assert.Equal(t, jenkins.Spec.Master.NodeSelector, jenkinsPod.Spec.NodeSelector) + jenkinsPod := getJenkinsMasterPod(jenkins) + jenkins = getJenkins(jenkins.Namespace, jenkins.Name) - assert.Equal(t, resources.JenkinsMasterContainerName, jenkinsPod.Spec.Containers[0].Name) - assert.Equal(t, len(jenkins.Spec.Master.Containers), len(jenkinsPod.Spec.Containers)) + assertMapContainsElementsFromAnotherMap(jenkins.Spec.Master.Annotations, jenkinsPod.ObjectMeta.Annotations) + Expect(jenkinsPod.Spec.NodeSelector).Should(Equal(jenkins.Spec.Master.NodeSelector)) - assert.Equal(t, jenkins.Spec.Master.SecurityContext, jenkinsPod.Spec.SecurityContext) - assert.Equal(t, jenkins.Spec.Master.Containers[0].Command, jenkinsPod.Spec.Containers[0].Command) + Expect(jenkinsPod.Spec.Containers[0].Name).Should(Equal(resources.JenkinsMasterContainerName)) + Expect(jenkinsPod.Spec.Containers).Should(HaveLen(len(jenkins.Spec.Master.Containers))) - assert.Equal(t, resources.GetJenkinsMasterPodLabels(*jenkins), jenkinsPod.Labels) - assert.Equal(t, jenkins.Spec.Master.PriorityClassName, jenkinsPod.Spec.PriorityClassName) + if jenkins.Spec.Master.SecurityContext == nil { + jenkins.Spec.Master.SecurityContext = &corev1.PodSecurityContext{} + } + Expect(jenkinsPod.Spec.SecurityContext).Should(Equal(jenkins.Spec.Master.SecurityContext)) + Expect(jenkinsPod.Spec.Containers[0].Command).Should(Equal(jenkins.Spec.Master.Containers[0].Command)) + + Expect(jenkinsPod.Labels).Should(Equal(resources.GetJenkinsMasterPodLabels(*jenkins))) + Expect(jenkinsPod.Spec.PriorityClassName).Should(Equal(jenkins.Spec.Master.PriorityClassName)) for _, actualContainer := range jenkinsPod.Spec.Containers { if actualContainer.Name == resources.JenkinsMasterContainerName { - verifyContainer(t, resources.NewJenkinsMasterContainer(jenkins), actualContainer) + verifyContainer(resources.NewJenkinsMasterContainer(jenkins), actualContainer) continue } @@ -269,11 +174,11 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) { } if expectedContainer == nil { - t.Errorf("Container '%+v' not found in pod", actualContainer) + Fail(fmt.Sprintf("Container '%+v' not found in pod", actualContainer)) continue } - verifyContainer(t, *expectedContainer, actualContainer) + verifyContainer(*expectedContainer, actualContainer) } for _, expectedVolume := range jenkins.Spec.Master.Volumes { @@ -281,58 +186,54 @@ func verifyJenkinsMasterPodAttributes(t *testing.T, jenkins *v1alpha2.Jenkins) { for _, actualVolume := range jenkinsPod.Spec.Volumes { if expectedVolume.Name == actualVolume.Name { volumeFound = true - assert.Equal(t, expectedVolume, actualVolume) + Expect(actualVolume).Should(Equal(expectedVolume)) } } if !volumeFound { - t.Errorf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes) + Fail(fmt.Sprintf("Missing volume '+%v', actaul volumes '%+v'", expectedVolume, jenkinsPod.Spec.Volumes)) } } - - t.Log("Jenkins pod attributes are valid") } -func verifyContainer(t *testing.T, expected corev1.Container, actual corev1.Container) { - assert.Equal(t, expected.Args, actual.Args, expected.Name, expected.Name) - assert.Equal(t, expected.Command, actual.Command, expected.Name) - assert.ElementsMatch(t, expected.Env, actual.Env, expected.Name) - assert.Equal(t, expected.EnvFrom, actual.EnvFrom, expected.Name) - assert.Equal(t, expected.Image, actual.Image, expected.Name) - assert.Equal(t, expected.ImagePullPolicy, actual.ImagePullPolicy, expected.Name) - assert.Equal(t, expected.Lifecycle, actual.Lifecycle, expected.Name) - assert.Equal(t, expected.LivenessProbe, actual.LivenessProbe, expected.Name) - assert.Equal(t, expected.Ports, actual.Ports, expected.Name) - assert.Equal(t, expected.ReadinessProbe, actual.ReadinessProbe, expected.Name) - assert.Equal(t, expected.Resources, actual.Resources, expected.Name) - assert.Equal(t, expected.SecurityContext, actual.SecurityContext, expected.Name) - assert.Equal(t, expected.WorkingDir, actual.WorkingDir, expected.Name) +func verifyContainer(expected corev1.Container, actual corev1.Container) { + Expect(actual.Args).Should(Equal(expected.Args), expected.Name) + Expect(actual.Command).Should(Equal(expected.Command), expected.Name) + Expect(actual.Env).Should(ConsistOf(expected.Env), expected.Name) + Expect(actual.EnvFrom).Should(Equal(expected.EnvFrom), expected.Name) + Expect(actual.Image).Should(Equal(expected.Image), expected.Name) + Expect(actual.ImagePullPolicy).Should(Equal(expected.ImagePullPolicy), expected.Name) + Expect(actual.Lifecycle).Should(Equal(expected.Lifecycle), expected.Name) + Expect(actual.LivenessProbe).Should(Equal(expected.LivenessProbe), expected.Name) + Expect(actual.Ports).Should(Equal(expected.Ports), expected.Name) + Expect(actual.ReadinessProbe).Should(Equal(expected.ReadinessProbe), expected.Name) + Expect(actual.Resources).Should(Equal(expected.Resources), expected.Name) + Expect(actual.SecurityContext).Should(Equal(expected.SecurityContext), expected.Name) + Expect(actual.WorkingDir).Should(Equal(expected.WorkingDir), expected.Name) if !base.CompareContainerVolumeMounts(expected, actual) { - t.Errorf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'", - expected.Name, expected.VolumeMounts, actual.VolumeMounts) + Fail(fmt.Sprintf("Volume mounts are different in container '%s': expected '%+v', actual '%+v'", + expected.Name, expected.VolumeMounts, expected.VolumeMounts)) } } -func verifyPlugins(t *testing.T, jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha2.Jenkins) { +func verifyPlugins(jenkinsClient jenkinsclient.Jenkins, jenkins *v1alpha2.Jenkins) { + By("installing plugins in Jenkins instance") + installedPlugins, err := jenkinsClient.GetPlugins(1) - if err != nil { - t.Fatal(err) - } + Expect(err).NotTo(HaveOccurred()) for _, basePlugin := range plugins.BasePlugins() { if found, ok := isPluginValid(installedPlugins, basePlugin); !ok { - t.Fatalf("Invalid plugin '%s', actual '%+v'", basePlugin, found) + Fail(fmt.Sprintf("Invalid plugin '%s', actual '%+v'", basePlugin, found)) } } for _, userPlugin := range jenkins.Spec.Master.Plugins { plugin := plugins.Plugin{Name: userPlugin.Name, Version: userPlugin.Version} if found, ok := isPluginValid(installedPlugins, plugin); !ok { - t.Fatalf("Invalid plugin '%s', actual '%+v'", plugin, found) + Fail(fmt.Sprintf("Invalid plugin '%s', actual '%+v'", plugin, found)) } } - - t.Log("All plugins have been installed") } func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (*gojenkins.Plugin, bool) { @@ -348,13 +249,15 @@ func isPluginValid(plugins *gojenkins.Plugins, requiredPlugin plugins.Plugin) (* return p, requiredPlugin.Version == p.Version } -func verifyUserConfiguration(t *testing.T, jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) { +func verifyUserConfiguration(jenkinsClient jenkinsclient.Jenkins, amountOfExecutors int, systemMessage string) { + By("configuring Jenkins by groovy scripts") + checkConfigurationViaGroovyScript := fmt.Sprintf(` if (!new Integer(%d).equals(Jenkins.instance.numExecutors)) { throw new Exception("Configuration via groovy scripts failed") }`, amountOfExecutors) logs, err := jenkinsClient.ExecuteScript(checkConfigurationViaGroovyScript) - assert.NoError(t, err, logs) + Expect(err).NotTo(HaveOccurred(), logs) checkSecretLoaderViaGroovyScript := fmt.Sprintf(` if (!new Integer(%d).equals(new Integer(secrets['NUMBER_OF_EXECUTORS']))) { @@ -363,30 +266,33 @@ if (!new Integer(%d).equals(new Integer(secrets['NUMBER_OF_EXECUTORS']))) { loader := groovy.AddSecretsLoaderToGroovyScript("/var/jenkins/groovy-scripts-secrets") logs, err = jenkinsClient.ExecuteScript(loader(checkSecretLoaderViaGroovyScript)) - assert.NoError(t, err, logs) + Expect(err).NotTo(HaveOccurred(), logs) + By("configuring Jenkins by CasC") checkConfigurationAsCode := fmt.Sprintf(` if (!"%s".equals(Jenkins.instance.systemMessage)) { throw new Exception("Configuration as code failed") }`, systemMessage) logs, err = jenkinsClient.ExecuteScript(checkConfigurationAsCode) - assert.NoError(t, err, logs) + Expect(err).NotTo(HaveOccurred(), logs) } -func verifyServices(t *testing.T, jenkins *v1alpha2.Jenkins) { - jenkinsHTTPService := getJenkinsService(t, jenkins, "http") - jenkinsSlaveService := getJenkinsService(t, jenkins, "slave") - assert.Equal(t, intstr.IntOrString{IntVal: constants.DefaultHTTPPortInt32, Type: intstr.Int}, jenkinsHTTPService.Spec.Ports[0].TargetPort) - assert.Equal(t, intstr.IntOrString{IntVal: constants.DefaultSlavePortInt32, Type: intstr.Int}, jenkinsSlaveService.Spec.Ports[0].TargetPort) +func verifyServices(jenkins *v1alpha2.Jenkins) { + By("creating Jenkins services properly") + + jenkinsHTTPService := getJenkinsService(jenkins, "http") + jenkinsSlaveService := getJenkinsService(jenkins, "slave") + Expect(jenkinsHTTPService.Spec.Ports[0].TargetPort).Should(Equal(intstr.IntOrString{IntVal: constants.DefaultHTTPPortInt32, Type: intstr.Int})) + Expect(jenkinsSlaveService.Spec.Ports[0].TargetPort).Should(Equal(intstr.IntOrString{IntVal: constants.DefaultSlavePortInt32, Type: intstr.Int})) } -func assertMapContainsElementsFromAnotherMap(t *testing.T, expected map[string]string, actual map[string]string) { +func assertMapContainsElementsFromAnotherMap(expected map[string]string, actual map[string]string) { for key, expectedValue := range expected { actualValue, keyExists := actual[key] if !keyExists { - assert.Failf(t, "key '%s' doesn't exist in map '%+v'", key, actual) + Fail(fmt.Sprintf("key '%s' doesn't exist in map '%+v'", key, actual)) continue } - assert.Equal(t, expectedValue, actualValue, expected, actual) + Expect(actualValue).Should(Equal(expectedValue), key, expected, actual) } } diff --git a/test/e2e/helm_test.go b/test/e2e/helm_test.go index 6637bdac..73d495da 100644 --- a/test/e2e/helm_test.go +++ b/test/e2e/helm_test.go @@ -2,6 +2,8 @@ package e2e +// TODO +/* import ( "fmt" "os/exec" @@ -66,3 +68,4 @@ func TestDeployHelmChart(t *testing.T) { waitForJenkinsBaseConfigurationToComplete(t, jenkins) waitForJenkinsUserConfigurationToComplete(t, jenkins) } +/* \ No newline at end of file diff --git a/test/e2e/jenkins_configuration_test.go b/test/e2e/jenkins_configuration_test.go new file mode 100644 index 00000000..5ff057ff --- /dev/null +++ b/test/e2e/jenkins_configuration_test.go @@ -0,0 +1,141 @@ +package e2e + +import ( + "fmt" + + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + + . "github.com/onsi/ginkgo" + corev1 "k8s.io/api/core/v1" + // +kubebuilder:scaffold:imports +) + +var _ = Describe("Jenkins controller configuration", func() { + + const ( + jenkinsCRName = e2e + numberOfExecutors = 6 + numberOfExecutorsEnvName = "NUMBER_OF_EXECUTORS" + systemMessage = "Configuration as Code integration works!!!" + systemMessageEnvName = "SYSTEM_MESSAGE" + priorityClassName = "" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + mySeedJob = seedJobConfig{ + SeedJob: v1alpha2.SeedJob{ + ID: "jenkins-operator", + CredentialID: "jenkins-operator", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/*.jenkins", + Description: "Jenkins Operator repository", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + PollSCM: "1 1 1 1 1", + UnstableOnDeprecation: true, + BuildPeriodically: "1 1 1 1 1", + FailOnMissingPlugin: true, + IgnoreMissingFiles: true, + //AdditionalClasspath: can fail with the seed job agent + GitHubPushTrigger: true, + }, + } + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{ + { + Name: userConfigurationConfigMapName, + }, + }, + Secret: v1alpha2.SecretRef{ + Name: userConfigurationSecretName, + }, + }, + } + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{ + { + Name: userConfigurationConfigMapName, + }, + }, + Secret: v1alpha2.SecretRef{ + Name: userConfigurationSecretName, + }, + }, + } + userConfigurationSecretData = map[string]string{ + systemMessageEnvName: systemMessage, + numberOfExecutorsEnvName: fmt.Sprintf("%d", numberOfExecutors), + } + ) + + BeforeEach(func() { + namespace = createNamespace() + + createUserConfigurationSecret(namespace.Name, userConfigurationSecretData) + createUserConfigurationConfigMap(namespace.Name, numberOfExecutorsEnvName, fmt.Sprintf("${%s}", systemMessageEnvName)) + jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) + createDefaultLimitsForContainersInNamespace(namespace.Name) + createKubernetesCredentialsProviderSecret(namespace.Name, mySeedJob) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when deploying CR to cluster", func() { + It("creates Jenkins instance and configures it", func() { + waitForJenkinsBaseConfigurationToComplete(jenkins) + verifyJenkinsMasterPodAttributes(jenkins) + verifyServices(jenkins) + jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(jenkins, namespace.Name) + defer cleanUpFunc() + verifyPlugins(jenkinsClient, jenkins) + waitForJenkinsUserConfigurationToComplete(jenkins) + verifyUserConfiguration(jenkinsClient, numberOfExecutors, systemMessage) + verifyJenkinsSeedJobs(jenkinsClient, []seedJobConfig{mySeedJob}) + }) + }) +}) + +var _ = Describe("Jenkins controller priority class", func() { + + const ( + jenkinsCRName = "k8s-ete-priority-class-existing" + priorityClassName = "system-cluster-critical" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + ) + + BeforeEach(func() { + namespace = createNamespace() + jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when deploying CR with priority class to cluster", func() { + It("creates Jenkins instance and configures it", func() { + waitForJenkinsBaseConfigurationToComplete(jenkins) + verifyJenkinsMasterPodAttributes(jenkins) + }) + }) +}) diff --git a/test/e2e/jenkins.go b/test/e2e/jenkins_test.go similarity index 62% rename from test/e2e/jenkins.go rename to test/e2e/jenkins_test.go index 73a118b0..55683081 100644 --- a/test/e2e/jenkins.go +++ b/test/e2e/jenkins_test.go @@ -3,25 +3,24 @@ package e2e import ( "context" "fmt" - "testing" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/constants" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" ) const ( @@ -29,31 +28,25 @@ const ( userConfigurationSecretName = "user-secret" ) -func getJenkins(t *testing.T, namespace, name string) *v1alpha2.Jenkins { +func getJenkins(namespace, name string) *v1alpha2.Jenkins { jenkins := &v1alpha2.Jenkins{} namespaceName := types.NamespacedName{Namespace: namespace, Name: name} - if err := framework.Global.Client.Get(context.TODO(), namespaceName, jenkins); err != nil { - t.Fatal(err) - } - + Expect(k8sClient.Get(context.TODO(), namespaceName, jenkins)).Should(Succeed()) return jenkins } -func getJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) *corev1.Pod { - lo := metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(), +func getJenkinsMasterPod(jenkins *v1alpha2.Jenkins) *corev1.Pod { + lo := &client.ListOptions{ + LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)), + Namespace: jenkins.Namespace, } - podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo) - if err != nil { - t.Fatal(err) - } - if len(podList.Items) != 1 { - t.Fatalf("Jenkins pod not found, pod list: %+v", podList) - } - return &podList.Items[0] + pods := &corev1.PodList{} + Expect(k8sClient.List(context.TODO(), pods, lo)).Should(Succeed()) + Expect(pods.Items).Should(HaveLen(1), fmt.Sprintf("Jenkins pod not found, pod list: %+v", pods.Items)) + return &pods.Items[0] } -func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { +func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { var seedJobs []v1alpha2.SeedJob if seedJob != nil { seedJobs = append(seedJobs, *seedJob...) @@ -90,6 +83,8 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S InitialDelaySeconds: int32(80), TimeoutSeconds: int32(4), FailureThreshold: int32(10), + SuccessThreshold: int32(1), + PeriodSeconds: int32(1), }, LivenessProbe: &corev1.Probe{ Handler: corev1.Handler{ @@ -102,6 +97,8 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S InitialDelaySeconds: int32(80), TimeoutSeconds: int32(4), FailureThreshold: int32(10), + SuccessThreshold: int32(1), + PeriodSeconds: int32(1), }, VolumeMounts: []corev1.VolumeMount{ { @@ -146,25 +143,23 @@ func createJenkinsCR(t *testing.T, name, namespace string, seedJob *[]v1alpha2.S Name: resources.GetResourceName(jenkins), }, } - updateJenkinsCR(t, jenkins) + updateJenkinsCR(jenkins) - t.Logf("Jenkins CR %+v", *jenkins) - if err := framework.Global.Client.Create(context.TODO(), jenkins, nil); err != nil { - t.Fatal(err) - } + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins CR %+v\n", *jenkins) + + Expect(k8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) return jenkins } -func createJenkinsAPIClientFromServiceAccount(t *testing.T, jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { - t.Log("Creating Jenkins API client from service account") +func createJenkinsAPIClientFromServiceAccount(jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { podName := resources.GetJenkinsMasterPodName(jenkins) - clientSet, err := kubernetes.NewForConfig(framework.Global.KubeConfig) + clientSet, err := kubernetes.NewForConfig(cfg) if err != nil { return nil, err } - config := configuration.Configuration{Jenkins: jenkins, ClientSet: *clientSet, Config: framework.Global.KubeConfig} + config := configuration.Configuration{Jenkins: jenkins, ClientSet: *clientSet, Config: cfg} r := base.New(config, jenkinsclient.JenkinsAPIConnectionSettings{}) token, _, err := r.Configuration.Exec(podName, resources.JenkinsMasterContainerName, []string{"cat", "/var/run/secrets/kubernetes.io/serviceaccount/token"}) @@ -175,12 +170,12 @@ func createJenkinsAPIClientFromServiceAccount(t *testing.T, jenkins *v1alpha2.Je return jenkinsclient.NewBearerTokenAuthorization(jenkinsAPIURL, token.String()) } -func createJenkinsAPIClientFromSecret(t *testing.T, jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { - t.Log("Creating Jenkins API client from secret") +func createJenkinsAPIClientFromSecret(jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { + _, _ = fmt.Fprintf(GinkgoWriter, "Creating Jenkins API client from secret\n") adminSecret := &corev1.Secret{} namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: resources.GetOperatorCredentialsSecretName(jenkins)} - if err := framework.Global.Client.Get(context.TODO(), namespaceName, adminSecret); err != nil { + if err := k8sClient.Get(context.TODO(), namespaceName, adminSecret); err != nil { return nil, err } @@ -191,19 +186,19 @@ func createJenkinsAPIClientFromSecret(t *testing.T, jenkins *v1alpha2.Jenkins, j ) } -func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha2.Jenkins, namespace string) (jenkinsclient.Jenkins, func()) { +func verifyJenkinsAPIConnection(jenkins *v1alpha2.Jenkins, namespace string) (jenkinsclient.Jenkins, func()) { + By("establishing Jenkins API connection") + var service corev1.Service - err := framework.Global.Client.Get(context.TODO(), types.NamespacedName{ + err := k8sClient.Get(context.TODO(), types.NamespacedName{ Namespace: jenkins.Namespace, Name: resources.GetJenkinsHTTPServiceName(jenkins), }, &service) - require.NoError(t, err) + Expect(err).NotTo(HaveOccurred()) podName := resources.GetJenkinsMasterPodName(jenkins) - port, cleanUpFunc, waitFunc, portForwardFunc, err := setupPortForwardToPod(t, namespace, podName, int(constants.DefaultHTTPPortInt32)) - if err != nil { - t.Fatal(err) - } + port, cleanUpFunc, waitFunc, portForwardFunc, err := setupPortForwardToPod(namespace, podName, int(constants.DefaultHTTPPortInt32)) + Expect(err).NotTo(HaveOccurred()) go portForwardFunc() waitFunc() @@ -213,41 +208,33 @@ func verifyJenkinsAPIConnection(t *testing.T, jenkins *v1alpha2.Jenkins, namespa UseNodePort: false, }.BuildJenkinsAPIUrl(service.Name, service.Namespace, service.Spec.Ports[0].Port, service.Spec.Ports[0].NodePort) - var client jenkinsclient.Jenkins + var jenkinsClient jenkinsclient.Jenkins if jenkins.Spec.JenkinsAPISettings.AuthorizationStrategy == v1alpha2.ServiceAccountAuthorizationStrategy { - client, err = createJenkinsAPIClientFromServiceAccount(t, jenkins, jenkinsAPIURL) + jenkinsClient, err = createJenkinsAPIClientFromServiceAccount(jenkins, jenkinsAPIURL) } else { - client, err = createJenkinsAPIClientFromSecret(t, jenkins, jenkinsAPIURL) + jenkinsClient, err = createJenkinsAPIClientFromSecret(jenkins, jenkinsAPIURL) } if err != nil { defer cleanUpFunc() - t.Fatal(err) + Fail(err.Error()) } - t.Log("I can establish connection to Jenkins API") - return client, cleanUpFunc + _, _ = fmt.Fprintf(GinkgoWriter, "I can establish connection to Jenkins API\n") + return jenkinsClient, cleanUpFunc } -func restartJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Restarting Jenkins master pod") - jenkinsPod := getJenkinsMasterPod(t, jenkins) - err := framework.Global.Client.Delete(context.TODO(), jenkinsPod) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins master pod has been restarted") -} +/*func restartJenkinsMasterPod(jenkins *v1alpha2.Jenkins) { + _, _ = fmt.Fprintf(GinkgoWriter, "Restarting Jenkins master pod") + jenkinsPod := getJenkinsMasterPod(jenkins) + Expect(k8sClient.Delete(context.TODO(), jenkinsPod)).Should(Succeed()) + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins master pod has been restarted") +}*/ -func getJenkinsService(t *testing.T, jenkins *v1alpha2.Jenkins, serviceKind string) *corev1.Service { +func getJenkinsService(jenkins *v1alpha2.Jenkins, serviceKind string) *corev1.Service { + service := &corev1.Service{} serviceName := constants.OperatorName + "-" + serviceKind + "-" + jenkins.ObjectMeta.Name - lo := metav1.ListOptions{ - FieldSelector: fields.SelectorFromSet(fields.Set{"metadata.name": serviceName}).String(), - } - serviceList, err := framework.Global.KubeClient.CoreV1().Services(jenkins.Namespace).List(lo) + Expect(k8sClient.Get(context.TODO(), client.ObjectKey{Name: serviceName, Namespace: jenkins.Namespace}, service)).Should(Succeed()) - require.NoError(t, err) - require.Equal(t, 1, len(serviceList.Items), fmt.Sprintf("'%s' service not found", serviceName)) - - return &serviceList.Items[0] + return service } diff --git a/test/e2e/main_test.go b/test/e2e/main_test.go deleted file mode 100644 index c1494109..00000000 --- a/test/e2e/main_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package e2e - -import ( - "flag" - "testing" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - "github.com/jenkinsci/kubernetes-operator/pkg/constants" - - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/operator-framework/operator-sdk/pkg/test/e2eutil" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -const ( - jenkinsOperatorDeploymentName = constants.OperatorName - seedJobConfigurationParameterName = "seed-job-config" -) - -var ( - seedJobConfigurationFile *string -) - -func TestMain(m *testing.M) { - seedJobConfigurationFile = flag.String(seedJobConfigurationParameterName, "", "path to seed job config") - - framework.MainEntry(m) -} - -func setupTest(t *testing.T) (string, *framework.Context) { - ctx := framework.NewContext(t) - err := ctx.InitializeClusterResources(nil) - if err != nil { - t.Fatalf("could not initialize cluster resources: %v", err) - } - - defer func() { - showLogsIfTestHasFailed(t, ctx) - if t.Failed() && ctx != nil { - ctx.Cleanup() - } - }() - - jenkinsServiceList := &v1alpha2.JenkinsList{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha2.Kind, - APIVersion: v1alpha2.SchemeGroupVersion.String(), - }, - } - err = framework.AddToFrameworkScheme(apis.AddToScheme, jenkinsServiceList) - if err != nil { - t.Fatalf("could not add scheme to framework scheme: %v", err) - } - - namespace, err := ctx.GetOperatorNamespace() - if err != nil { - t.Fatalf("could not get namespace: %v", err) - } - t.Logf("Test namespace '%s'", namespace) - - // wait for jenkins-operator to be ready - err = e2eutil.WaitForDeployment(t, framework.Global.KubeClient, namespace, jenkinsOperatorDeploymentName, 1, retryInterval, timeout) - if err != nil { - t.Fatal(err) - } - - return namespace, ctx -} diff --git a/test/e2e/mode_kubernetes.go b/test/e2e/mode_kubernetes.go index f17f0730..9408844c 100644 --- a/test/e2e/mode_kubernetes.go +++ b/test/e2e/mode_kubernetes.go @@ -4,17 +4,14 @@ package e2e import ( - "testing" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" ) const ( - skipTestSafeRestart = false - skipTestPriorityClass = false +//skipTestSafeRestart = false +//skipTestPriorityClass = false ) -func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Update Jenkins CR") +func updateJenkinsCR(jenkins *v1alpha2.Jenkins) { // do nothing } diff --git a/test/e2e/mode_openshift.go b/test/e2e/mode_openshift.go index d6aa054d..a2f6f584 100644 --- a/test/e2e/mode_openshift.go +++ b/test/e2e/mode_openshift.go @@ -3,9 +3,7 @@ package e2e import ( - "testing" - - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" corev1 "k8s.io/api/core/v1" @@ -16,9 +14,7 @@ const ( skipTestPriorityClass = true ) -func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Update Jenkins CR: OpenShift") - +func updateJenkinsCR(jenkins *v1alpha2.Jenkins) { jenkins.Spec.Master.Containers[0].Image = "quay.io/openshift/origin-jenkins" jenkins.Spec.Master.Containers[0].Command = []string{ "bash", diff --git a/test/e2e/mode_openshift_oauth.go b/test/e2e/mode_openshift_oauth.go index a832aed4..419efbec 100644 --- a/test/e2e/mode_openshift_oauth.go +++ b/test/e2e/mode_openshift_oauth.go @@ -2,6 +2,8 @@ package e2e +// TODO +/* import ( "context" "testing" @@ -100,3 +102,4 @@ func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) { jenkins.Spec.Master.Plugins = jenkins.Spec.Master.Plugins[0:3] // remove devoptics plugin } } +*/ \ No newline at end of file diff --git a/test/e2e/operator.go b/test/e2e/operator.go deleted file mode 100644 index ad810e19..00000000 --- a/test/e2e/operator.go +++ /dev/null @@ -1,155 +0,0 @@ -package e2e - -import ( - "bytes" - "fmt" - "io" - "sort" - "testing" - - framework "github.com/operator-framework/operator-sdk/pkg/test" - v1 "k8s.io/api/core/v1" - "k8s.io/api/events/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" -) - -var ( - podLogTailLimit int64 = 15 - kubernetesEventsLimit int64 = 15 - // MUST match the labels in the deployment manifest: deploy/operator.yaml - operatorPodLabels = map[string]string{ - "name": "jenkins-operator", - } -) - -func getOperatorPod(namespace string) (*v1.Pod, error) { - listOptions := metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(operatorPodLabels).String(), - } - - podList, err := framework.Global.KubeClient.CoreV1().Pods(namespace).List(listOptions) - if err != nil { - return nil, err - } - if len(podList.Items) != 1 { - return nil, fmt.Errorf("expected exactly one pod, got: '%+v'", podList) - } - - return &podList.Items[0], nil -} - -func getOperatorLogs(namespace string) (string, error) { - pod, err := getOperatorPod(namespace) - if err != nil { - return "", err - } - - logOptions := v1.PodLogOptions{TailLines: &podLogTailLimit} - req := framework.Global.KubeClient.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &logOptions) - podLogs, err := req.Stream() - if err != nil { - return "", err - } - - defer func() { - if podLogs != nil { - _ = podLogs.Close() - } - }() - - buf := new(bytes.Buffer) - _, err = io.Copy(buf, podLogs) - if err != nil { - return "", err - } - - logs := buf.String() - return logs, nil -} - -func printOperatorLogs(t *testing.T, namespace string) { - t.Logf("Operator logs in '%s' namespace:\n", namespace) - logs, err := getOperatorLogs(namespace) - if err != nil { - t.Errorf("Couldn't get the operator pod logs: %s", err) - } else { - t.Logf("Last %d lines of log from operator:\n %s", podLogTailLimit, logs) - } -} - -func getKubernetesEvents(namespace string) ([]v1beta1.Event, error) { - listOptions := metav1.ListOptions{ - Limit: kubernetesEventsLimit, - } - - events, err := framework.Global.KubeClient.EventsV1beta1().Events(namespace).List(listOptions) - if err != nil { - return nil, err - } - - sort.SliceStable(events.Items, func(i, j int) bool { - return events.Items[i].CreationTimestamp.Unix() < events.Items[j].CreationTimestamp.Unix() - }) - - return events.Items, nil -} - -func printKubernetesEvents(t *testing.T, namespace string) { - t.Logf("Kubernetes events in '%s' namespace:\n", namespace) - events, err := getKubernetesEvents(namespace) - if err != nil { - t.Errorf("Couldn't get kubernetes events: %s", err) - } else { - t.Logf("Last %d events from kubernetes:\n", kubernetesEventsLimit) - - for _, event := range events { - t.Logf("%+v\n\n", event) - } - } -} - -func getKubernetesPods(namespace string) (*v1.PodList, error) { - return framework.Global.KubeClient.CoreV1().Pods(namespace).List(metav1.ListOptions{}) -} - -func printKubernetesPods(t *testing.T, namespace string) { - t.Logf("All pods in '%s' namespace:\n", namespace) - podList, err := getKubernetesPods(namespace) - if err != nil { - t.Errorf("Couldn't get kubernetes pods: %s", err) - } - - for _, pod := range podList.Items { - t.Logf("%+v\n\n", pod) - } -} - -func showLogsIfTestHasFailed(t *testing.T, ctx *framework.Context) { - namespace, err := ctx.GetOperatorNamespace() - if err != nil { - t.Fatalf("Failed to get '%s' namespace", err) - } - - if t.Failed() { - t.Log("Test failed. Bellow here you can check logs:") - - printKubernetesEvents(t, namespace) - printKubernetesPods(t, namespace) - printOperatorLogs(t, namespace) - } -} - -func showLogsAndCleanup(t *testing.T, ctx *framework.Context) { - namespace, err := ctx.GetOperatorNamespace() - if err != nil { - t.Fatalf("Failed to get '%s' namespace", err) - } - - showLogsIfTestHasFailed(t, ctx) - - ctx.Cleanup() - if err = waitUntilNamespaceDestroyed(namespace); err != nil { - t.Fatalf("Failed to wait for namespace until destroyed '%s'", err) - } -} diff --git a/test/e2e/port_forward.go b/test/e2e/port_forward_test.go similarity index 85% rename from test/e2e/port_forward.go rename to test/e2e/port_forward_test.go index 49efcd78..12f318af 100644 --- a/test/e2e/port_forward.go +++ b/test/e2e/port_forward_test.go @@ -7,9 +7,9 @@ import ( "net/url" "os" "strings" - "testing" - framework "github.com/operator-framework/operator-sdk/pkg/test" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/cli-runtime/pkg/genericclioptions" @@ -49,11 +49,9 @@ func getFreePort() (int, error) { return l.Addr().(*net.TCPAddr).Port, nil } -func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) (port int, cleanUpFunc func(), waitFunc func(), portForwardFunc func(), err error) { +func setupPortForwardToPod(namespace, podName string, podPort int) (port int, cleanUpFunc func(), waitFunc func(), portForwardFunc func(), err error) { port, err = getFreePort() - if err != nil { - t.Fatal(err) - } + Expect(err).NotTo(HaveOccurred()) stream := genericclioptions.IOStreams{ In: os.Stdin, @@ -68,7 +66,7 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) readyCh := make(chan struct{}) req := portForwardToPodRequest{ - config: framework.Global.KubeConfig, + config: cfg, pod: v1.Pod{ ObjectMeta: metav1.ObjectMeta{ Name: podName, @@ -83,9 +81,9 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) } waitFunc = func() { - t.Log("Waiting for the port-forward.") + _, _ = fmt.Fprintf(GinkgoWriter, "Waiting for the port-forward.\n") <-readyCh - t.Log("The port-forward is established.") + _, _ = fmt.Fprintf(GinkgoWriter, "The port-forward is established.\n") } portForwardFunc = func() { @@ -96,7 +94,7 @@ func setupPortForwardToPod(t *testing.T, namespace, podName string, podPort int) } cleanUpFunc = func() { - t.Log("Closing port-forward") + _, _ = fmt.Fprintf(GinkgoWriter, "Closing port-forward\n") close(stopCh) } diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 6f36d968..15740c92 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -1,5 +1,7 @@ package e2e +// TODO +/* import ( "context" "testing" @@ -109,3 +111,4 @@ func checkBaseConfigurationCompleteTimeIsNotSet(t *testing.T, jenkins *v1alpha2. t.Fatalf("Status.BaseConfigurationCompletedTime is set after pod restart, status %+v", jenkinsStatus.Status) } } +*/ \ No newline at end of file diff --git a/test/e2e/restorebackup_test.go b/test/e2e/restorebackup_test.go index 2b09cf45..4ca42422 100644 --- a/test/e2e/restorebackup_test.go +++ b/test/e2e/restorebackup_test.go @@ -1,5 +1,7 @@ package e2e +// TODO +/* import ( "context" "testing" @@ -214,3 +216,4 @@ func resetJenkinsStatus(t *testing.T, jenkins *v1alpha2.Jenkins) { err := framework.Global.Client.Update(context.TODO(), jenkins) require.NoError(t, err) } +*/ \ No newline at end of file diff --git a/test/e2e/seedjobs_test.go b/test/e2e/seedjobs_test.go index 8ac0b11c..7268013c 100644 --- a/test/e2e/seedjobs_test.go +++ b/test/e2e/seedjobs_test.go @@ -2,24 +2,19 @@ package e2e import ( "context" - "encoding/json" "fmt" - "io/ioutil" - "os" - "testing" "text/template" "time" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/internal/render" "github.com/jenkinsci/kubernetes-operator/internal/try" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/user/seedjobs" "github.com/jenkinsci/kubernetes-operator/pkg/constants" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -32,11 +27,12 @@ type seedJobConfig struct { PrivateKey string `json:"privateKey,omitempty"` } -type seedJobsConfig struct { +/*type seedJobsConfig struct { SeedJobs []seedJobConfig `json:"seedJobs,omitempty"` -} +}*/ -func TestSeedJobs(t *testing.T) { +// FIXME +/*func TestSeedJobs(t *testing.T) { t.Parallel() if seedJobConfigurationFile == nil || len(*seedJobConfigurationFile) == 0 { t.Skipf("Skipping test because flag '%+v' is not set", seedJobConfigurationFile) @@ -81,8 +77,9 @@ func loadSeedJobsConfig(t *testing.T) seedJobsConfig { assert.NotEmpty(t, result.SeedJobs) return result } +*/ -func createKubernetesCredentialsProviderSecret(t *testing.T, namespace string, config seedJobConfig) { +func createKubernetesCredentialsProviderSecret(namespace string, config seedJobConfig) { if config.JenkinsCredentialType == v1alpha2.NoJenkinsCredentialCredentialType { return } @@ -105,31 +102,32 @@ func createKubernetesCredentialsProviderSecret(t *testing.T, namespace string, c }, } - err := framework.Global.Client.Create(context.TODO(), secret, nil) - require.NoError(t, err) + Expect(k8sClient.Create(context.TODO(), secret)).Should(Succeed()) } -func verifyJenkinsSeedJobs(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJobConfig) { +func verifyJenkinsSeedJobs(jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJobConfig) { + By("creating Jenkins jobs by seed jobs") + var err error for _, seedJob := range seedJobs { if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType { err = verifyIfJenkinsCredentialExists(jenkinsClient, seedJob.CredentialID) - assert.NoErrorf(t, err, "Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID)) } - verifySeedJobProperties(t, jenkinsClient, seedJob) + verifySeedJobProperties(jenkinsClient, seedJob) for _, requireJobName := range seedJob.JobNames { err = try.Until(func() (end bool, err error) { _, err = jenkinsClient.GetJob(requireJobName) return err == nil, err }, time.Second*2, time.Minute*2) - assert.NoErrorf(t, err, "Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID)) } } } -func verifySeedJobProperties(t *testing.T, jenkinsClient jenkinsclient.Jenkins, seedJob seedJobConfig) { +func verifySeedJobProperties(jenkinsClient jenkinsclient.Jenkins, seedJob seedJobConfig) { data := struct { ID string CredentialID string @@ -163,10 +161,10 @@ func verifySeedJobProperties(t *testing.T, jenkinsClient jenkinsclient.Jenkins, } groovyScript, err := render.Render(verifySeedJobPropertiesGroovyScriptTemplate, data) - assert.NoError(t, err, groovyScript) + Expect(err).NotTo(HaveOccurred(), groovyScript) logs, err := jenkinsClient.ExecuteScript(groovyScript) - assert.NoError(t, err, logs, groovyScript) + Expect(err).NotTo(HaveOccurred(), logs, groovyScript) } func verifyIfJenkinsCredentialExists(jenkinsClient jenkinsclient.Jenkins, credentialName string) error { diff --git a/controllers/suite_test.go b/test/e2e/suite_test.go similarity index 52% rename from controllers/suite_test.go rename to test/e2e/suite_test.go index 8c1935be..7567d1f5 100644 --- a/controllers/suite_test.go +++ b/test/e2e/suite_test.go @@ -1,33 +1,27 @@ -/* -Copyright 2021. - -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 +package e2e import ( + "context" + "fmt" "path/filepath" "testing" + "time" - jenkinsiov1alpha2 "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/controllers" jenkinsClient "github.com/jenkinsci/kubernetes-operator/pkg/client" + "github.com/jenkinsci/kubernetes-operator/pkg/constants" + "github.com/jenkinsci/kubernetes-operator/pkg/event" + "github.com/jenkinsci/kubernetes-operator/pkg/notifications" e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -37,11 +31,8 @@ import ( // +kubebuilder:scaffold:imports ) -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - var ( - //cfg *rest.Config + cfg *rest.Config k8sClient client.Client testEnv *envtest.Environment ) @@ -55,45 +46,55 @@ func TestAPIs(t *testing.T) { } var _ = BeforeSuite(func(done Done) { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(false))) By("bootstrapping test environment") + useExistingCluster := true testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + //BinaryAssetsDirectory: path.Join("..", "..", "testbin", "bin"), + UseExistingCluster: &useExistingCluster, } - cfg, err := testEnv.Start() + var err error + cfg, err = testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) - err = jenkinsiov1alpha2.AddToScheme(scheme.Scheme) + err = v1alpha2.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) // +kubebuilder:scaffold:scheme - //setup manager + // setup manager k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ Scheme: scheme.Scheme, }) Expect(err).NotTo(HaveOccurred()) - //setup controller + // setup controller clientSet, err := kubernetes.NewForConfig(cfg) Expect(err).NotTo(HaveOccurred()) + // setup events + events, err := event.New(cfg, constants.OperatorName) + Expect(err).NotTo(HaveOccurred()) notificationEvents := make(chan e.Event) + go notifications.Listen(notificationEvents, events, k8sClient) - // validate jenkins API connection - jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{} + jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{ + Hostname: "192.168.99.100", // FIXME minikube ip + UseNodePort: true, + } - err = (&JenkinsReconciler{ + err = (&controllers.JenkinsReconciler{ Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), JenkinsAPIConnectionSettings: jenkinsAPIConnectionSettings, ClientSet: *clientSet, Config: *cfg, NotificationEvents: ¬ificationEvents, - KubernetesClusterDomain: "", + KubernetesClusterDomain: "cluster.local", }).SetupWithManager(k8sManager) Expect(err).NotTo(HaveOccurred()) @@ -105,7 +106,6 @@ var _ = BeforeSuite(func(done Done) { k8sClient = k8sManager.GetClient() Expect(k8sClient).NotTo(BeNil()) close(done) - }, 60) var _ = AfterSuite(func() { @@ -113,3 +113,39 @@ var _ = AfterSuite(func() { err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) }) + +func createNamespace() *corev1.Namespace { + By("creating temporary namespace") + + namespace := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("%d", time.Now().Unix()), + }, + } + Expect(k8sClient.Create(context.TODO(), namespace)).Should(Succeed()) + return namespace +} + +func destroyNamespace(namespace *corev1.Namespace) { + By("deleting temporary namespace") + + Expect(k8sClient.Delete(context.TODO(), namespace)).Should(Succeed()) + + Eventually(func() (bool, error) { + namespaces := &corev1.NamespaceList{} + err := k8sClient.List(context.TODO(), namespaces) + if err != nil { + return false, err + } + + exists := false + for _, namespaceItem := range namespaces.Items { + if namespaceItem.Name == namespace.Name { + exists = true + break + } + } + + return !exists, nil + }, time.Second*120, time.Second).Should(BeTrue()) +} diff --git a/test/e2e/wait.go b/test/e2e/wait.go deleted file mode 100644 index ff3919bb..00000000 --- a/test/e2e/wait.go +++ /dev/null @@ -1,134 +0,0 @@ -package e2e - -import ( - goctx "context" - "net/http" - "testing" - "time" - - "golang.org/x/net/context" - - "github.com/jenkinsci/kubernetes-operator/internal/try" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" - jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" - "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" - - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/wait" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var ( - retryInterval = time.Second * 5 - timeout = time.Second * 60 -) - -// checkConditionFunc is used to check if a condition for the jenkins CR is set -type checkConditionFunc func(*v1alpha2.Jenkins, error) bool - -func waitForJenkinsBaseConfigurationToComplete(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Waiting for Jenkins base configuration to complete") - _, err := WaitUntilJenkinsConditionSet(retryInterval, 170, jenkins, func(jenkins *v1alpha2.Jenkins, err error) bool { - t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err) - return err == nil && jenkins.Status.BaseConfigurationCompletedTime != nil - }) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins pod is running") - - // update jenkins CR because Operator sets default values - namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} - err = framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkins) - assert.NoError(t, err) -} - -func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) { - err := wait.Poll(retryInterval, 30*retryInterval, func() (bool, error) { - lo := metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(), - } - podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo) - if err != nil { - return false, err - } - if len(podList.Items) != 1 { - return false, nil - } - - return podList.Items[0].DeletionTimestamp == nil, nil - }) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins pod has been recreated") -} - -func waitForJenkinsUserConfigurationToComplete(t *testing.T, jenkins *v1alpha2.Jenkins) { - t.Log("Waiting for Jenkins user configuration to complete") - _, err := WaitUntilJenkinsConditionSet(retryInterval, 110, jenkins, func(jenkins *v1alpha2.Jenkins, err error) bool { - t.Logf("Current Jenkins status: '%+v', error '%s'", jenkins.Status, err) - return err == nil && jenkins.Status.UserConfigurationCompletedTime != nil - }) - if err != nil { - t.Fatal(err) - } - t.Log("Jenkins pod is running") -} - -func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) { - err := try.Until(func() (end bool, err error) { - status, err := jenkinsClient.Poll() - if err != nil { - return false, err - } - if status != http.StatusOK { - return false, errors.Wrap(err, "couldn't poll data from Jenkins API") - } - return true, nil - }, time.Second, time.Second*70) - require.NoError(t, err) -} - -// WaitUntilJenkinsConditionSet retries until the specified condition check becomes true for the jenkins CR -func WaitUntilJenkinsConditionSet(retryInterval time.Duration, retries int, jenkins *v1alpha2.Jenkins, checkCondition checkConditionFunc) (*v1alpha2.Jenkins, error) { - jenkinsStatus := &v1alpha2.Jenkins{} - err := wait.Poll(retryInterval, time.Duration(retries)*retryInterval, func() (bool, error) { - namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} - err := framework.Global.Client.Get(goctx.TODO(), namespacedName, jenkinsStatus) - return checkCondition(jenkinsStatus, err), nil - }) - if err != nil { - return nil, err - } - return jenkinsStatus, nil -} - -func waitUntilNamespaceDestroyed(namespace string) error { - err := try.Until(func() (bool, error) { - var namespaceList v1.NamespaceList - err := framework.Global.Client.List(context.TODO(), &namespaceList, &client.ListOptions{}) - if err != nil { - return true, err - } - - exists := false - for _, namespaceItem := range namespaceList.Items { - if namespaceItem.Name == namespace { - exists = true - break - } - } - - return !exists, nil - }, time.Second, time.Second*120) - - return err -} diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go new file mode 100644 index 00000000..255e3a3e --- /dev/null +++ b/test/e2e/wait_test.go @@ -0,0 +1,87 @@ +package e2e + +import ( + "context" + "fmt" + "time" + + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" +) + +var ( + retryInterval = time.Second * 5 +) + +func waitForJenkinsBaseConfigurationToComplete(jenkins *v1alpha2.Jenkins) { + By("waiting for Jenkins base configuration phase to complete") + + Eventually(func() (*metav1.Time, error) { + actualJenkins := &v1alpha2.Jenkins{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, actualJenkins) + if err != nil { + return nil, err + } + + return actualJenkins.Status.BaseConfigurationCompletedTime, nil + }, time.Duration(170)*retryInterval, retryInterval).Should(Not(BeNil())) + + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins pod is running\n") + + // update jenkins CR because Operator sets default values + namespacedName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} + Expect(k8sClient.Get(context.TODO(), namespacedName, jenkins)).Should(Succeed()) +} + +/*func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) { + err := wait.Poll(retryInterval, 30*retryInterval, func() (bool, error) { + lo := metav1.ListOptions{ + LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(), + } + podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo) + if err != nil { + return false, err + } + if len(podList.Items) != 1 { + return false, nil + } + + return podList.Items[0].DeletionTimestamp == nil, nil + }) + if err != nil { + t.Fatal(err) + } + _, _ = fmt.Fprintf(GinkgoWriter,"Jenkins pod has been recreated") +}*/ + +func waitForJenkinsUserConfigurationToComplete(jenkins *v1alpha2.Jenkins) { + By("waiting for Jenkins user configuration phase to complete") + + Eventually(func() (*metav1.Time, error) { + actualJenkins := &v1alpha2.Jenkins{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, actualJenkins) + if err != nil { + return nil, err + } + + return actualJenkins.Status.UserConfigurationCompletedTime, nil + }, time.Duration(110)*retryInterval, retryInterval).Should(Not(BeNil())) + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins instance is up and ready\n") +} + +/*func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) { + err := try.Until(func() (end bool, err error) { + status, err := jenkinsClient.Poll() + if err != nil { + return false, err + } + if status != http.StatusOK { + return false, errors.Wrap(err, "couldn't poll data from Jenkins API") + } + return true, nil + }, time.Second, time.Second*70) + require.NoError(t, err) +}*/ diff --git a/variables.mk b/variables.mk index 6648c14b..582ba15f 100644 --- a/variables.mk +++ b/variables.mk @@ -1,5 +1,4 @@ -# Set POSIX sh for maximum interoperability -SHELL := /bin/sh +SHELL := /bin/bash PATH := $(GOPATH)/bin:$(PATH) OSFLAG := @@ -60,7 +59,7 @@ GO_LDFLAGS_STATIC=-ldflags "-w $(CTIMEVAR) -extldflags -static" GOOSARCHES = linux/amd64 PACKAGES = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor) -PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e) +PACKAGES_FOR_UNIT_TESTS = $(shell go list -f '{{.ImportPath}}/' ./... | grep -v vendor | grep -v e2e | grep -v controllers) # Run all the e2e tests by default E2E_TEST_SELECTOR ?= .* @@ -93,4 +92,6 @@ ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin else GOBIN=$(shell go env GOBIN) -endif \ No newline at end of file +endif + +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin \ No newline at end of file From 363b9e0fc031884ba957cd8fb836249fdd49eac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Wed, 27 Jan 2021 10:05:43 +0100 Subject: [PATCH 04/15] Resolve some FIXME --- Makefile | 2 +- controllers/handler.go | 8 +++++--- controllers/jenkins_controller.go | 3 +++ test/e2e/helm_test.go | 2 +- test/e2e/mode_openshift_oauth.go | 2 +- test/e2e/restart_test.go | 6 +++--- test/e2e/restorebackup_test.go | 2 +- test/e2e/suite_test.go | 16 ++++++++++++++-- 8 files changed, 29 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index 3e9a0bff..4529758e 100644 --- a/Makefile +++ b/Makefile @@ -92,7 +92,7 @@ test: ## Runs the go tests e2e: deepcopy-gen ## Runs e2e tests, you can use EXTRA_ARGS @echo "+ $@" RUNNING_TESTS=1 go test -parallel=1 "./test/e2e/" -tags "$(BUILDTAGS) cgo" -v -timeout 60m -run "$(E2E_TEST_SELECTOR)" \ - $(TEST_ARGS) + -jenkins-api-hostname=$(JENKINS_API_HOSTNAME) -jenkins-api-port=$(JENKINS_API_PORT) -jenkins-api-use-nodeport=$(JENKINS_API_USE_NODEPORT) $(E2E_TEST_ARGS) .PHONY: vet vet: ## Verifies `go vet` passes diff --git a/controllers/handler.go b/controllers/handler.go index 68414a36..627d01f9 100644 --- a/controllers/handler.go +++ b/controllers/handler.go @@ -2,7 +2,9 @@ package controllers import ( "fmt" + "reflect" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/constants" "github.com/jenkinsci/kubernetes-operator/pkg/log" @@ -10,6 +12,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -72,8 +75,7 @@ func (e *enqueueRequestForJenkins) getOwnerReconcileRequests(object metav1.Objec return nil } -// FIXME unused -/*type jenkinsDecorator struct { +type jenkinsDecorator struct { handler handler.EventHandler } @@ -95,4 +97,4 @@ func (e *jenkinsDecorator) Delete(evt event.DeleteEvent, q workqueue.RateLimitin func (e *jenkinsDecorator) Generic(evt event.GenericEvent, q workqueue.RateLimitingInterface) { e.handler.Generic(evt, q) -}*/ +} diff --git a/controllers/jenkins_controller.go b/controllers/jenkins_controller.go index fc7526e5..fe0e992b 100644 --- a/controllers/jenkins_controller.go +++ b/controllers/jenkins_controller.go @@ -44,6 +44,7 @@ import ( "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" ) @@ -80,6 +81,7 @@ func (r *JenkinsReconciler) SetupWithManager(mgr ctrl.Manager) error { jenkinsHandler := &enqueueRequestForJenkins{} configMapResource := &source.Kind{Type: &corev1.ConfigMap{TypeMeta: metav1.TypeMeta{APIVersion: APIVersion, Kind: ConfigMapKind}}} secretResource := &source.Kind{Type: &corev1.Secret{TypeMeta: metav1.TypeMeta{APIVersion: APIVersion, Kind: SecretKind}}} + decorator := jenkinsDecorator{handler: &handler.EnqueueRequestForObject{}} return ctrl.NewControllerManagedBy(mgr). For(&v1alpha2.Jenkins{}). Owns(&corev1.Pod{}). @@ -87,6 +89,7 @@ func (r *JenkinsReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.ConfigMap{}). Watches(secretResource, jenkinsHandler). Watches(configMapResource, jenkinsHandler). + Watches(&source.Kind{Type: &v1alpha2.Jenkins{}}, &decorator). Complete(r) } diff --git a/test/e2e/helm_test.go b/test/e2e/helm_test.go index 73d495da..dd915044 100644 --- a/test/e2e/helm_test.go +++ b/test/e2e/helm_test.go @@ -68,4 +68,4 @@ func TestDeployHelmChart(t *testing.T) { waitForJenkinsBaseConfigurationToComplete(t, jenkins) waitForJenkinsUserConfigurationToComplete(t, jenkins) } -/* \ No newline at end of file +*/ diff --git a/test/e2e/mode_openshift_oauth.go b/test/e2e/mode_openshift_oauth.go index 419efbec..30c31d5a 100644 --- a/test/e2e/mode_openshift_oauth.go +++ b/test/e2e/mode_openshift_oauth.go @@ -102,4 +102,4 @@ func updateJenkinsCR(t *testing.T, jenkins *v1alpha2.Jenkins) { jenkins.Spec.Master.Plugins = jenkins.Spec.Master.Plugins[0:3] // remove devoptics plugin } } -*/ \ No newline at end of file +*/ diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index 15740c92..b91611e6 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -90,9 +90,9 @@ jenkins.save() func checkIfAuthorizationStrategyUnsecuredIsSet(t *testing.T, jenkinsClient jenkinsclient.Jenkins) { logs, err := jenkinsClient.ExecuteScript(` import hudson.security.* - + def jenkins = jenkins.model.Jenkins.getInstance() - + if (!(jenkins.getAuthorizationStrategy() instanceof AuthorizationStrategy.Unsecured)) { throw new Exception('AuthorizationStrategy.Unsecured is not set') } @@ -111,4 +111,4 @@ func checkBaseConfigurationCompleteTimeIsNotSet(t *testing.T, jenkins *v1alpha2. t.Fatalf("Status.BaseConfigurationCompletedTime is set after pod restart, status %+v", jenkinsStatus.Status) } } -*/ \ No newline at end of file +*/ diff --git a/test/e2e/restorebackup_test.go b/test/e2e/restorebackup_test.go index 4ca42422..eb4b462e 100644 --- a/test/e2e/restorebackup_test.go +++ b/test/e2e/restorebackup_test.go @@ -216,4 +216,4 @@ func resetJenkinsStatus(t *testing.T, jenkins *v1alpha2.Jenkins) { err := framework.Global.Client.Update(context.TODO(), jenkins) require.NoError(t, err) } -*/ \ No newline at end of file +*/ diff --git a/test/e2e/suite_test.go b/test/e2e/suite_test.go index 7567d1f5..eca535e4 100644 --- a/test/e2e/suite_test.go +++ b/test/e2e/suite_test.go @@ -2,6 +2,7 @@ package e2e import ( "context" + "flag" "fmt" "path/filepath" "testing" @@ -35,8 +36,18 @@ var ( cfg *rest.Config k8sClient client.Client testEnv *envtest.Environment + + hostname *string + port *int + useNodePort *bool ) +func init() { + hostname = flag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") + port = flag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") + useNodePort = flag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") +} + func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) @@ -83,8 +94,9 @@ var _ = BeforeSuite(func(done Done) { go notifications.Listen(notificationEvents, events, k8sClient) jenkinsAPIConnectionSettings := jenkinsClient.JenkinsAPIConnectionSettings{ - Hostname: "192.168.99.100", // FIXME minikube ip - UseNodePort: true, + Hostname: *hostname, + UseNodePort: *useNodePort, + Port: *port, } err = (&controllers.JenkinsReconciler{ From 34ffdde054888dd791af7a7e8eb9b0d8922894f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Wed, 27 Jan 2021 11:29:38 +0100 Subject: [PATCH 05/15] Resolve some FIXME --- pkg/configuration/base/rbac.go | 7 ++++++- pkg/configuration/base/reconcile_test.go | 5 ++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pkg/configuration/base/rbac.go b/pkg/configuration/base/rbac.go index 43a8db5c..711e1d29 100644 --- a/pkg/configuration/base/rbac.go +++ b/pkg/configuration/base/rbac.go @@ -6,8 +6,10 @@ import ( "strings" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" + stackerr "github.com/pkg/errors" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -43,7 +45,10 @@ func (r *JenkinsBaseConfigurationReconciler) ensureExtraRBAC(meta metav1.ObjectM for _, roleRef := range r.Configuration.Jenkins.Spec.Roles { name = getExtraRoleBindingName(meta.Name, roleRef) roleBinding := resources.NewRoleBinding(name, meta.Namespace, meta.Name, roleRef) - err = r.CreateOrUpdateResource(roleBinding) + err := r.Client.Create(context.TODO(), roleBinding) + if err != nil && errors.IsAlreadyExists(err) { + continue + } if err != nil { return stackerr.WithStack(err) } diff --git a/pkg/configuration/base/reconcile_test.go b/pkg/configuration/base/reconcile_test.go index 556e7c54..246d3529 100644 --- a/pkg/configuration/base/reconcile_test.go +++ b/pkg/configuration/base/reconcile_test.go @@ -743,7 +743,6 @@ func TestEnsureExtraRBAC(t *testing.T) { assert.Equal(t, jenkins.Spec.Roles[1], roleBindings.Items[2].RoleRef) }) t.Run("delete one extra", func(t *testing.T) { - t.Skip() //FIXME // given fakeClient := fake.NewClientBuilder().Build() err := v1alpha2.SchemeBuilder.AddToScheme(scheme.Scheme) @@ -803,8 +802,8 @@ func TestEnsureExtraRBAC(t *testing.T) { roleBindings, err := fetchAllRoleBindings(fakeClient) assert.NoError(t, err) assert.Equal(t, 3, len(roleBindings.Items)) - assert.Equal(t, metaObject.Name, roleBindings.Items[1].Name) - assert.Equal(t, jenkins.Spec.Roles[0], roleBindings.Items[2].RoleRef) + assert.Equal(t, metaObject.Name, roleBindings.Items[0].Name) + assert.Equal(t, jenkins.Spec.Roles[0], roleBindings.Items[1].RoleRef) }) } From 53961d7bdef2bac4597df863fccc759392f0c0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Wed, 27 Jan 2021 12:32:08 +0100 Subject: [PATCH 06/15] Fix logging --- main.go | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/main.go b/main.go index 48b41a9f..3b3255eb 100644 --- a/main.go +++ b/main.go @@ -28,6 +28,7 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/constants" "github.com/jenkinsci/kubernetes-operator/pkg/event" + "github.com/jenkinsci/kubernetes-operator/pkg/log" "github.com/jenkinsci/kubernetes-operator/pkg/notifications" e "github.com/jenkinsci/kubernetes-operator/pkg/notifications/event" "github.com/jenkinsci/kubernetes-operator/version" @@ -55,9 +56,8 @@ var ( metricsHost = "0.0.0.0" metricsPort int32 = 8383 scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") + logger = logf.Log.WithName("cmd") ) -var logger = logf.Log.WithName("cmd") func printInfo() { logger.Info(fmt.Sprintf("Version: %s", version.Version)) @@ -91,26 +91,24 @@ func main() { hostname := flag.String("jenkins-api-hostname", "", "Hostname or IP of Jenkins API. It can be service name, node IP or localhost.") port := flag.Int("jenkins-api-port", 0, "The port on which Jenkins API is running. Note: If you want to use nodePort don't set this setting and --jenkins-api-use-nodeport must be true.") useNodePort := flag.Bool("jenkins-api-use-nodeport", false, "Connect to Jenkins API using the service nodePort instead of service port. If you want to set this as true - don't set --jenkins-api-port.") - debug := flag.Bool("debug", false, "Set log level to debug") kubernetesClusterDomain := flag.String("cluster-domain", "cluster.local", "Use custom domain name instead of 'cluster.local'.") - //TODO fix logs opts := zap.Options{ Development: true, } opts.BindFlags(flag.CommandLine) flag.Parse() + debug := &opts.Development + log.Debug = *debug ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) printInfo() namespace, found := os.LookupEnv("WATCH_NAMESPACE") if !found { - var err error - fatal(errors.Wrap(err, "failed to get watch namespace, please set up WATCH_NAMESPACE environment variable"), *debug) + fatal(errors.New("failed to get watch namespace, please set up WATCH_NAMESPACE environment variable"), *debug) } logger.Info(fmt.Sprintf("Watch namespace: %v", namespace)) - //Config // get a config to talk to the API server cfg, err := config.GetConfig() if err != nil { @@ -126,8 +124,7 @@ func main() { LeaderElectionID: "c674355f.jenkins.io", }) if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) + fatal(errors.Wrap(err, "unable to start manager"), *debug) } // setup events @@ -181,20 +178,15 @@ func main() { // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up health check") - os.Exit(1) + fatal(errors.Wrap(err, "unable to set up health check"), *debug) } if err := mgr.AddReadyzCheck("check", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up ready check") - os.Exit(1) + fatal(errors.Wrap(err, "unable to set up ready check"), *debug) } - logger.Info("Starting the Cmd.") - - setupLog.Info("starting manager") + logger.Info("starting manager") if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - setupLog.Error(err, "problem running manager") - os.Exit(1) + fatal(errors.Wrap(err, "problem running manager"), *debug) } } @@ -204,5 +196,5 @@ func fatal(err error, debug bool) { } else { logger.Error(nil, fmt.Sprintf("%s", err)) } - os.Exit(-1) + os.Exit(1) } From e03b3f25a5f24d52fe93cbf1ecf7def81b618fdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Wed, 27 Jan 2021 14:29:05 +0100 Subject: [PATCH 07/15] Fix container-runtime-build Makefile goal --- Dockerfile | 8 +++++--- Makefile | 6 +++--- build/Dockerfile | 21 --------------------- pkg/event/event.go | 1 - 4 files changed, 8 insertions(+), 28 deletions(-) delete mode 100644 build/Dockerfile diff --git a/Dockerfile b/Dockerfile index da398ee5..ff840ea4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,8 @@ +ARG GO_VERSION + # Build the manager binary -FROM golang:1.15 as builder +FROM golang:$GO_VERSION as builder +ARG CTIMEVAR WORKDIR /workspace # Copy the Go Modules manifests @@ -18,8 +21,7 @@ COPY version/ version/ COPY main.go main.go # Build -# FIXME look at makefile build -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -ldflags "-w $CTIMEVAR" -o manager main.go # Use distroless as minimal base image to package the manager binary # Refer to https://github.com/GoogleContainerTools/distroless for more details diff --git a/Makefile b/Makefile index 4529758e..8abf5581 100644 --- a/Makefile +++ b/Makefile @@ -201,13 +201,13 @@ container-runtime-login: ## Log in into the Docker repository @echo "+ $@" .PHONY: container-runtime-build -container-runtime-build: check-env ## Build the container +container-runtime-build: check-env deepcopy-gen ## Build the container @echo "+ $@" $(CONTAINER_RUNTIME_COMMAND) build \ --build-arg GO_VERSION=$(GO_VERSION) \ - --build-arg OPERATOR_SDK_VERSION=$(OPERATOR_SDK_VERSION) \ + --build-arg CTIMEVAR="$(CTIMEVAR)" \ -t $(DOCKER_REGISTRY):$(GITCOMMIT) . \ - --file build/Dockerfile $(CONTAINER_RUNTIME_EXTRA_ARGS) + --file Dockerfile $(CONTAINER_RUNTIME_EXTRA_ARGS) .PHONY: container-runtime-images container-runtime-images: ## List all local containers diff --git a/build/Dockerfile b/build/Dockerfile deleted file mode 100644 index 1615bd64..00000000 --- a/build/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -ARG GO_VERSION - -# build stage -FROM golang:$GO_VERSION-alpine3.11 AS build-stage -ARG OPERATOR_SDK_VERSION -ENV GO111MODULE=on -RUN apk --no-cache add git curl make \ - && curl -L https://github.com/operator-framework/operator-sdk/releases/download/v$OPERATOR_SDK_VERSION/operator-sdk-v$OPERATOR_SDK_VERSION-x86_64-linux-gnu -o /usr/local/bin/operator-sdk \ - && chmod +x /usr/local/bin/operator-sdk -ADD . /kubernetes-operator - -RUN cd /kubernetes-operator && make build - -# run stage -FROM alpine:3.10 - -USER nobody - -COPY --from=build-stage /kubernetes-operator/build/_output/bin/jenkins-operator /usr/local/bin/jenkins-operator - -CMD [ "/usr/local/bin/jenkins-operator" ] diff --git a/pkg/event/event.go b/pkg/event/event.go index ca0a9e0a..ce0713eb 100644 --- a/pkg/event/event.go +++ b/pkg/event/event.go @@ -54,7 +54,6 @@ func initializeEventRecorder(config *rest.Config, component string) (record.Even return nil, errors.WithStack(err) } eventBroadcaster := record.NewBroadcaster() - //eventBroadcaster.StartLogging(glog.Infof) TODO integrate with proper logger eventBroadcaster.StartRecordingToSink( &typedcorev1.EventSinkImpl{ Interface: client.CoreV1().Events("")}) From ea861d8bd5098e324c7ce4430d40eedf641aa768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Wed, 27 Jan 2021 16:12:47 +0100 Subject: [PATCH 08/15] Download all dependent binaries into repo/bin --- Makefile | 28 ++++++++++++++-------------- main.go | 2 +- variables.mk | 4 +++- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 8abf5581..350494e7 100644 --- a/Makefile +++ b/Makefile @@ -66,22 +66,22 @@ fmt: ## Verifies all files have been `gofmt`ed @go fmt $(PACKAGES) .PHONY: lint -HAS_GOLINT := $(shell which golangci-lint) +HAS_GOLINT := $(shell which $(PROJECT_DIR)/bin/golangci-lint) lint: ## Verifies `golint` passes @echo "+ $@" ifndef HAS_GOLINT - go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.26.0 + $(call go-get-tool,$(PROJECT_DIR)/bin/golangci-lint,github.com/golangci/golangci-lint/cmd/golangci-lint@v1.26.0) endif - @golangci-lint run + @bin/golangci-lint run .PHONY: goimports -HAS_GOIMPORTS := $(shell which goimports) +HAS_GOIMPORTS := $(shell which $(PROJECT_DIR)/bin/goimports) goimports: ## Verifies `goimports` passes @echo "+ $@" ifndef HAS_GOIMPORTS - go get -u golang.org/x/tools/cmd/goimports + $(call go-get-tool,$(PROJECT_DIR)/bin/goimports,golang.org/x/tools/cmd/goimports@v0.1.0) endif - @goimports -l -e $(shell find . -type f -name '*.go' -not -path "./vendor/*") + @bin/goimports -l -e $(shell find . -type f -name '*.go' -not -path "./vendor/*") .PHONY: test test: ## Runs the go tests @@ -99,19 +99,20 @@ vet: ## Verifies `go vet` passes @echo "+ $@" @go vet $(PACKAGES) -#FIXME download to tmp not locally .PHONY: staticcheck -HAS_STATICCHECK := $(shell which staticcheck) +HAS_STATICCHECK := $(shell which $(PROJECT_DIR)/bin/staticcheck) PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) staticcheck: ## Verifies `staticcheck` passes @echo "+ $@" ifndef HAS_STATICCHECK - wget -O staticcheck_$(PLATFORM)_amd64.tar.gz https://github.com/dominikh/go-tools/releases/download/2020.1.3/staticcheck_$(PLATFORM)_amd64.tar.gz - tar zxvf staticcheck_$(PLATFORM)_amd64.tar.gz - mkdir -p $(GOPATH)/bin - mv staticcheck/staticcheck $(GOPATH)/bin + $(eval TMP_DIR := $(shell mktemp -d)) + wget -O $(TMP_DIR)/staticcheck_$(PLATFORM)_amd64.tar.gz https://github.com/dominikh/go-tools/releases/download/2020.1.3/staticcheck_$(PLATFORM)_amd64.tar.gz + tar zxvf $(TMP_DIR)/staticcheck_$(PLATFORM)_amd64.tar.gz -C $(TMP_DIR) + mkdir -p $(PROJECT_DIR)/bin + mv $(TMP_DIR)/staticcheck/staticcheck $(PROJECT_DIR)/bin + rm -rf $(TMP_DIR) endif - @staticcheck $(PACKAGES) + @$(PROJECT_DIR)/bin/staticcheck $(PACKAGES) .PHONY: cover cover: ## Runs go test with coverage @@ -410,7 +411,6 @@ kustomize: $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) # go-get-tool will 'go get' any package $2 and install it to $1. -PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) define go-get-tool @[ -f $(1) ] || { \ set -e ;\ diff --git a/main.go b/main.go index 3b3255eb..949adda9 100644 --- a/main.go +++ b/main.go @@ -69,7 +69,7 @@ func printInfo() { func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1alpha2.AddToScheme(scheme)) - utilruntime.Must(routev1.AddToScheme(scheme)) // FIXME optional + utilruntime.Must(routev1.AddToScheme(scheme)) utilruntime.Must(corev1.AddToScheme(scheme)) // +kubebuilder:scaffold:scheme } diff --git a/variables.mk b/variables.mk index 582ba15f..2d4b2e28 100644 --- a/variables.mk +++ b/variables.mk @@ -94,4 +94,6 @@ else GOBIN=$(shell go env GOBIN) endif -ENVTEST_ASSETS_DIR=$(shell pwd)/testbin \ No newline at end of file +ENVTEST_ASSETS_DIR=$(shell pwd)/testbin + +PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) \ No newline at end of file From 814f5d77c15cdfbf01d55ef1385c975623ccfa30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Wed, 27 Jan 2021 16:32:16 +0100 Subject: [PATCH 09/15] Fix scheme-doc-gen Makefile goal --- Makefile | 2 +- api/v1alpha2/doc.go | 5 ++++ api/v1alpha2/groupversion_info.go | 36 ------------------------ api/v1alpha2/jenkins_types.go | 27 +++++++----------- api/v1alpha2/register.go | 10 +++++++ config/crd/bases/jenkins.io_jenkins.yaml | 4 +-- 6 files changed, 28 insertions(+), 56 deletions(-) create mode 100644 api/v1alpha2/doc.go delete mode 100644 api/v1alpha2/groupversion_info.go diff --git a/Makefile b/Makefile index 350494e7..62fdcefd 100644 --- a/Makefile +++ b/Makefile @@ -284,7 +284,7 @@ ifndef HAS_GEN_CRD_API_REFERENCE_DOCS @tar -C $(GEN_CRD_API) -zxf $(GEN_CRD_API)_linux_amd64.tar.gz @rm $(GEN_CRD_API)_linux_amd64.tar.gz endif - $(GEN_CRD_API)/$(GEN_CRD_API) -config gen-crd-api-config.json -api-dir github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/$(API_VERSION) -template-dir $(GEN_CRD_API)/template -out-file documentation/$(VERSION)/jenkins-$(API_VERSION)-scheme.md + $(GEN_CRD_API)/$(GEN_CRD_API) -config gen-crd-api-config.json -api-dir $(PKG)/api/$(API_VERSION) -template-dir $(GEN_CRD_API)/template -out-file documentation/$(VERSION)/jenkins-$(API_VERSION)-scheme.md .PHONY: check-minikube check-minikube: ## Checks if KUBERNETES_PROVIDER is set to minikube diff --git a/api/v1alpha2/doc.go b/api/v1alpha2/doc.go new file mode 100644 index 00000000..62442c14 --- /dev/null +++ b/api/v1alpha2/doc.go @@ -0,0 +1,5 @@ +// Package v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group +// +k8s:deepcopy-gen=package,register +// +kubebuilder:object:generate=true +// +groupName=jenkins.io +package v1alpha2 diff --git a/api/v1alpha2/groupversion_info.go b/api/v1alpha2/groupversion_info.go deleted file mode 100644 index 93674fa3..00000000 --- a/api/v1alpha2/groupversion_info.go +++ /dev/null @@ -1,36 +0,0 @@ -/* -Copyright 2021. - -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 v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group -// +kubebuilder:object:generate=true -// +groupName=jenkins.io -package v1alpha2 - -import ( - "k8s.io/apimachinery/pkg/runtime/schema" - "sigs.k8s.io/controller-runtime/pkg/scheme" -) - -var ( - // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "jenkins.io", Version: "v1alpha2"} - - // SchemeBuilder is used to add go types to the GroupVersionKind scheme - SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} - - // AddToScheme adds the types in this group-version to the given scheme. - AddToScheme = SchemeBuilder.AddToScheme -) diff --git a/api/v1alpha2/jenkins_types.go b/api/v1alpha2/jenkins_types.go index 7b130633..0895cd96 100644 --- a/api/v1alpha2/jenkins_types.go +++ b/api/v1alpha2/jenkins_types.go @@ -1,19 +1,3 @@ -/* -Copyright 2021. - -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 v1alpha2 import ( @@ -23,6 +7,7 @@ import ( ) // JenkinsSpec defines the desired state of Jenkins +// +k8s:openapi-gen=true type JenkinsSpec struct { // Master represents Jenkins master pod properties and Jenkins plugins. // Every single change here requires a pod restart. @@ -449,6 +434,7 @@ type Service struct { } // JenkinsStatus defines the observed state of Jenkins +// +k8s:openapi-gen=true type JenkinsStatus struct { // OperatorVersion is the operator version which manages this CR // +optional @@ -497,17 +483,24 @@ type JenkinsStatus struct { // +kubebuilder:object:root=true // +kubebuilder:subresource:status +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // Jenkins is the Schema for the jenkins API +// +k8s:openapi-gen=true type Jenkins struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec JenkinsSpec `json:"spec,omitempty"` + // Spec defines the desired state of the Jenkins + Spec JenkinsSpec `json:"spec,omitempty"` + + // Status defines the observed state of Jenkins Status JenkinsStatus `json:"status,omitempty"` } // +kubebuilder:object:root=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // JenkinsList contains a list of Jenkins type JenkinsList struct { diff --git a/api/v1alpha2/register.go b/api/v1alpha2/register.go index 8013c209..d3e4c040 100644 --- a/api/v1alpha2/register.go +++ b/api/v1alpha2/register.go @@ -6,6 +6,7 @@ package v1alpha2 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" + "sigs.k8s.io/controller-runtime/pkg/scheme" ) const ( @@ -14,8 +15,17 @@ const ( ) var ( + // GroupVersion is group version used to register these objects + GroupVersion = schema.GroupVersion{Group: "jenkins.io", Version: "v1alpha2"} + // SchemeGroupVersion is group version used to register these objects SchemeGroupVersion = schema.GroupVersion{Group: "jenkins.io", Version: "v1alpha2"} + + // SchemeBuilder is used to add go types to the GroupVersionKind scheme + SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} + + // AddToScheme adds the types in this group-version to the given scheme. + AddToScheme = SchemeBuilder.AddToScheme ) // GetObjectKind returns Jenkins object kind diff --git a/config/crd/bases/jenkins.io_jenkins.yaml b/config/crd/bases/jenkins.io_jenkins.yaml index 1f86fe9a..0ecdbf00 100644 --- a/config/crd/bases/jenkins.io_jenkins.yaml +++ b/config/crd/bases/jenkins.io_jenkins.yaml @@ -34,7 +34,7 @@ spec: metadata: type: object spec: - description: JenkinsSpec defines the desired state of Jenkins + description: Spec defines the desired state of the Jenkins properties: backup: description: 'Backup defines configuration of Jenkins backup More @@ -3321,7 +3321,7 @@ spec: - master type: object status: - description: JenkinsStatus defines the observed state of Jenkins + description: Status defines the observed state of Jenkins properties: appliedGroovyScripts: description: AppliedGroovyScripts is a list with all applied groovy From 294be87c64d7c5cdb4308dc39689a9b1882c30b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tomasz=20S=C4=99k?= Date: Thu, 28 Jan 2021 16:16:21 +0100 Subject: [PATCH 10/15] Makefile cleanup --- Makefile | 55 +++++++++++++++++++++++------ operator-sdk.mk | 94 ------------------------------------------------- 2 files changed, 44 insertions(+), 105 deletions(-) delete mode 100644 operator-sdk.mk diff --git a/Makefile b/Makefile index 62fdcefd..4490caeb 100644 --- a/Makefile +++ b/Makefile @@ -313,12 +313,21 @@ crc-start: check-crc ## Start CodeReady Containers Kubernetes cluster @echo "+ $@" crc start +.PHONY: sembump +HAS_SEMBUMP := $(shell which $(PROJECT_DIR)/bin/sembump) +PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) +sembump: # Download sembump locally if necessary + @echo "+ $@" +ifndef HAS_SEMBUMP + wget -O $(PROJECT_DIR)/bin/sembump https://github.com/justintout/sembump/releases/download/v0.1.0/sembump-$(PLATFORM)-amd64 + chmod +x $(PROJECT_DIR)/bin/sembump +endif + .PHONY: bump-version BUMP := patch -bump-version: ## Bump the version in the version file. Set BUMP to [ patch | major | minor ] +bump-version: sembump ## Bump the version in the version file. Set BUMP to [ patch | major | minor ] @echo "+ $@" - #@go get -u github.com/jessfraz/junk/sembump # update sembump tool FIXME - $(eval NEW_VERSION=$(shell sembump --kind $(BUMP) $(VERSION))) + $(eval NEW_VERSION=$(shell bin/sembump --kind $(BUMP) $(VERSION))) @echo "Bumping VERSION.txt from $(VERSION) to $(NEW_VERSION)" echo $(NEW_VERSION) > VERSION.txt @echo "Updating version from $(VERSION) to $(NEW_VERSION) in README.md" @@ -377,11 +386,16 @@ helm-deploy: helm-package helm repo index chart/ --url https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart/jenkins-operator/ cd chart/ && mv jenkins-operator-*.tgz jenkins-operator +# Download hugo locally if necessary +HUGO = $(shell pwd)/bin/hugo +hugo: + $(call go-get-tool,$(HUGO),github.com/gohugoio/hugo@v0.62.2) + .PHONY: generate-docs -generate-docs: ## Re-generate docs directory from the website directory +generate-docs: hugo ## Re-generate docs directory from the website directory @echo "+ $@" rm -rf docs || echo "Cannot remove docs dir, ignoring" - hugo -s website -d ../docs + bin/hugo -s website -d ../docs ##################### FROM OPERATOR SDK ######################## # Install CRDs into a cluster @@ -389,9 +403,18 @@ install-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl apply -f - # Uninstall CRDs from a cluster -uninstall: manifests kustomize +uninstall-crds: manifests kustomize $(KUSTOMIZE) build config/crd | kubectl delete -f - +# Deploy controller in the configured Kubernetes cluster in ~/.kube/config +deploy: manifests kustomize + cd config/manager && $(KUSTOMIZE) edit set image controller=$(DOCKER_REGISTRY):$(GITCOMMIT) + $(KUSTOMIZE) build config/default | kubectl apply -f - + +# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config +undeploy: + $(KUSTOMIZE) build config/default | kubectl delete -f - + # Generate manifests e.g. CRD, RBAC etc. manifests: controller-gen $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases @@ -423,13 +446,23 @@ rm -rf $$TMP_DIR ;\ } endef +.PHONY: operator-sdk +HAS_OPERATOR_SDK := $(shell which $(PROJECT_DIR)/bin/operator-sdk) +PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) +operator-sdk: # Download operator-sdk locally if necessary + @echo "+ $@" +ifndef HAS_OPERATOR_SDK + wget -O $(PROJECT_DIR)/bin/operator-sdk https://github.com/operator-framework/operator-sdk/releases/download/v1.3.0/operator-sdk_$(PLATFORM)_amd64 + chmod +x $(PROJECT_DIR)/bin/operator-sdk +endif + # Generate bundle manifests and metadata, then validate generated files. .PHONY: bundle -bundle: manifests kustomize - operator-sdk generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) - operator-sdk bundle validate ./bundle +bundle: manifests operator-sdk kustomize + bin/operator-sdk generate kustomize manifests -q + cd config/manager && $(KUSTOMIZE) edit set image controller=$(DOCKER_ORGANIZATION)/$(DOCKER_REGISTRY):$(VERSION_TAG) + $(KUSTOMIZE) build config/manifests | bin/operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) + bin/operator-sdk bundle validate ./bundle # Build the bundle image. .PHONY: bundle-build diff --git a/operator-sdk.mk b/operator-sdk.mk deleted file mode 100644 index 10ea4673..00000000 --- a/operator-sdk.mk +++ /dev/null @@ -1,94 +0,0 @@ -all: manager -#TODO for removing after Makefile fix -# Run tests -ENVTEST_ASSETS_DIR=$(shell pwd)/testbin -test: generate fmt vet manifests - mkdir -p ${ENVTEST_ASSETS_DIR} - test -f ${ENVTEST_ASSETS_DIR}/setup-envtest.sh || curl -sSLo ${ENVTEST_ASSETS_DIR}/setup-envtest.sh https://raw.githubusercontent.com/kubernetes-sigs/controller-runtime/v0.7.0/hack/setup-envtest.sh - source ${ENVTEST_ASSETS_DIR}/setup-envtest.sh; fetch_envtest_tools $(ENVTEST_ASSETS_DIR); setup_envtest_env $(ENVTEST_ASSETS_DIR); go test ./... -coverprofile cover.out - -# Build manager binary -manager: generate fmt vet - go build -o bin/manager main.go - -# Run against the configured Kubernetes cluster in ~/.kube/config -run: generate fmt vet manifests - go run ./main.go - -# Install CRDs into a cluster -install-crds: manifests kustomize - $(KUSTOMIZE) build config/crd | kubectl apply -f - - -# Uninstall CRDs from a cluster -uninstall: manifests kustomize - $(KUSTOMIZE) build config/crd | kubectl delete -f - - -# Deploy controller in the configured Kubernetes cluster in ~/.kube/config -deploy: manifests kustomize - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default | kubectl apply -f - - -# UnDeploy controller from the configured Kubernetes cluster in ~/.kube/config -undeploy: - $(KUSTOMIZE) build config/default | kubectl delete -f - - -# Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -# Run go fmt against code -fmt: - go fmt ./... - -# Run go vet against code -vet: - go vet ./... - -# Generate code -generate: controller-gen - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - -# Build the docker image -docker-build: test - docker build -t ${IMG} . - -# Push the docker image -docker-push: - docker push ${IMG} - -# Download controller-gen locally if necessary -CONTROLLER_GEN = $(shell pwd)/bin/controller-gen -controller-gen: - $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.4.1) - -# Download kustomize locally if necessary -KUSTOMIZE = $(shell pwd)/bin/kustomize -kustomize: - $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) - -# go-get-tool will 'go get' any package $2 and install it to $1. -PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) -define go-get-tool -@[ -f $(1) ] || { \ -set -e ;\ -TMP_DIR=$$(mktemp -d) ;\ -cd $$TMP_DIR ;\ -go mod init tmp ;\ -echo "Downloading $(2)" ;\ -GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ -rm -rf $$TMP_DIR ;\ -} -endef - -# Generate bundle manifests and metadata, then validate generated files. -.PHONY: bundle -bundle: manifests kustomize - operator-sdk generate kustomize manifests -q - cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) - $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) - operator-sdk bundle validate ./bundle - -# Build the bundle image. -.PHONY: bundle-build -bundle-build: - docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . From 021ebb4745d54d447dd5c44147ed19f826e31043 Mon Sep 17 00:00:00 2001 From: SylwiaBrant <45368349+SylwiaBrant@users.noreply.github.com> Date: Fri, 5 Feb 2021 13:17:06 +0100 Subject: [PATCH 11/15] Added TestPlugins, TestRestart and TestRestoreBackup (#504) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Tomasz SÄ™k --- controllers/jenkins_controller.go | 14 +-- pkg/configuration/base/resources/probe.go | 11 +- test/e2e/configuration_test.go | 43 -------- test/e2e/jenkins_configuration_test.go | 55 ++++++++++ test/e2e/jenkins_pod_restart_test.go | 108 +++++++++++++++++++ test/e2e/jenkins_restore_backup_test.go | 61 +++++++++++ test/e2e/jenkins_test.go | 120 +++++++++++++++++++++- test/e2e/restart_test.go | 91 ++++------------ test/e2e/restorebackup_test.go | 113 ++++++++------------ test/e2e/seedjobs_test.go | 57 ++++++++-- test/e2e/wait_test.go | 50 +++++---- 11 files changed, 485 insertions(+), 238 deletions(-) create mode 100644 test/e2e/jenkins_pod_restart_test.go create mode 100644 test/e2e/jenkins_restore_backup_test.go diff --git a/controllers/jenkins_controller.go b/controllers/jenkins_controller.go index fe0e992b..23b3d9df 100644 --- a/controllers/jenkins_controller.go +++ b/controllers/jenkins_controller.go @@ -211,14 +211,6 @@ func (r *JenkinsReconciler) reconcile(request reconcile.Request) (reconcile.Resu return reconcile.Result{Requeue: true}, jenkins, nil } - requeue, err = r.handleDeprecatedData(jenkins) - if err != nil { - return reconcile.Result{}, jenkins, err - } - if requeue { - return reconcile.Result{Requeue: true}, jenkins, nil - } - config := r.newJenkinsReconcilier(jenkins) // Reconcile base configuration baseConfiguration := base.New(config, r.JenkinsAPIConnectionSettings) @@ -367,7 +359,7 @@ func (r *JenkinsReconciler) setDefaults(jenkins *v1alpha2.Jenkins) (requeue bool if jenkinsContainer.ReadinessProbe == nil { logger.Info("Setting default Jenkins readinessProbe") changed = true - jenkinsContainer.ReadinessProbe = resources.NewSimpleProbe(containerProbeURI, containerProbePortName, corev1.URISchemeHTTP, 30) + jenkinsContainer.ReadinessProbe = resources.NewProbe(containerProbeURI, containerProbePortName, corev1.URISchemeHTTP, 30, 1, 3) } if jenkinsContainer.LivenessProbe == nil { logger.Info("Setting default Jenkins livenessProbe") @@ -493,7 +485,3 @@ func basePlugins() (result []v1alpha2.Plugin) { } return } - -func (r *JenkinsReconciler) handleDeprecatedData(_ *v1alpha2.Jenkins) (requeue bool, err error) { - return false, nil -} diff --git a/pkg/configuration/base/resources/probe.go b/pkg/configuration/base/resources/probe.go index 41d54dc7..f59c1534 100644 --- a/pkg/configuration/base/resources/probe.go +++ b/pkg/configuration/base/resources/probe.go @@ -5,7 +5,7 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" ) -func NewSimpleProbe(uri string, port string, scheme corev1.URIScheme, initialDelaySeconds int32) *corev1.Probe { +func NewProbe(uri string, port string, scheme corev1.URIScheme, initialDelaySeconds, timeoutSeconds, failureThreshold int32) *corev1.Probe { return &corev1.Probe{ Handler: corev1.Handler{ HTTPGet: &corev1.HTTPGetAction{ @@ -15,14 +15,9 @@ func NewSimpleProbe(uri string, port string, scheme corev1.URIScheme, initialDel }, }, InitialDelaySeconds: initialDelaySeconds, + TimeoutSeconds: timeoutSeconds, + FailureThreshold: failureThreshold, SuccessThreshold: int32(1), PeriodSeconds: int32(1), } } - -func NewProbe(uri string, port string, scheme corev1.URIScheme, initialDelaySeconds, timeoutSeconds, failureThreshold int32) *corev1.Probe { - p := NewSimpleProbe(uri, port, scheme, initialDelaySeconds) - p.TimeoutSeconds = timeoutSeconds - p.FailureThreshold = failureThreshold - return p -} diff --git a/test/e2e/configuration_test.go b/test/e2e/configuration_test.go index 780a739c..aa759670 100644 --- a/test/e2e/configuration_test.go +++ b/test/e2e/configuration_test.go @@ -23,49 +23,6 @@ import ( const e2e = "e2e" -// FIXME -/*func TestPlugins(t *testing.T) { - t.Parallel() - namespace, ctx := setupTest(t) - // Deletes test namespace - defer showLogsAndCleanup(t, ctx) - - jobID := "k8s-e2e" - - priorityClassName := "" - seedJobs := &[]v1alpha2.SeedJob{ - { - ID: "jenkins-operator", - CredentialID: "jenkins-operator", - JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, - Targets: "cicd/jobs/k8s.jenkins", - Description: "Jenkins Operator repository", - RepositoryBranch: "master", - RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", - }, - } - - jenkins := createJenkinsCR(t, "k8s-e2e", namespace, seedJobs, v1alpha2.GroovyScripts{}, v1alpha2.ConfigurationAsCode{}, priorityClassName) - waitForJenkinsUserConfigurationToComplete(t, jenkins) - - jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc() - waitForJob(t, jenkinsClient, jobID) - job, err := jenkinsClient.GetJob(jobID) - - require.NoError(t, err, job) - i, err := job.InvokeSimple(map[string]string{}) - require.NoError(t, err, i) - // FIXME: waitForJobToFinish use - time.Sleep(100 * time.Second) // wait for the build to complete - - job, err = jenkinsClient.GetJob(jobID) - require.NoError(t, err, job) - build, err := job.GetLastBuild() - require.NoError(t, err) - assert.True(t, build.IsGood()) -}*/ - func createUserConfigurationSecret(namespace string, stringData map[string]string) { By("creating user configuration secret") diff --git a/test/e2e/jenkins_configuration_test.go b/test/e2e/jenkins_configuration_test.go index 5ff057ff..7e454f36 100644 --- a/test/e2e/jenkins_configuration_test.go +++ b/test/e2e/jenkins_configuration_test.go @@ -139,3 +139,58 @@ var _ = Describe("Jenkins controller priority class", func() { }) }) }) + +var _ = Describe("Jenkins controller plugins test", func() { + + const ( + jenkinsCRName = e2e + priorityClassName = "" + jobID = "k8s-e2e" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + mySeedJob = seedJobConfig{ + SeedJob: v1alpha2.SeedJob{ + ID: "jenkins-operator", + CredentialID: "jenkins-operator", + JenkinsCredentialType: v1alpha2.NoJenkinsCredentialCredentialType, + Targets: "cicd/jobs/k8s.jenkins", + Description: "Jenkins Operator repository", + RepositoryBranch: "master", + RepositoryURL: "https://github.com/jenkinsci/kubernetes-operator.git", + }, + } + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + ) + + BeforeEach(func() { + namespace = createNamespace() + jenkins = createJenkinsCRSafe(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when deploying CR with a SeedJob to cluster", func() { + It("runs kubernetes plugin job successfully", func() { + waitForJenkinsUserConfigurationToComplete(jenkins) + jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(jenkins, namespace.Name) + defer cleanUpFunc() + waitForJobCreation(jenkinsClient, jobID) + verifyJobCanBeRun(jenkinsClient, jobID) + verifyJobHasBeenRunCorrectly(jenkinsClient, jobID) + }) + }) +}) diff --git a/test/e2e/jenkins_pod_restart_test.go b/test/e2e/jenkins_pod_restart_test.go new file mode 100644 index 00000000..46cda3a2 --- /dev/null +++ b/test/e2e/jenkins_pod_restart_test.go @@ -0,0 +1,108 @@ +package e2e + +import ( + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + // +kubebuilder:scaffold:imports +) + +var _ = Describe("Jenkins controller", func() { + + const ( + jenkinsCRName = e2e + priorityClassName = "" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + ) + + BeforeEach(func() { + namespace = createNamespace() + + configureAuthorizationToUnSecure(namespace.Name, userConfigurationConfigMapName) + jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when restarting Jenkins master pod", func() { + It("new Jenkins Master pod should be created", func() { + waitForJenkinsBaseConfigurationToComplete(jenkins) + restartJenkinsMasterPod(jenkins) + waitForRecreateJenkinsMasterPod(jenkins) + checkBaseConfigurationCompleteTimeIsNotSet(jenkins) + waitForJenkinsBaseConfigurationToComplete(jenkins) + }) + }) +}) + +var _ = Describe("Jenkins controller", func() { + + const ( + jenkinsCRName = e2e + priorityClassName = "" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + groovyScripts = v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{ + { + Name: userConfigurationConfigMapName, + }, + }, + }, + } + casc = v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + } + ) + + BeforeEach(func() { + namespace = createNamespace() + + configureAuthorizationToUnSecure(namespace.Name, userConfigurationConfigMapName) + jenkins = createJenkinsCRSafeRestart(jenkinsCRName, namespace.Name, nil, groovyScripts, casc, priorityClassName) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when running Jenkins safe restart", func() { + It("authorization strategy is not overwritten", func() { + waitForJenkinsBaseConfigurationToComplete(jenkins) + waitForJenkinsUserConfigurationToComplete(jenkins) + jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(jenkins, namespace.Name) + defer cleanUpFunc() + checkIfAuthorizationStrategyUnsecuredIsSet(jenkinsClient) + + err := jenkinsClient.SafeRestart() + Expect(err).NotTo(HaveOccurred()) + waitForJenkinsSafeRestart(jenkinsClient) + + checkIfAuthorizationStrategyUnsecuredIsSet(jenkinsClient) + }) + }) +}) diff --git a/test/e2e/jenkins_restore_backup_test.go b/test/e2e/jenkins_restore_backup_test.go new file mode 100644 index 00000000..eeb474d8 --- /dev/null +++ b/test/e2e/jenkins_restore_backup_test.go @@ -0,0 +1,61 @@ +package e2e + +import ( + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + + . "github.com/onsi/ginkgo" + corev1 "k8s.io/api/core/v1" + // +kubebuilder:scaffold:imports +) + +var _ = Describe("Jenkins controller backup and restore", func() { + + const ( + jenkinsCRName = e2e + jobID = "e2e-jenkins-operator" + ) + + var ( + namespace *corev1.Namespace + jenkins *v1alpha2.Jenkins + ) + + BeforeEach(func() { + namespace = createNamespace() + + createPVC(namespace.Name) + jenkins = createJenkinsWithBackupAndRestoreConfigured(jenkinsCRName, namespace.Name) + }) + + AfterEach(func() { + destroyNamespace(namespace) + }) + + Context("when deploying CR with backup enabled to cluster", func() { + It("performs backups before pod deletion and restores them even Jenkins status is restarted", func() { + waitForJenkinsUserConfigurationToComplete(jenkins) + jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(jenkins, namespace.Name) + defer cleanUpFunc() + waitForJobCreation(jenkinsClient, jobID) + verifyJobCanBeRun(jenkinsClient, jobID) + + jenkins = getJenkins(jenkins.Namespace, jenkins.Name) + restartJenkinsMasterPod(jenkins) + waitForRecreateJenkinsMasterPod(jenkins) + waitForJenkinsUserConfigurationToComplete(jenkins) + jenkinsClient2, cleanUpFunc2 := verifyJenkinsAPIConnection(jenkins, namespace.Name) + defer cleanUpFunc2() + waitForJobCreation(jenkinsClient2, jobID) + verifyJobBuildsAfterRestoreBackup(jenkinsClient2, jobID) + + resetJenkinsStatus(jenkins) + jenkins = getJenkins(jenkins.Namespace, jenkins.Name) + checkBaseConfigurationCompleteTimeIsNotSet(jenkins) + waitForJenkinsUserConfigurationToComplete(jenkins) + jenkinsClient3, cleanUpFunc3 := verifyJenkinsAPIConnection(jenkins, namespace.Name) + defer cleanUpFunc3() + waitForJobCreation(jenkinsClient3, jobID) + verifyJobBuildsAfterRestoreBackup(jenkinsClient3, jobID) + }) + }) +}) diff --git a/test/e2e/jenkins_test.go b/test/e2e/jenkins_test.go index 55683081..8a316464 100644 --- a/test/e2e/jenkins_test.go +++ b/test/e2e/jenkins_test.go @@ -152,6 +152,112 @@ func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovy return jenkins } +func createJenkinsCRSafeRestart(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovyScripts v1alpha2.GroovyScripts, casc v1alpha2.ConfigurationAsCode, priorityClassName string) *v1alpha2.Jenkins { + var seedJobs []v1alpha2.SeedJob + if seedJob != nil { + seedJobs = append(seedJobs, *seedJob...) + } + + jenkins := &v1alpha2.Jenkins{ + TypeMeta: v1alpha2.JenkinsTypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: v1alpha2.JenkinsSpec{ + GroovyScripts: groovyScripts, + ConfigurationAsCode: casc, + Master: v1alpha2.JenkinsMaster{ + Annotations: map[string]string{"test": "label"}, + Containers: []v1alpha2.Container{ + { + Name: resources.JenkinsMasterContainerName, + Env: []corev1.EnvVar{ + { + Name: "TEST_ENV", + Value: "test_env_value", + }, + }, + ReadinessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/login", + Port: intstr.FromString("http"), + Scheme: corev1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: int32(80), + TimeoutSeconds: int32(4), + FailureThreshold: int32(10), + SuccessThreshold: int32(1), + PeriodSeconds: int32(1), + }, + LivenessProbe: &corev1.Probe{ + Handler: corev1.Handler{ + HTTPGet: &corev1.HTTPGetAction{ + Path: "/login", + Port: intstr.FromString("http"), + Scheme: corev1.URISchemeHTTP, + }, + }, + InitialDelaySeconds: int32(100), + TimeoutSeconds: int32(5), + FailureThreshold: int32(12), + SuccessThreshold: int32(1), + PeriodSeconds: int32(5), + }, + VolumeMounts: []corev1.VolumeMount{ + { + Name: "plugins-cache", + MountPath: "/usr/share/jenkins/ref/plugins", + }, + }, + }, + { + Name: "envoyproxy", + Image: "envoyproxy/envoy-alpine:v1.14.1", + }, + }, + Plugins: []v1alpha2.Plugin{ + {Name: "audit-trail", Version: "3.7"}, + {Name: "simple-theme-plugin", Version: "0.6"}, + {Name: "github", Version: "1.32.0"}, + {Name: "devoptics", Version: "1.1905", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1905/devoptics.hpi"}, + }, + PriorityClassName: priorityClassName, + NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, + Volumes: []corev1.Volume{ + { + Name: "plugins-cache", + VolumeSource: corev1.VolumeSource{ + EmptyDir: &corev1.EmptyDirVolumeSource{}, + }, + }, + }, + }, + SeedJobs: seedJobs, + Service: v1alpha2.Service{ + Type: corev1.ServiceTypeNodePort, + Port: constants.DefaultHTTPPortInt32, + }, + }, + } + jenkins.Spec.Roles = []rbacv1.RoleRef{ + { + APIGroup: "rbac.authorization.k8s.io", + Kind: "Role", + Name: resources.GetResourceName(jenkins), + }, + } + updateJenkinsCR(jenkins) + + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins CR %+v\n", *jenkins) + + Expect(k8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) + + return jenkins +} + func createJenkinsAPIClientFromServiceAccount(jenkins *v1alpha2.Jenkins, jenkinsAPIURL string) (jenkinsclient.Jenkins, error) { podName := resources.GetJenkinsMasterPodName(jenkins) @@ -224,12 +330,18 @@ func verifyJenkinsAPIConnection(jenkins *v1alpha2.Jenkins, namespace string) (je return jenkinsClient, cleanUpFunc } -/*func restartJenkinsMasterPod(jenkins *v1alpha2.Jenkins) { - _, _ = fmt.Fprintf(GinkgoWriter, "Restarting Jenkins master pod") +func restartJenkinsMasterPod(jenkins *v1alpha2.Jenkins) { + _, _ = fmt.Fprintf(GinkgoWriter, "Restarting Jenkins master pod\n") jenkinsPod := getJenkinsMasterPod(jenkins) Expect(k8sClient.Delete(context.TODO(), jenkinsPod)).Should(Succeed()) - _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins master pod has been restarted") -}*/ + + Eventually(func() (bool, error) { + jenkinsPod = getJenkinsMasterPod(jenkins) + return jenkinsPod.DeletionTimestamp != nil, nil + }, 30*retryInterval, retryInterval).Should(BeTrue()) + + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins master pod has been restarted\n") +} func getJenkinsService(jenkins *v1alpha2.Jenkins, serviceKind string) *corev1.Service { service := &corev1.Service{} diff --git a/test/e2e/restart_test.go b/test/e2e/restart_test.go index b91611e6..f5eb145b 100644 --- a/test/e2e/restart_test.go +++ b/test/e2e/restart_test.go @@ -1,70 +1,20 @@ package e2e -// TODO -/* import ( "context" - "testing" + "time" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" ) -func TestJenkinsMasterPodRestart(t *testing.T) { - t.Parallel() - namespace, ctx := setupTest(t) - - defer showLogsAndCleanup(t, ctx) - - jenkins := createJenkinsCR(t, "e2e", namespace, nil, v1alpha2.GroovyScripts{}, v1alpha2.ConfigurationAsCode{}, "") - waitForJenkinsBaseConfigurationToComplete(t, jenkins) - restartJenkinsMasterPod(t, jenkins) - waitForRecreateJenkinsMasterPod(t, jenkins) - checkBaseConfigurationCompleteTimeIsNotSet(t, jenkins) - waitForJenkinsBaseConfigurationToComplete(t, jenkins) -} - -func TestSafeRestart(t *testing.T) { - if skipTestSafeRestart { - t.Skip() - } - t.Parallel() - namespace, ctx := setupTest(t) - // Deletes test namespace - defer ctx.Cleanup() - - jenkinsCRName := "e2e" - configureAuthorizationToUnSecure(t, namespace, userConfigurationConfigMapName) - groovyScriptsConfig := v1alpha2.GroovyScripts{ - Customization: v1alpha2.Customization{ - Configurations: []v1alpha2.ConfigMapRef{ - { - Name: userConfigurationConfigMapName, - }, - }, - }, - } - jenkins := createJenkinsCR(t, jenkinsCRName, namespace, nil, groovyScriptsConfig, v1alpha2.ConfigurationAsCode{}, "") - waitForJenkinsBaseConfigurationToComplete(t, jenkins) - waitForJenkinsUserConfigurationToComplete(t, jenkins) - jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc() - checkIfAuthorizationStrategyUnsecuredIsSet(t, jenkinsClient) - - err := jenkinsClient.SafeRestart() - require.NoError(t, err) - waitForJenkinsSafeRestart(t, jenkinsClient) - - checkIfAuthorizationStrategyUnsecuredIsSet(t, jenkinsClient) -} - -func configureAuthorizationToUnSecure(t *testing.T, namespace, configMapName string) { +func configureAuthorizationToUnSecure(namespace, configMapName string) { limitRange := &corev1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: configMapName, @@ -83,11 +33,12 @@ jenkins.save() }, } - err := framework.Global.Client.Create(context.TODO(), limitRange, nil) - require.NoError(t, err) + Expect(k8sClient.Create(context.TODO(), limitRange)).Should(Succeed()) } -func checkIfAuthorizationStrategyUnsecuredIsSet(t *testing.T, jenkinsClient jenkinsclient.Jenkins) { +func checkIfAuthorizationStrategyUnsecuredIsSet(jenkinsClient jenkinsclient.Jenkins) { + By("checking if Authorization Strategy Unsecured is set") + logs, err := jenkinsClient.ExecuteScript(` import hudson.security.* @@ -97,18 +48,18 @@ func checkIfAuthorizationStrategyUnsecuredIsSet(t *testing.T, jenkinsClient jenk throw new Exception('AuthorizationStrategy.Unsecured is not set') } `) - require.NoError(t, err, logs) + Expect(err).NotTo(HaveOccurred(), logs) } -func checkBaseConfigurationCompleteTimeIsNotSet(t *testing.T, jenkins *v1alpha2.Jenkins) { - jenkinsStatus := &v1alpha2.Jenkins{} - namespaceName := types.NamespacedName{Namespace: jenkins.Namespace, Name: jenkins.Name} - err := framework.Global.Client.Get(context.TODO(), namespaceName, jenkinsStatus) - if err != nil { - t.Fatal(err) - } - if jenkinsStatus.Status.BaseConfigurationCompletedTime != nil { - t.Fatalf("Status.BaseConfigurationCompletedTime is set after pod restart, status %+v", jenkinsStatus.Status) - } +func checkBaseConfigurationCompleteTimeIsNotSet(jenkins *v1alpha2.Jenkins) { + By("checking that Base Configuration's complete time is not set") + + Eventually(func() (bool, error) { + actualJenkins := &v1alpha2.Jenkins{} + err := k8sClient.Get(context.TODO(), types.NamespacedName{Name: jenkins.Name, Namespace: jenkins.Namespace}, actualJenkins) + if err != nil { + return false, err + } + return actualJenkins.Status.BaseConfigurationCompletedTime == nil, nil + }, time.Duration(110)*retryInterval, time.Second).Should(BeTrue()) } -*/ diff --git a/test/e2e/restorebackup_test.go b/test/e2e/restorebackup_test.go index eb4b462e..46d4b524 100644 --- a/test/e2e/restorebackup_test.go +++ b/test/e2e/restorebackup_test.go @@ -1,21 +1,17 @@ package e2e -// TODO -/* import ( "context" - "testing" + "fmt" "time" - "github.com/jenkinsci/kubernetes-operator/internal/try" - "github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2" + "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" "github.com/jenkinsci/kubernetes-operator/pkg/client" "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" "github.com/jenkinsci/kubernetes-operator/pkg/constants" - framework "github.com/operator-framework/operator-sdk/pkg/test" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -23,64 +19,33 @@ import ( const pvcName = "pvc-jenkins" -func TestBackupAndRestore(t *testing.T) { - t.Parallel() - namespace, ctx := setupTest(t) +func waitForJobCreation(jenkinsClient client.Jenkins, jobID string) { + By("waiting for Jenkins job creation") - defer showLogsAndCleanup(t, ctx) - - jobID := "e2e-jenkins-operator" - createPVC(t, namespace) - jenkins := createJenkinsWithBackupAndRestoreConfigured(t, "e2e", namespace) - waitForJenkinsUserConfigurationToComplete(t, jenkins) - - jenkinsClient, cleanUpFunc := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc() - waitForJob(t, jenkinsClient, jobID) - job, err := jenkinsClient.GetJob(jobID) - require.NoError(t, err, job) - i, err := job.InvokeSimple(map[string]string{}) - require.NoError(t, err, i) - // FIXME: waitForJobToFinish use - time.Sleep(60 * time.Second) // wait for the build to complete - - jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) - lastDoneBackup := jenkins.Status.LastBackup - restartJenkinsMasterPod(t, jenkins) - waitForRecreateJenkinsMasterPod(t, jenkins) - waitForJenkinsUserConfigurationToComplete(t, jenkins) - jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) - assert.Equal(t, lastDoneBackup, jenkins.Status.RestoredBackup) - jenkinsClient2, cleanUpFunc2 := verifyJenkinsAPIConnection(t, jenkins, namespace) - defer cleanUpFunc2() - waitForJob(t, jenkinsClient2, jobID) - verifyJobBuildsAfterRestoreBackup(t, jenkinsClient2, jobID) - - jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) - lastDoneBackup = jenkins.Status.LastBackup - resetJenkinsStatus(t, jenkins) - waitForJenkinsUserConfigurationToComplete(t, jenkins) - jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) - assert.Equal(t, lastDoneBackup, jenkins.Status.RestoredBackup) -} - -func waitForJob(t *testing.T, jenkinsClient client.Jenkins, jobID string) { - err := try.Until(func() (end bool, err error) { + var err error + Eventually(func() (bool, error) { _, err = jenkinsClient.GetJob(jobID) + if err != nil { + return false, err + } return err == nil, err - }, time.Second*2, time.Minute*3) - require.NoErrorf(t, err, "Jenkins job '%s' not created by seed job", jobID) + }, time.Minute*3, time.Second*2).Should(BeTrue()) + + Expect(err).NotTo(HaveOccurred()) } -func verifyJobBuildsAfterRestoreBackup(t *testing.T, jenkinsClient client.Jenkins, jobID string) { +func verifyJobBuildsAfterRestoreBackup(jenkinsClient client.Jenkins, jobID string) { + By("checking if job builds after restoring backup") + job, err := jenkinsClient.GetJob(jobID) - require.NoError(t, err) + Expect(err).NotTo(HaveOccurred()) build, err := job.GetLastBuild() - require.NoError(t, err) - assert.Equal(t, int64(1), build.GetBuildNumber()) + Expect(err).NotTo(HaveOccurred()) + + Expect(build.GetBuildNumber()).To(Equal(int64(1))) } -func createPVC(t *testing.T, namespace string) { +func createPVC(namespace string) { pvc := &corev1.PersistentVolumeClaim{ ObjectMeta: metav1.ObjectMeta{ Name: pvcName, @@ -96,11 +61,10 @@ func createPVC(t *testing.T, namespace string) { }, } - err := framework.Global.Client.Create(context.TODO(), pvc, nil) - require.NoError(t, err) + Expect(k8sClient.Create(context.TODO(), pvc)).Should(Succeed()) } -func createJenkinsWithBackupAndRestoreConfigured(t *testing.T, name, namespace string) *v1alpha2.Jenkins { +func createJenkinsWithBackupAndRestoreConfigured(name, namespace string) *v1alpha2.Jenkins { containerName := "backup" jenkins := &v1alpha2.Jenkins{ TypeMeta: v1alpha2.JenkinsTypeMeta(), @@ -130,6 +94,16 @@ func createJenkinsWithBackupAndRestoreConfigured(t *testing.T, name, namespace s }, }, }, + GroovyScripts: v1alpha2.GroovyScripts{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + }, + ConfigurationAsCode: v1alpha2.ConfigurationAsCode{ + Customization: v1alpha2.Customization{ + Configurations: []v1alpha2.ConfigMapRef{}, + }, + }, Master: v1alpha2.JenkinsMaster{ Containers: []v1alpha2.Container{ { @@ -201,19 +175,20 @@ func createJenkinsWithBackupAndRestoreConfigured(t *testing.T, name, namespace s }, }, } - updateJenkinsCR(t, jenkins) - t.Logf("Jenkins CR %+v", *jenkins) - err := framework.Global.Client.Create(context.TODO(), jenkins, nil) - require.NoError(t, err) + updateJenkinsCR(jenkins) + + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins CR %+v\n", *jenkins) + + Expect(k8sClient.Create(context.TODO(), jenkins)).Should(Succeed()) return jenkins } -func resetJenkinsStatus(t *testing.T, jenkins *v1alpha2.Jenkins) { - jenkins = getJenkins(t, jenkins.Namespace, jenkins.Name) +func resetJenkinsStatus(jenkins *v1alpha2.Jenkins) { + By("resetting Jenkins status") + + jenkins = getJenkins(jenkins.Namespace, jenkins.Name) jenkins.Status = v1alpha2.JenkinsStatus{} - err := framework.Global.Client.Update(context.TODO(), jenkins) - require.NoError(t, err) + Expect(k8sClient.Status().Update(context.TODO(), jenkins)).Should(Succeed()) } -*/ diff --git a/test/e2e/seedjobs_test.go b/test/e2e/seedjobs_test.go index 7268013c..5c176680 100644 --- a/test/e2e/seedjobs_test.go +++ b/test/e2e/seedjobs_test.go @@ -13,6 +13,7 @@ import ( "github.com/jenkinsci/kubernetes-operator/pkg/configuration/user/seedjobs" "github.com/jenkinsci/kubernetes-operator/pkg/constants" + "github.com/bndr/gojenkins" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -27,12 +28,13 @@ type seedJobConfig struct { PrivateKey string `json:"privateKey,omitempty"` } -/*type seedJobsConfig struct { +/* +type seedJobsConfig struct { SeedJobs []seedJobConfig `json:"seedJobs,omitempty"` -}*/ +} // FIXME -/*func TestSeedJobs(t *testing.T) { +func TestSeedJobs(t *testing.T) { t.Parallel() if seedJobConfigurationFile == nil || len(*seedJobConfigurationFile) == 0 { t.Skipf("Skipping test because flag '%+v' is not set", seedJobConfigurationFile) @@ -63,22 +65,22 @@ type seedJobConfig struct { verifyJenkinsSeedJobs(t, jenkinsClient, seedJobsConfig.SeedJobs) } -func loadSeedJobsConfig(t *testing.T) seedJobsConfig { +func loadSeedJobsConfig() seedJobsConfig { + //seedJobConfigurationFile = flag.String(seedJobConfigurationParameterName, "", "path to seed job config") jsonFile, err := os.Open(*seedJobConfigurationFile) - assert.NoError(t, err) + Expect(err).NotTo(HaveOccurred()) defer func() { _ = jsonFile.Close() }() byteValue, err := ioutil.ReadAll(jsonFile) - assert.NoError(t, err) + Expect(err).NotTo(HaveOccurred()) var result seedJobsConfig err = json.Unmarshal(byteValue, &result) - assert.NoError(t, err) - assert.NotEmpty(t, result.SeedJobs) + Expect(err).NotTo(HaveOccurred()) + Expect(result.SeedJobs).NotTo(BeEmpty()) return result } */ - func createKubernetesCredentialsProviderSecret(namespace string, config seedJobConfig) { if config.JenkinsCredentialType == v1alpha2.NoJenkinsCredentialCredentialType { return @@ -112,7 +114,7 @@ func verifyJenkinsSeedJobs(jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJ for _, seedJob := range seedJobs { if seedJob.JenkinsCredentialType == v1alpha2.BasicSSHCredentialType || seedJob.JenkinsCredentialType == v1alpha2.UsernamePasswordCredentialType { err = verifyIfJenkinsCredentialExists(jenkinsClient, seedJob.CredentialID) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins credential '%s' not created for seed job ID '%s'", seedJob.CredentialID, seedJob.ID)) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins credential '%s' not created for seed job ID '%s'\n", seedJob.CredentialID, seedJob.ID)) } verifySeedJobProperties(jenkinsClient, seedJob) @@ -122,7 +124,7 @@ func verifyJenkinsSeedJobs(jenkinsClient jenkinsclient.Jenkins, seedJobs []seedJ _, err = jenkinsClient.GetJob(requireJobName) return err == nil, err }, time.Second*2, time.Minute*2) - Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins job '%s' not created by seed job ID '%s'", requireJobName, seedJob.ID)) + Expect(err).NotTo(HaveOccurred(), fmt.Sprintf("Jenkins job '%s' not created by seed job ID '%s'\n", requireJobName, seedJob.ID)) } } } @@ -292,3 +294,36 @@ for (BuildStep step : jobRef.getBuildersList()) { } } `)) + +func verifyJobCanBeRun(jenkinsClient jenkinsclient.Jenkins, jobID string) { + By("retrieving created Jenkins job") + job, err := jenkinsClient.GetJob(jobID) + Expect(err).To(BeNil()) + + By("running Jenkins job") + _, err = job.InvokeSimple(map[string]string{}) + Expect(err).To(BeNil()) + + // FIXME: waitForJobToFinish use + By("waiting for the job to finish") + time.Sleep(100 * time.Second) // wait for the build to complete +} +func verifyJobHasBeenRunCorrectly(jenkinsClient jenkinsclient.Jenkins, jobID string) { + By("retrieving finished job") + + var ( + err error + job *gojenkins.Job + build *gojenkins.Build + ) + + Eventually(func() (bool, error) { + job, err = jenkinsClient.GetJob(jobID) + Expect(err).To(BeNil()) + build, err = job.GetLastBuild() + Expect(err).To(BeNil()) + + By("evaluating correctness of the outcome") + return build.IsGood(), err + }, time.Duration(110)*retryInterval, retryInterval).Should(BeTrue()) +} diff --git a/test/e2e/wait_test.go b/test/e2e/wait_test.go index 255e3a3e..9d511c79 100644 --- a/test/e2e/wait_test.go +++ b/test/e2e/wait_test.go @@ -3,13 +3,20 @@ package e2e import ( "context" "fmt" + "net/http" "time" "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" + jenkinsclient "github.com/jenkinsci/kubernetes-operator/pkg/client" + "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" ) var ( @@ -36,26 +43,28 @@ func waitForJenkinsBaseConfigurationToComplete(jenkins *v1alpha2.Jenkins) { Expect(k8sClient.Get(context.TODO(), namespacedName, jenkins)).Should(Succeed()) } -/*func waitForRecreateJenkinsMasterPod(t *testing.T, jenkins *v1alpha2.Jenkins) { - err := wait.Poll(retryInterval, 30*retryInterval, func() (bool, error) { - lo := metav1.ListOptions{ - LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)).String(), +func waitForRecreateJenkinsMasterPod(jenkins *v1alpha2.Jenkins) { + By("waiting for Jenkins Master Pod recreation") + + Eventually(func() (bool, error) { + lo := &client.ListOptions{ + LabelSelector: labels.SelectorFromSet(resources.GetJenkinsMasterPodLabels(*jenkins)), + Namespace: jenkins.Namespace, } - podList, err := framework.Global.KubeClient.CoreV1().Pods(jenkins.ObjectMeta.Namespace).List(lo) + pods := &corev1.PodList{} + err := k8sClient.List(context.TODO(), pods, lo) if err != nil { return false, err } - if len(podList.Items) != 1 { + if len(pods.Items) != 1 { return false, nil } - return podList.Items[0].DeletionTimestamp == nil, nil - }) - if err != nil { - t.Fatal(err) - } - _, _ = fmt.Fprintf(GinkgoWriter,"Jenkins pod has been recreated") -}*/ + return pods.Items[0].DeletionTimestamp == nil, nil + }, 30*retryInterval, retryInterval).Should(BeTrue()) + + _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins pod has been recreated\n") +} func waitForJenkinsUserConfigurationToComplete(jenkins *v1alpha2.Jenkins) { By("waiting for Jenkins user configuration phase to complete") @@ -66,22 +75,23 @@ func waitForJenkinsUserConfigurationToComplete(jenkins *v1alpha2.Jenkins) { if err != nil { return nil, err } - return actualJenkins.Status.UserConfigurationCompletedTime, nil }, time.Duration(110)*retryInterval, retryInterval).Should(Not(BeNil())) _, _ = fmt.Fprintf(GinkgoWriter, "Jenkins instance is up and ready\n") } -/*func waitForJenkinsSafeRestart(t *testing.T, jenkinsClient jenkinsclient.Jenkins) { - err := try.Until(func() (end bool, err error) { +func waitForJenkinsSafeRestart(jenkinsClient jenkinsclient.Jenkins) { + By("waiting for Jenkins safe restart") + + Eventually(func() (bool, error) { status, err := jenkinsClient.Poll() + _, _ = fmt.Fprintf(GinkgoWriter, "Safe restart status: %+v, err: %s\n", status, err) if err != nil { return false, err } if status != http.StatusOK { - return false, errors.Wrap(err, "couldn't poll data from Jenkins API") + return false, err } return true, nil - }, time.Second, time.Second*70) - require.NoError(t, err) -}*/ + }, time.Second*200, time.Second*5).Should(BeTrue()) +} From 77a79e8f9749cf6b6d1484c23d52257cc7d40166 Mon Sep 17 00:00:00 2001 From: Sylwia Brant Date: Fri, 5 Feb 2021 13:26:34 +0100 Subject: [PATCH 12/15] Removed image CRD, updated plugins --- api/v1alpha2/jenkinsimage_types.go | 49 ------- api/v1alpha2/register.go | 1 - api/v1alpha2/zz_generated.deepcopy.go | 130 ------------------ .../crd/bases/jenkins.io_jenkinsimages.yaml | 95 ------------- .../samples/jenkins.io_v1alpha2_jenkins.yaml | 6 +- controllers/jenkinsImage_controller.go | 111 --------------- controllers/jenkins_controller.go | 2 +- main.go | 7 - pkg/configuration/base/resources/builder.go | 129 ----------------- pkg/plugins/base_plugins.go | 4 +- 10 files changed, 6 insertions(+), 528 deletions(-) delete mode 100644 api/v1alpha2/jenkinsimage_types.go delete mode 100644 config/crd/bases/jenkins.io_jenkinsimages.yaml delete mode 100644 controllers/jenkinsImage_controller.go delete mode 100644 pkg/configuration/base/resources/builder.go diff --git a/api/v1alpha2/jenkinsimage_types.go b/api/v1alpha2/jenkinsimage_types.go deleted file mode 100644 index 0be257a9..00000000 --- a/api/v1alpha2/jenkinsimage_types.go +++ /dev/null @@ -1,49 +0,0 @@ -package v1alpha2 - -import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - -// JenkinsImageSpec defines the desired state of JenkinsImage -type JenkinsImageSpec struct { - BaseImage Image `json:"image"` - Plugins []JenkinsPlugin `json:"plugins"` // Plugins list -} - -// Defines Jenkins Plugin structure -type JenkinsPlugin struct { - Name string `json:"name"` - Version string `json:"version,omitempty"` -} - -// Defines Jenkins Plugin structure -type Image struct { - Name string `json:"name"` - Tag string `json:"version,omitempty"` -} - -// JenkinsImageStatus defines the observed state of JenkinsImage -type JenkinsImageStatus struct { - Image string `json:"image,omitempty"` - MD5Sum string `json:"md5sum,omitempty"` - InstalledPlugins []JenkinsPlugin `json:"installedPlugins,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// JenkinsImage is the Schema for the jenkinsimages API -// +kubebuilder:subresource:status -// +kubebuilder:resource:path=jenkinsimages,scope=Namespaced -type JenkinsImage struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec JenkinsImageSpec `json:"spec,omitempty"` - Status JenkinsImageStatus `json:"status,omitempty"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// JenkinsImageList contains a list of JenkinsImage -type JenkinsImageList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []JenkinsImage `json:"items"` -} diff --git a/api/v1alpha2/register.go b/api/v1alpha2/register.go index d3e4c040..cecb9df5 100644 --- a/api/v1alpha2/register.go +++ b/api/v1alpha2/register.go @@ -53,5 +53,4 @@ func JenkinsTypeMeta() metav1.TypeMeta { func init() { SchemeBuilder.Register(&Jenkins{}, &JenkinsList{}) - SchemeBuilder.Register(&JenkinsImage{}, &JenkinsImageList{}) } diff --git a/api/v1alpha2/zz_generated.deepcopy.go b/api/v1alpha2/zz_generated.deepcopy.go index af28e1fd..566f8d1d 100644 --- a/api/v1alpha2/zz_generated.deepcopy.go +++ b/api/v1alpha2/zz_generated.deepcopy.go @@ -217,21 +217,6 @@ func (in *Handler) DeepCopy() *Handler { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Image) DeepCopyInto(out *Image) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Image. -func (in *Image) DeepCopy() *Image { - if in == nil { - return nil - } - out := new(Image) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Jenkins) DeepCopyInto(out *Jenkins) { *out = *in @@ -274,106 +259,6 @@ func (in *JenkinsAPISettings) DeepCopy() *JenkinsAPISettings { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JenkinsImage) DeepCopyInto(out *JenkinsImage) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImage. -func (in *JenkinsImage) DeepCopy() *JenkinsImage { - if in == nil { - return nil - } - out := new(JenkinsImage) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *JenkinsImage) 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 *JenkinsImageList) DeepCopyInto(out *JenkinsImageList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]JenkinsImage, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImageList. -func (in *JenkinsImageList) DeepCopy() *JenkinsImageList { - if in == nil { - return nil - } - out := new(JenkinsImageList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *JenkinsImageList) 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 *JenkinsImageSpec) DeepCopyInto(out *JenkinsImageSpec) { - *out = *in - out.BaseImage = in.BaseImage - if in.Plugins != nil { - in, out := &in.Plugins, &out.Plugins - *out = make([]JenkinsPlugin, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImageSpec. -func (in *JenkinsImageSpec) DeepCopy() *JenkinsImageSpec { - if in == nil { - return nil - } - out := new(JenkinsImageSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JenkinsImageStatus) DeepCopyInto(out *JenkinsImageStatus) { - *out = *in - if in.InstalledPlugins != nil { - in, out := &in.InstalledPlugins, &out.InstalledPlugins - *out = make([]JenkinsPlugin, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsImageStatus. -func (in *JenkinsImageStatus) DeepCopy() *JenkinsImageStatus { - if in == nil { - return nil - } - out := new(JenkinsImageStatus) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JenkinsList) DeepCopyInto(out *JenkinsList) { *out = *in @@ -483,21 +368,6 @@ func (in *JenkinsMaster) DeepCopy() *JenkinsMaster { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *JenkinsPlugin) DeepCopyInto(out *JenkinsPlugin) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new JenkinsPlugin. -func (in *JenkinsPlugin) DeepCopy() *JenkinsPlugin { - if in == nil { - return nil - } - out := new(JenkinsPlugin) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *JenkinsSpec) DeepCopyInto(out *JenkinsSpec) { *out = *in diff --git a/config/crd/bases/jenkins.io_jenkinsimages.yaml b/config/crd/bases/jenkins.io_jenkinsimages.yaml deleted file mode 100644 index 7c79607a..00000000 --- a/config/crd/bases/jenkins.io_jenkinsimages.yaml +++ /dev/null @@ -1,95 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.4.1 - creationTimestamp: null - name: jenkinsimages.jenkins.io -spec: - group: jenkins.io - names: - kind: JenkinsImage - listKind: JenkinsImageList - plural: jenkinsimages - singular: jenkinsimage - scope: Namespaced - versions: - - name: v1alpha2 - schema: - openAPIV3Schema: - description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: JenkinsImageSpec defines the desired state of JenkinsImage - properties: - image: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - plugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - required: - - image - - plugins - type: object - status: - description: JenkinsImageStatus defines the observed state of JenkinsImage - properties: - image: - type: string - installedPlugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - md5sum: - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/config/samples/jenkins.io_v1alpha2_jenkins.yaml b/config/samples/jenkins.io_v1alpha2_jenkins.yaml index 68c07cfb..265b967c 100644 --- a/config/samples/jenkins.io_v1alpha2_jenkins.yaml +++ b/config/samples/jenkins.io_v1alpha2_jenkins.yaml @@ -18,7 +18,7 @@ spec: disableCSRFProtection: false containers: - name: jenkins-master - image: jenkins/jenkins:2.249.3-lts-alpine + image: jenkins/jenkins:2.263.3-lts-alpine imagePullPolicy: Always livenessProbe: failureThreshold: 12 @@ -31,12 +31,12 @@ spec: successThreshold: 1 timeoutSeconds: 5 readinessProbe: - failureThreshold: 5 + failureThreshold: 10 httpGet: path: /login port: http scheme: HTTP - initialDelaySeconds: 60 + initialDelaySeconds: 80 periodSeconds: 10 successThreshold: 1 timeoutSeconds: 1 diff --git a/controllers/jenkinsImage_controller.go b/controllers/jenkinsImage_controller.go deleted file mode 100644 index 5fb4d020..00000000 --- a/controllers/jenkinsImage_controller.go +++ /dev/null @@ -1,111 +0,0 @@ -package controllers - -import ( - "context" - - "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" - "github.com/jenkinsci/kubernetes-operator/pkg/configuration/base/resources" - "github.com/jenkinsci/kubernetes-operator/pkg/log" - - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - 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" -) - -var logxx = log.Log - -// JenkinsImageReconciler reconciles a JenkinsImage object -type JenkinsImageReconciler struct { - // This Client, initialized using mgr.Client() above, is a split Client - // that reads objects from the cache and writes to the apiserver - Client client.Client - Scheme *runtime.Scheme -} - -// SetupWithManager sets up the controller with the Manager. -func (r *JenkinsImageReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&v1alpha2.JenkinsImage{}). - Owns(&corev1.Pod{}). - Owns(&corev1.ConfigMap{}). - Complete(r) -} - -// Reconcile reads that state of the cluster for a JenkinsImage object and makes changes based on the state read -// and what is in the JenkinsImage.Spec -// The Controller will requeue the Request to be processed again if the returned error is non-nil or -// Result.Requeue is true, otherwise upon completion it will remove the work from the queue. -func (r *JenkinsImageReconciler) Reconcile(_ context.Context, request ctrl.Request) (ctrl.Result, error) { - reqLogger := logxx.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) - reqLogger.Info("Reconciling JenkinsImage") - - // Fetch the JenkinsImage instance - instance := &v1alpha2.JenkinsImage{} - err := r.Client.Get(context.TODO(), request.NamespacedName, instance) - if err != nil { - if apierrors.IsNotFound(err) { - // Request object not found, could have been deleted after reconcile request. - // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. - // Return and don't requeue - return reconcile.Result{}, nil - } - // Error reading the object - requeue the request. - return reconcile.Result{}, err - } - - // Define a new ConfigMap containing the Dockerfile used to build the image - dockerfile := resources.NewDockerfileConfigMap(instance) - // Set JenkinsImage instance as the owner and controller - if err := controllerutil.SetControllerReference(instance, dockerfile, r.Scheme); err != nil { - return reconcile.Result{}, err - } - - // Check if this ConfigMap already exists - foundConfigMap := &corev1.ConfigMap{} - err = r.Client.Get(context.TODO(), types.NamespacedName{Name: dockerfile.Name, Namespace: dockerfile.Namespace}, foundConfigMap) - if err != nil && apierrors.IsNotFound(err) { - reqLogger.Info("Creating a new ConfigMap", "ConfigMap.Namespace", dockerfile.Namespace, "ConfigMap.Name", dockerfile.Name) - err = r.Client.Create(context.TODO(), dockerfile) - if err != nil { - return reconcile.Result{}, err - } - // ConfigMap created successfully - don't requeue - return reconcile.Result{}, nil - } else if err != nil { - return reconcile.Result{}, err - } - // ConfigMap already exists - don't requeue - reqLogger.Info("Skip reconcile: ConfigMap already exists", "ConfigMap.Namespace", foundConfigMap.Namespace, "ConfigMap.Name", foundConfigMap.Name) - - // Define a new Pod object - pod := resources.NewBuilderPod(instance) - // Set JenkinsImage instance as the owner and controller - if err := controllerutil.SetControllerReference(instance, pod, r.Scheme); err != nil { - return reconcile.Result{}, err - } - - // Check if this Pod already exists - foundPod := &corev1.Pod{} - err = r.Client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, foundPod) - if err != nil && apierrors.IsNotFound(err) { - reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name) - err = r.Client.Create(context.TODO(), pod) - if err != nil { - return reconcile.Result{}, err - } - - // Pod created successfully - don't requeue - return reconcile.Result{}, nil - } else if err != nil { - return reconcile.Result{}, err - } - // Pod already exists - don't requeue - reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", foundPod.Namespace, "Pod.Name", foundPod.Name) - - return reconcile.Result{}, nil -} diff --git a/controllers/jenkins_controller.go b/controllers/jenkins_controller.go index 23b3d9df..875250f2 100644 --- a/controllers/jenkins_controller.go +++ b/controllers/jenkins_controller.go @@ -359,7 +359,7 @@ func (r *JenkinsReconciler) setDefaults(jenkins *v1alpha2.Jenkins) (requeue bool if jenkinsContainer.ReadinessProbe == nil { logger.Info("Setting default Jenkins readinessProbe") changed = true - jenkinsContainer.ReadinessProbe = resources.NewProbe(containerProbeURI, containerProbePortName, corev1.URISchemeHTTP, 30, 1, 3) + jenkinsContainer.ReadinessProbe = resources.NewProbe(containerProbeURI, containerProbePortName, corev1.URISchemeHTTP, 60, 1, 10) } if jenkinsContainer.LivenessProbe == nil { logger.Info("Setting default Jenkins livenessProbe") diff --git a/main.go b/main.go index 949adda9..0ef69846 100644 --- a/main.go +++ b/main.go @@ -168,13 +168,6 @@ func main() { fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) } - if err = (&controllers.JenkinsImageReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - fatal(errors.Wrap(err, "unable to create Jenkins controller"), *debug) - } - // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("health", healthz.Ping); err != nil { diff --git a/pkg/configuration/base/resources/builder.go b/pkg/configuration/base/resources/builder.go deleted file mode 100644 index 181a6292..00000000 --- a/pkg/configuration/base/resources/builder.go +++ /dev/null @@ -1,129 +0,0 @@ -package resources - -import ( - "fmt" - - "github.com/jenkinsci/kubernetes-operator/api/v1alpha2" - - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - logf "sigs.k8s.io/controller-runtime/pkg/log" -) - -const ( - NameWithSuffixFormat = "%s-%s" - PluginDefinitionFormat = "%s:%s" - BuilderDockerfileArg = "--dockerfile=/workspace/dockerfile/Dockerfile" - BuilderContextDirArg = "--context=dir://workspace/" - BuilderPushArg = "--no-push" - BuilderDigestFileArg = "--digest-file=/dev/termination-log" - BuilderSuffix = "builder" - DockerfileStorageSuffix = "dockerfile-storage" - DockerfileNameSuffix = "dockerfile" - JenkinsImageBuilderImage = "gcr.io/kaniko-project/executor:latest" - JenkinsImageBuilderName = "jenkins-image-builder" - JenkinsImageDefaultBaseImage = "jenkins/jenkins:lts" - DockerfileName = "Dockerfile" - DockerfileTemplate = `FROM %s -RUN curl -o /tmp/install-plugins.sh https://raw.githubusercontent.com/jenkinsci/docker/master/install-plugins.sh -RUN chmod +x /tmp/install-plugins.sh -RUN install-plugins.sh %s ` -) - -var log = logf.Log.WithName("controller_jenkinsimage") - -// NewBuilderPod returns a busybox pod with the same name/namespace as the cr. -func NewBuilderPod(cr *v1alpha2.JenkinsImage) *corev1.Pod { - name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, BuilderSuffix) - args := []string{BuilderDockerfileArg, BuilderContextDirArg, BuilderPushArg, BuilderDigestFileArg} - volumes := getVolumes(cr) - volumeMounts := getVolumesMounts(cr) - p := &corev1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: cr.Namespace, - }, - Spec: corev1.PodSpec{ - RestartPolicy: corev1.RestartPolicyNever, - Containers: []corev1.Container{ - { - Name: JenkinsImageBuilderName, - Image: JenkinsImageBuilderImage, - Args: args, - VolumeMounts: volumeMounts, - }, - }, - Volumes: volumes, - }, - } - return p -} - -// NewDockerfileConfigMap returns a busybox pod with the same name/namespace as the cr. -func NewDockerfileConfigMap(cr *v1alpha2.JenkinsImage) *corev1.ConfigMap { - dockerfileContent := fmt.Sprintf(DockerfileTemplate, getDefaultedBaseImage(cr), getPluginsList(cr)) - name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileNameSuffix) - data := map[string]string{DockerfileName: dockerfileContent} - dockerfile := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: cr.Namespace, - }, - Data: data, - } - return dockerfile -} - -func getPluginsList(cr *v1alpha2.JenkinsImage) string { - logger := log.WithName("jenkinsimage_getPluginsList") - plugins := "" - for _, v := range cr.Spec.Plugins { - plugins += fmt.Sprintf(PluginDefinitionFormat, v.Name, v.Version) + " " - logger.Info(fmt.Sprintf("Adding plugin %s:%s ", v.Name, v.Version)) - } - return plugins -} - -func getDefaultedBaseImage(cr *v1alpha2.JenkinsImage) string { - if len(cr.Spec.BaseImage.Name) != 0 { - return cr.Spec.BaseImage.Name - } - return JenkinsImageDefaultBaseImage -} - -func getVolumes(cr *v1alpha2.JenkinsImage) []corev1.Volume { - name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileStorageSuffix) - storage := corev1.Volume{ - Name: name, - VolumeSource: corev1.VolumeSource{ - EmptyDir: &corev1.EmptyDirVolumeSource{}, - }, - } - - name = fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileNameSuffix) - config := corev1.Volume{ - Name: name, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: name}, - }, - }, - } - volumes := []corev1.Volume{storage, config} - return volumes -} - -func getVolumesMounts(cr *v1alpha2.JenkinsImage) []corev1.VolumeMount { - name := fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileStorageSuffix) - storage := corev1.VolumeMount{ - Name: name, - MountPath: "/workspace", - } - name = fmt.Sprintf(NameWithSuffixFormat, cr.Name, DockerfileNameSuffix) - config := corev1.VolumeMount{ - Name: name, - MountPath: "/workspace/dockerfile", - } - volumeMounts := []corev1.VolumeMount{storage, config} - return volumeMounts -} diff --git a/pkg/plugins/base_plugins.go b/pkg/plugins/base_plugins.go index 21b83ba6..7f8a761b 100644 --- a/pkg/plugins/base_plugins.go +++ b/pkg/plugins/base_plugins.go @@ -1,11 +1,11 @@ package plugins const ( - configurationAsCodePlugin = "configuration-as-code:1.46" + configurationAsCodePlugin = "configuration-as-code:1.47" gitPlugin = "git:4.5.0" jobDslPlugin = "job-dsl:1.77" kubernetesCredentialsProviderPlugin = "kubernetes-credentials-provider:0.15" - kubernetesPlugin = "kubernetes:1.28.6" + kubernetesPlugin = "kubernetes:1.29.0" workflowAggregatorPlugin = "workflow-aggregator:2.6" workflowJobPlugin = "workflow-job:2.40" ) From d225cc113e323fe44a648cf149ff661b96c2d254 Mon Sep 17 00:00:00 2001 From: Sylwia Brant Date: Mon, 8 Feb 2021 13:37:00 +0100 Subject: [PATCH 13/15] Minikube downloaded to bin, test fixes --- Makefile | 21 +- config.minikube.env | 4 +- deploy/all-in-one-v1alpha2.yaml | 168 - deploy/crds/jenkins.io_jenkins_crd.yaml | 3027 ----------------- deploy/crds/jenkins.io_jenkinsimages_crd.yaml | 85 - deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml | 15 - deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml | 106 - .../jenkins_v1alpha2_jenkinsimage_cr.yaml | 24 - .../jenkins_v1alpha2_jenkinsimage_crd.yaml | 85 - ...openshift_jenkins_v1alpha2_jenkins_cr.yaml | 73 - ...operator.v0.2.2.clusterserviceversion.yaml | 244 -- .../0.2.2/jenkins_v1alpha2_jenkins_crd.yaml | 20 - ...operator.v0.3.0.clusterserviceversion.yaml | 226 -- .../0.3.0/jenkins_v1alpha2_jenkins_crd.yaml | 20 - .../jenkins-operator.package.yaml | 5 - deploy/operator.yaml | 34 - deploy/role.yaml | 117 - deploy/role_binding.yaml | 12 - deploy/service_account.yaml | 5 - test/e2e/jenkins_configuration_test.go | 2 +- test/e2e/jenkins_test.go | 14 +- variables.mk | 1 + 22 files changed, 25 insertions(+), 4283 deletions(-) delete mode 100644 deploy/all-in-one-v1alpha2.yaml delete mode 100644 deploy/crds/jenkins.io_jenkins_crd.yaml delete mode 100644 deploy/crds/jenkins.io_jenkinsimages_crd.yaml delete mode 100644 deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml delete mode 100644 deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml delete mode 100644 deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml delete mode 100644 deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml delete mode 100644 deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml delete mode 100644 deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml delete mode 100644 deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml delete mode 100644 deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml delete mode 100644 deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml delete mode 100644 deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml delete mode 100644 deploy/operator.yaml delete mode 100644 deploy/role.yaml delete mode 100644 deploy/role_binding.yaml delete mode 100644 deploy/service_account.yaml diff --git a/Makefile b/Makefile index 4490caeb..254a96b6 100644 --- a/Makefile +++ b/Makefile @@ -101,7 +101,6 @@ vet: ## Verifies `go vet` passes .PHONY: staticcheck HAS_STATICCHECK := $(shell which $(PROJECT_DIR)/bin/staticcheck) -PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) staticcheck: ## Verifies `staticcheck` passes @echo "+ $@" ifndef HAS_STATICCHECK @@ -302,11 +301,21 @@ ifneq ($(KUBERNETES_PROVIDER),crc) $(error KUBERNETES_PROVIDER not set to 'crc') endif -.PHONY: minikube-start -minikube-start: check-minikube ## Start minikube +.PHONY: minikube +HAS_MINIKUBE := $(shell which $(PROJECT_DIR)/bin/minikube) +minikube: ## Download minikube if it's not present @echo "+ $@" - @minikube status && exit 0 || \ - minikube start --kubernetes-version $(MINIKUBE_KUBERNETES_VERSION) --dns-domain=$(CLUSTER_DOMAIN) --extra-config=kubelet.cluster-domain=$(CLUSTER_DOMAIN) --vm-driver=$(MINIKUBE_DRIVER) --memory 4096 --cpus 3 +ifndef HAS_MINIKUBE + mkdir -p $(PROJECT_DIR)/bin + wget -O $(PROJECT_DIR)/bin/minikube https://github.com/kubernetes/minikube/releases/download/v$(MINIKUBE_VERSION)/minikube-$(PLATFORM)-amd64 + chmod +x $(PROJECT_DIR)/bin/minikube +endif + +.PHONY: minikube-start +minikube-start: minikube check-minikube ## Start minikube + @echo "+ $@" + bin/minikube status && exit 0 || \ + bin/minikube start --kubernetes-version $(MINIKUBE_KUBERNETES_VERSION) --dns-domain=$(CLUSTER_DOMAIN) --extra-config=kubelet.cluster-domain=$(CLUSTER_DOMAIN) --vm-driver=$(MINIKUBE_DRIVER) --memory 4096 --cpus 3 .PHONY: crc-start crc-start: check-crc ## Start CodeReady Containers Kubernetes cluster @@ -315,7 +324,6 @@ crc-start: check-crc ## Start CodeReady Containers Kubernetes cluster .PHONY: sembump HAS_SEMBUMP := $(shell which $(PROJECT_DIR)/bin/sembump) -PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) sembump: # Download sembump locally if necessary @echo "+ $@" ifndef HAS_SEMBUMP @@ -448,7 +456,6 @@ endef .PHONY: operator-sdk HAS_OPERATOR_SDK := $(shell which $(PROJECT_DIR)/bin/operator-sdk) -PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) operator-sdk: # Download operator-sdk locally if necessary @echo "+ $@" ifndef HAS_OPERATOR_SDK diff --git a/config.minikube.env b/config.minikube.env index 309181c9..fcb35b14 100644 --- a/config.minikube.env +++ b/config.minikube.env @@ -2,9 +2,9 @@ KUBERNETES_PROVIDER=minikube MINIKUBE_KUBERNETES_VERSION=v1.17.4 MINIKUBE_DRIVER=virtualbox -MINIKUBE_VERSION=1.4.0 +MINIKUBE_VERSION=1.17.1 KUBECTL_CONTEXT=minikube -JENKINS_API_HOSTNAME_COMMAND=minikube ip +JENKINS_API_HOSTNAME_COMMAND=bin/minikube ip JENKINS_API_PORT=0 JENKINS_API_USE_NODEPORT=true diff --git a/deploy/all-in-one-v1alpha2.yaml b/deploy/all-in-one-v1alpha2.yaml deleted file mode 100644 index 4f013d84..00000000 --- a/deploy/all-in-one-v1alpha2.yaml +++ /dev/null @@ -1,168 +0,0 @@ ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: jenkins-operator ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: jenkins-operator -rules: - - apiGroups: - - "" - resources: - - services - - configmaps - - secrets - - serviceaccounts - verbs: - - get - - create - - update - - list - - watch - - apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' - - apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - - rolebindings - verbs: - - create - - update - - list - - watch - - apiGroups: - - "" - resources: - - pods/portforward - verbs: - - create - - apiGroups: - - "" - resources: - - pods/log - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods - - pods/exec - verbs: - - "*" - - apiGroups: - - "" - resources: - - events - verbs: - - watch - - list - - create - - patch - - apiGroups: - - apps - resourceNames: - - jenkins-operator - resources: - - deployments/finalizers - verbs: - - update - - apiGroups: - - jenkins.io - resources: - - '*' - verbs: - - '*' - - apiGroups: - - "" - resources: - - persistentvolumeclaims - verbs: - - get - - list - - watch - - apiGroups: - - "route.openshift.io" - resources: - - routes - verbs: - - get - - list - - watch - - create - - update - - apiGroups: - - "image.openshift.io" - resources: - - imagestreams - verbs: - - get - - list - - watch - - apiGroups: - - "build.openshift.io" - resources: - - builds - - buildconfigs - verbs: - - get - - list - - watch ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: jenkins-operator -subjects: -- kind: ServiceAccount - name: jenkins-operator -roleRef: - kind: Role - name: jenkins-operator - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: jenkins-operator -spec: - replicas: 1 - selector: - matchLabels: - name: jenkins-operator - template: - metadata: - labels: - name: jenkins-operator - spec: - serviceAccountName: jenkins-operator - containers: - - name: jenkins-operator - image: virtuslab/jenkins-operator:v0.5.0 - command: - - jenkins-operator - args: [] - imagePullPolicy: IfNotPresent - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: OPERATOR_NAME - value: "jenkins-operator" diff --git a/deploy/crds/jenkins.io_jenkins_crd.yaml b/deploy/crds/jenkins.io_jenkins_crd.yaml deleted file mode 100644 index 1d4e96c4..00000000 --- a/deploy/crds/jenkins.io_jenkins_crd.yaml +++ /dev/null @@ -1,3027 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: jenkins.jenkins.io -spec: - group: jenkins.io - names: - kind: Jenkins - listKind: JenkinsList - plural: jenkins - singular: jenkins - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: Jenkins is the Schema for the jenkins 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: Spec defines the desired state of the Jenkins - properties: - backup: - description: 'Backup defines configuration of Jenkins backup More info: - https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' - properties: - action: - description: Action defines action which performs backup in backup - container sidecar - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - type: object - containerName: - description: ContainerName is the container name responsible for - backup operation - type: string - interval: - description: Interval tells how often make backup in seconds Defaults - to 30. - format: int64 - type: integer - makeBackupBeforePodDeletion: - description: MakeBackupBeforePodDeletion tells operator to make - backup before Jenkins master pod deletion - type: boolean - required: - - action - - containerName - - interval - - makeBackupBeforePodDeletion - type: object - configurationAsCode: - description: ConfigurationAsCode defines configuration of Jenkins customization - via Configuration as Code Jenkins plugin - properties: - configurations: - items: - description: ConfigMapRef is reference to Kubernetes ConfigMap - properties: - name: - type: string - required: - - name - type: object - type: array - secret: - description: SecretRef is reference to Kubernetes secret - properties: - name: - type: string - required: - - name - type: object - required: - - configurations - - secret - type: object - groovyScripts: - description: GroovyScripts defines configuration of Jenkins customization - via groovy scripts - properties: - configurations: - items: - description: ConfigMapRef is reference to Kubernetes ConfigMap - properties: - name: - type: string - required: - - name - type: object - type: array - secret: - description: SecretRef is reference to Kubernetes secret - properties: - name: - type: string - required: - - name - type: object - required: - - configurations - - secret - type: object - jenkinsAPISettings: - description: JenkinsAPISettings defines configuration used by the operator - to gain admin access to the Jenkins API - properties: - authorizationStrategy: - description: AuthorizationStrategy defines authorization strategy - of the operator for the Jenkins API - type: string - required: - - authorizationStrategy - type: object - master: - description: Master represents Jenkins master pod properties and Jenkins - plugins. Every single change here requires a pod restart. - 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 - basePlugins: - description: 'BasePlugins contains plugins required by operator - Defaults to : - name: kubernetes version: 1.15.7 - name: workflow-job - version: "2.39" - name: workflow-aggregator version: "2.6" - name: - git version: 3.10.0 - name: job-dsl version: "1.74" - name: configuration-as-code - version: "1.19" - name: kubernetes-credentials-provider version: 0.12.1' - items: - description: Plugin defines Jenkins plugin - properties: - name: - description: Name is the name of Jenkins plugin - type: string - version: - description: Version is the version of Jenkins plugin - type: string - required: - - name - - version - type: object - type: array - containers: - description: 'List of containers belonging to the pod. Containers - cannot currently be added or removed. There must be at least one - container in a Pod. Defaults to: - image: jenkins/jenkins:lts imagePullPolicy: - Always livenessProbe: failureThreshold: 12 httpGet: path: - /login port: http scheme: HTTP initialDelaySeconds: - 80 periodSeconds: 10 successThreshold: 1 timeoutSeconds: - 5 name: jenkins-master readinessProbe: failureThreshold: - 3 httpGet: path: /login port: http scheme: - HTTP initialDelaySeconds: 30 periodSeconds: 10 successThreshold: - 1 timeoutSeconds: 1 resources: limits: cpu: 1500m memory: - 3Gi requests: cpu: "1" memory: 600Mi' - items: - description: Container defines Kubernetes container attributes - properties: - args: - description: 'Arguments to the entrypoint. The docker image''s - CMD is used if this is not provided. Variable references - $(VAR_NAME) are expanded using the container''s environment. - If a variable cannot be resolved, the reference in the input - string will be unchanged. The $(VAR_NAME) syntax can be - escaped with a double $$, ie: $$(VAR_NAME). Escaped references - will never be expanded, regardless of whether the variable - exists or not. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - command: - description: 'Entrypoint array. Not executed within a shell. - The docker image''s ENTRYPOINT is used if this is not provided. - Variable references $(VAR_NAME) are expanded using the container''s - environment. If a variable cannot be resolved, the reference - in the input string will be unchanged. The $(VAR_NAME) syntax - can be escaped with a double $$, ie: $$(VAR_NAME). Escaped - references will never be expanded, regardless of whether - the variable exists or not. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' - items: - type: string - type: array - env: - description: List of environment variables to set in the container. - items: - description: EnvVar represents an environment variable present - in a Container. - properties: - name: - description: Name of the environment variable. Must - be a C_IDENTIFIER. - type: string - value: - description: 'Variable references $(VAR_NAME) are expanded - using the previous defined environment variables in - the container and any service environment variables. - If a variable cannot be resolved, the reference in - the input string will be unchanged. The $(VAR_NAME) - syntax can be escaped with a double $$, ie: $$(VAR_NAME). - Escaped references will never be expanded, regardless - of whether the variable exists or not. Defaults to - "".' - type: string - valueFrom: - description: Source for the environment variable's value. - Cannot be used if value is not empty. - properties: - configMapKeyRef: - description: Selects a key of a ConfigMap. - properties: - key: - description: The key to select. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap or - its key must be defined - type: boolean - required: - - key - type: object - fieldRef: - description: 'Selects a field of the pod: supports - metadata.name, metadata.namespace, metadata.labels, - metadata.annotations, spec.nodeName, spec.serviceAccountName, - status.hostIP, status.podIP.' - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in - the specified API version. - type: string - required: - - fieldPath - type: object - resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, limits.ephemeral-storage, requests.cpu, - requests.memory and requests.ephemeral-storage) - are currently supported.' - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: - description: Specifies the output format of - the exposed resources, defaults to "1" - type: string - resource: - description: 'Required: resource to select' - type: string - required: - - resource - type: object - secretKeyRef: - description: Selects a key of a secret in the pod's - namespace - properties: - key: - description: The key of the secret to select - from. Must be a valid secret key. - type: string - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - required: - - key - type: object - type: object - required: - - name - type: object - type: array - envFrom: - description: List of sources to populate environment variables - in the container. The keys defined within a source must - be a C_IDENTIFIER. All invalid keys will be reported as - an event when the container is starting. When a key exists - in multiple sources, the value associated with the last - source will take precedence. Values defined by an Env with - a duplicate key will take precedence. - items: - description: EnvFromSource represents the source of a set - of ConfigMaps - properties: - configMapRef: - description: The ConfigMap to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the ConfigMap must - be defined - type: boolean - type: object - prefix: - description: An optional identifier to prepend to each - key in the ConfigMap. Must be a C_IDENTIFIER. - type: string - secretRef: - description: The Secret to select from - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - optional: - description: Specify whether the Secret must be - defined - type: boolean - type: object - type: object - type: array - image: - description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images' - type: string - imagePullPolicy: - description: Image pull policy. One of Always, Never, IfNotPresent. - Defaults to Always. - type: string - lifecycle: - description: Actions that the management system should take - in response to container lifecycle events. - properties: - postStart: - description: 'PostStart is called immediately after a - container is created. If the handler fails, the container - is terminated and restarted according to its restart - policy. Other management of the container blocks until - the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: One and only one of the following should - be specified. Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory - for the command is root ('/') in the container's - filesystem. The command is simply exec'd, it - is not run inside a shell, so traditional shell - instructions ('|', etc) won't work. To use a - shell, you need to explicitly call out to that - shell. Exit status of 0 is treated as live/healthy - and non-zero is unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to - perform. - properties: - host: - description: Host name to connect to, defaults - to the pod IP. You probably want to set "Host" - in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: 'TCPSocket specifies an action involving - a TCP port. TCP hooks not yet supported TODO: implement - a realistic TCP lifecycle hook' - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - preStop: - description: 'PreStop is called immediately before a container - is terminated due to an API request or management event - such as liveness/startup probe failure, preemption, - resource contention, etc. The handler is not called - if the container crashes or exits. The reason for termination - is passed to the handler. The Pod''s termination grace - period countdown begins before the PreStop hooked is - executed. Regardless of the outcome of the handler, - the container will eventually terminate within the Pod''s - termination grace period. Other management of the container - blocks until the hook completes or until the termination - grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' - properties: - exec: - description: One and only one of the following should - be specified. Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory - for the command is root ('/') in the container's - filesystem. The command is simply exec'd, it - is not run inside a shell, so traditional shell - instructions ('|', etc) won't work. To use a - shell, you need to explicitly call out to that - shell. Exit status of 0 is treated as live/healthy - and non-zero is unhealthy. - items: - type: string - type: array - type: object - httpGet: - description: HTTPGet specifies the http request to - perform. - properties: - host: - description: Host name to connect to, defaults - to the pod IP. You probably want to set "Host" - in httpHeaders instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the - host. Defaults to HTTP. - type: string - required: - - port - type: object - tcpSocket: - description: 'TCPSocket specifies an action involving - a TCP port. TCP hooks not yet supported TODO: implement - a realistic TCP lifecycle hook' - properties: - host: - description: 'Optional: Host name to connect to, - defaults to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range - 1 to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - type: object - type: object - livenessProbe: - description: Periodic probe of container liveness. Container - will be restarted if the probe fails. - properties: - exec: - description: One and only one of the following should - be specified. Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range 1 - to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: 'TCPSocket specifies an action involving - a TCP port. TCP hooks not yet supported TODO: implement - a realistic TCP lifecycle hook' - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range 1 - to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - timeoutSeconds: - description: 'Number of seconds after which the probe - times out. Defaults to 1 second. Minimum value is 1. - More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - name: - description: Name of the container specified as a DNS_LABEL. - Each container in a pod must have a unique name (DNS_LABEL). - type: string - ports: - description: List of ports to expose from the container. Exposing - a port here gives the system additional information about - the network connections a container uses, but is primarily - informational. Not specifying a port here DOES NOT prevent - that port from being exposed. Any port which is listening - on the default "0.0.0.0" address inside a container will - be accessible from the network. - items: - description: ContainerPort represents a network port in - a single container. - properties: - containerPort: - description: Number of port to expose on the pod's IP - address. This must be a valid port number, 0 < x < - 65536. - format: int32 - type: integer - hostIP: - description: What host IP to bind the external port - to. - type: string - hostPort: - description: Number of port to expose on the host. If - specified, this must be a valid port number, 0 < x - < 65536. If HostNetwork is specified, this must match - ContainerPort. Most containers do not need this. - format: int32 - type: integer - name: - description: If specified, this must be an IANA_SVC_NAME - and unique within the pod. Each named port in a pod - must have a unique name. Name for the port that can - be referred to by services. - type: string - protocol: - description: Protocol for port. Must be UDP, TCP, or - SCTP. Defaults to "TCP". - type: string - required: - - containerPort - type: object - type: array - readinessProbe: - description: Periodic probe of container service readiness. - Container will be removed from service endpoints if the - probe fails. - properties: - exec: - description: One and only one of the following should - be specified. Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute - inside the container, the working directory for - the command is root ('/') in the container's filesystem. - The command is simply exec'd, it is not run inside - a shell, so traditional shell instructions ('|', - etc) won't work. To use a shell, you need to explicitly - call out to that shell. Exit status of 0 is treated - as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - failureThreshold: - description: Minimum consecutive failures for the probe - to be considered failed after having succeeded. Defaults - to 3. Minimum value is 1. - format: int32 - type: integer - httpGet: - description: HTTPGet specifies the http request to perform. - properties: - host: - description: Host name to connect to, defaults to - the pod IP. You probably want to set "Host" in httpHeaders - instead. - type: string - httpHeaders: - description: Custom headers to set in the request. - HTTP allows repeated headers. - items: - description: HTTPHeader describes a custom header - to be used in HTTP probes - properties: - name: - description: The header field name - type: string - value: - description: The header field value - type: string - required: - - name - - value - type: object - type: array - path: - description: Path to access on the HTTP server. - type: string - port: - anyOf: - - type: integer - - type: string - description: Name or number of the port to access - on the container. Number must be in the range 1 - to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - scheme: - description: Scheme to use for connecting to the host. - Defaults to HTTP. - type: string - required: - - port - type: object - initialDelaySeconds: - description: 'Number of seconds after the container has - started before liveness probes are initiated. More info: - https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - periodSeconds: - description: How often (in seconds) to perform the probe. - Default to 10 seconds. Minimum value is 1. - format: int32 - type: integer - successThreshold: - description: Minimum consecutive successes for the probe - to be considered successful after having failed. Defaults - to 1. Must be 1 for liveness and startup. Minimum value - is 1. - format: int32 - type: integer - tcpSocket: - description: 'TCPSocket specifies an action involving - a TCP port. TCP hooks not yet supported TODO: implement - a realistic TCP lifecycle hook' - properties: - host: - description: 'Optional: Host name to connect to, defaults - to the pod IP.' - type: string - port: - anyOf: - - type: integer - - type: string - description: Number or name of the port to access - on the container. Number must be in the range 1 - to 65535. Name must be an IANA_SVC_NAME. - x-kubernetes-int-or-string: true - required: - - port - type: object - timeoutSeconds: - description: 'Number of seconds after which the probe - times out. Defaults to 1 second. Minimum value is 1. - More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' - format: int32 - type: integer - type: object - resources: - description: 'Compute Resources required by this container. - More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - properties: - limits: - additionalProperties: - type: string - description: 'Limits describes the maximum amount of compute - resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - requests: - additionalProperties: - type: string - description: 'Requests describes the minimum amount of - compute resources required. If Requests is omitted for - a container, it defaults to Limits if that is explicitly - specified, otherwise to an implementation-defined value. - More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' - type: object - type: object - securityContext: - description: 'Security options the pod should run with. More - info: https://kubernetes.io/docs/concepts/policy/security-context/ - More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' - properties: - allowPrivilegeEscalation: - description: 'AllowPrivilegeEscalation controls whether - a process can gain more privileges than its parent process. - This bool directly controls if the no_new_privs flag - will be set on the container process. AllowPrivilegeEscalation - is true always when the container is: 1) run as Privileged - 2) has CAP_SYS_ADMIN' - type: boolean - capabilities: - description: The capabilities to add/drop when running - containers. Defaults to the default set of capabilities - granted by the container runtime. - properties: - add: - description: Added capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - drop: - description: Removed capabilities - items: - description: Capability represent POSIX capabilities - type - type: string - type: array - type: object - privileged: - description: Run container in privileged mode. Processes - in privileged containers are essentially equivalent - to root on the host. Defaults to false. - type: boolean - procMount: - description: procMount denotes the type of proc mount - to use for the containers. The default is DefaultProcMount - which uses the container runtime defaults for readonly - paths and masked paths. This requires the ProcMountType - feature flag to be enabled. - type: string - readOnlyRootFilesystem: - description: Whether this container has a read-only root - filesystem. Default is false. - type: boolean - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be - set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as - a non-root user. If true, the Kubelet will validate - the image at runtime to ensure that it does not run - as UID 0 (root) and fail to start the container if it - does. If unset or false, no such validation will be - performed. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata - if unspecified. May also be set in PodSecurityContext. If - set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to the - container. If unspecified, the container runtime will - allocate a random SELinux context for each container. May - also be set in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - properties: - level: - description: Level is SELinux level label that applies - to the container. - type: string - role: - description: Role is a SELinux role label that applies - to the container. - type: string - type: - description: Type is a SELinux type label that applies - to the container. - type: string - user: - description: User is a SELinux user label that applies - to the container. - type: string - type: object - windowsOptions: - description: The Windows specific settings applied to - all containers. If unspecified, the options from the - PodSecurityContext will be used. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA - admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec - named by the GMSACredentialSpecName field. This - field is alpha-level and is only honored by servers - that enable the WindowsGMSA feature flag. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of - the GMSA credential spec to use. This field is alpha-level - and is only honored by servers that enable the WindowsGMSA - feature flag. - type: string - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set - in PodSecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence. This field is alpha-level and - it is only honored by servers that enable the WindowsRunAsUserName - feature flag. - type: string - type: object - type: object - volumeMounts: - description: Pod volumes to mount into the container's filesystem. - items: - description: VolumeMount describes a mounting of a Volume - within a container. - properties: - mountPath: - description: Path within the container at which the - volume should be mounted. Must not contain ':'. - type: string - mountPropagation: - description: mountPropagation determines how mounts - are propagated from the host to container and the - other way around. When not set, MountPropagationNone - is used. This field is beta in 1.10. - type: string - name: - description: This must match the Name of a Volume. - type: string - readOnly: - description: Mounted read-only if true, read-write otherwise - (false or unspecified). Defaults to false. - type: boolean - subPath: - description: Path within the volume from which the container's - volume should be mounted. Defaults to "" (volume's - root). - type: string - subPathExpr: - description: Expanded path within the volume from which - the container's volume should be mounted. Behaves - similarly to SubPath but environment variable references - $(VAR_NAME) are expanded using the container's environment. - Defaults to "" (volume's root). SubPathExpr and SubPath - are mutually exclusive. This field is beta in 1.15. - type: string - required: - - mountPath - - name - type: object - type: array - workingDir: - description: Container's working directory. If not specified, - the container runtime's default will be used, which might - be configured in the container image. - type: string - required: - - image - - imagePullPolicy - - name - - resources - type: object - type: array - disableCSRFProtection: - description: DisableCSRFProtection allows you to toggle CSRF Protection - on Jenkins - type: boolean - imagePullSecrets: - description: 'ImagePullSecrets is an optional list of references - to secrets in the same namespace to use for pulling any of the - images used by this PodSpec. If specified, these secrets will - be passed to individual puller implementations for them to use. - For example, in the case of docker, only DockerConfig type secrets - are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' - items: - description: LocalObjectReference contains enough information - to let you locate the referenced object inside the same namespace. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - type: object - type: array - 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 - masterAnnotations: - 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 - Deprecated: will be removed in the future, please use Annotations(annotations)' - type: object - nodeSelector: - additionalProperties: - type: string - description: 'NodeSelector is a selector which must be true for - the pod to fit on a node. Selector which must match a node''s - labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' - type: object - plugins: - description: Plugins contains plugins required by user - items: - description: Plugin defines Jenkins plugin - properties: - name: - description: Name is the name of Jenkins plugin - type: string - version: - description: Version is the version of Jenkins plugin - type: string - required: - - name - - version - type: object - type: array - securityContext: - description: 'SecurityContext that applies to all the containers - of the Jenkins Master. As per kubernetes specification, it can - be overridden for each container individually. Defaults to: runAsUser: - 1000 fsGroup: 1000' - properties: - fsGroup: - description: "A special supplemental group that applies to all - containers in a pod. Some volume types allow the Kubelet to - change the ownership of that volume to be owned by the pod: - \n 1. The owning GID will be the FSGroup 2. The setgid bit - is set (new files created in the volume will be owned by FSGroup) - 3. The permission bits are OR'd with rw-rw---- \n If unset, - the Kubelet will not modify the ownership and permissions - of any volume." - format: int64 - type: integer - runAsGroup: - description: The GID to run the entrypoint of the container - process. Uses runtime default if unset. May also be set in - SecurityContext. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence for - that container. - format: int64 - type: integer - runAsNonRoot: - description: Indicates that the container must run as a non-root - user. If true, the Kubelet will validate the image at runtime - to ensure that it does not run as UID 0 (root) and fail to - start the container if it does. If unset or false, no such - validation will be performed. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence. - type: boolean - runAsUser: - description: The UID to run the entrypoint of the container - process. Defaults to user specified in image metadata if unspecified. - May also be set in SecurityContext. If set in both SecurityContext - and PodSecurityContext, the value specified in SecurityContext - takes precedence for that container. - format: int64 - type: integer - seLinuxOptions: - description: The SELinux context to be applied to all containers. - If unspecified, the container runtime will allocate a random - SELinux context for each container. May also be set in SecurityContext. If - set in both SecurityContext and PodSecurityContext, the value - specified in SecurityContext takes precedence for that container. - properties: - level: - description: Level is SELinux level label that applies to - the container. - type: string - role: - description: Role is a SELinux role label that applies to - the container. - type: string - type: - description: Type is a SELinux type label that applies to - the container. - type: string - user: - description: User is a SELinux user label that applies to - the container. - type: string - type: object - supplementalGroups: - description: A list of groups applied to the first process run - in each container, in addition to the container's primary - GID. If unspecified, no groups will be added to any container. - items: - format: int64 - type: integer - type: array - sysctls: - description: Sysctls hold a list of namespaced sysctls used - for the pod. Pods with unsupported sysctls (by the container - runtime) might fail to launch. - items: - description: Sysctl defines a kernel parameter to be set - properties: - name: - description: Name of a property to set - type: string - value: - description: Value of a property to set - type: string - required: - - name - - value - type: object - type: array - windowsOptions: - description: The Windows specific settings applied to all containers. - If unspecified, the options within a container's SecurityContext - will be used. If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - properties: - gmsaCredentialSpec: - description: GMSACredentialSpec is where the GMSA admission - webhook (https://github.com/kubernetes-sigs/windows-gmsa) - inlines the contents of the GMSA credential spec named - by the GMSACredentialSpecName field. This field is alpha-level - and is only honored by servers that enable the WindowsGMSA - feature flag. - type: string - gmsaCredentialSpecName: - description: GMSACredentialSpecName is the name of the GMSA - credential spec to use. This field is alpha-level and - is only honored by servers that enable the WindowsGMSA - feature flag. - type: string - runAsUserName: - description: The UserName in Windows to run the entrypoint - of the container process. Defaults to the user specified - in image metadata if unspecified. May also be set in PodSecurityContext. - If set in both SecurityContext and PodSecurityContext, - the value specified in SecurityContext takes precedence. - This field is alpha-level and it is only honored by servers - that enable the WindowsRunAsUserName feature flag. - type: string - type: object - type: object - tolerations: - description: If specified, the pod's tolerations. - items: - description: The pod this Toleration is attached to tolerates - any taint that matches the triple using the - matching operator . - properties: - effect: - description: Effect indicates the taint effect to match. Empty - means match all taint effects. When specified, allowed values - are NoSchedule, PreferNoSchedule and NoExecute. - type: string - key: - description: Key is the taint key that the toleration applies - to. Empty means match all taint keys. If the key is empty, - operator must be Exists; this combination means to match - all values and all keys. - type: string - operator: - description: Operator represents a key's relationship to the - value. Valid operators are Exists and Equal. Defaults to - Equal. Exists is equivalent to wildcard for value, so that - a pod can tolerate all taints of a particular category. - type: string - tolerationSeconds: - description: TolerationSeconds represents the period of time - the toleration (which must be of effect NoExecute, otherwise - this field is ignored) tolerates the taint. By default, - it is not set, which means tolerate the taint forever (do - not evict). Zero and negative values will be treated as - 0 (evict immediately) by the system. - format: int64 - type: integer - value: - description: Value is the taint value the toleration matches - to. If the operator is Exists, the value should be empty, - otherwise just a regular string. - type: string - type: object - type: array - volumes: - description: 'List of volumes that can be mounted by containers - belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' - items: - description: Volume represents a named volume in a pod that may - be accessed by any container in the pod. - properties: - awsElasticBlockStore: - description: 'AWSElasticBlockStore represents an AWS Disk - resource that is attached to a kubelet''s host machine and - then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' - properties: - fsType: - description: 'Filesystem type of the volume that you want - to mount. Tip: Ensure that the filesystem type is supported - by the host operating system. Examples: "ext4", "xfs", - "ntfs". Implicitly inferred to be "ext4" if unspecified. - More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - partition: - description: 'The partition in the volume that you want - to mount. If omitted, the default is to mount by volume - name. Examples: For volume /dev/sda1, you specify the - partition as "1". Similarly, the volume partition for - /dev/sda is "0" (or you can leave the property empty).' - format: int32 - type: integer - readOnly: - description: 'Specify "true" to force and set the ReadOnly - property in VolumeMounts to "true". If omitted, the - default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' - type: boolean - volumeID: - description: 'Unique ID of the persistent disk resource - in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' - type: string - required: - - volumeID - type: object - azureDisk: - description: AzureDisk represents an Azure Data Disk mount - on the host and bind mount to the pod. - properties: - cachingMode: - description: 'Host Caching mode: None, Read Only, Read - Write.' - type: string - diskName: - description: The Name of the data disk in the blob storage - type: string - diskURI: - description: The URI the data disk in the blob storage - type: string - fsType: - description: Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Ex. "ext4", - "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - type: string - kind: - description: 'Expected values Shared: multiple blob disks - per storage account Dedicated: single blob disk per - storage account Managed: azure managed data disk (only - in managed availability set). defaults to shared' - type: string - readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - required: - - diskName - - diskURI - type: object - azureFile: - description: AzureFile represents an Azure File Service mount - on the host and bind mount to the pod. - properties: - readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - secretName: - description: the name of secret that contains Azure Storage - Account Name and Key - type: string - shareName: - description: Share Name - type: string - required: - - secretName - - shareName - type: object - cephfs: - description: CephFS represents a Ceph FS mount on the host - that shares a pod's lifetime - properties: - monitors: - description: 'Required: Monitors is a collection of Ceph - monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - items: - type: string - type: array - path: - description: 'Optional: Used as the mounted root, rather - than the full Ceph tree, default is /' - type: string - readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting in VolumeMounts. - More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - type: boolean - secretFile: - description: 'Optional: SecretFile is the path to key - ring for User, default is /etc/ceph/user.secret More - info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - type: string - secretRef: - description: 'Optional: SecretRef is reference to the - authentication secret for User, default is empty. More - info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - user: - description: 'Optional: User is the rados user name, default - is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' - type: string - required: - - monitors - type: object - cinder: - description: 'Cinder represents a cinder volume attached and - mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - properties: - fsType: - description: 'Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Examples: - "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" - if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - type: string - readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting in VolumeMounts. - More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - type: boolean - secretRef: - description: 'Optional: points to a secret object containing - parameters used to connect to OpenStack.' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - volumeID: - description: 'volume id used to identify the volume in - cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' - type: string - required: - - volumeID - type: object - configMap: - description: ConfigMap represents a configMap that should - populate this volume - properties: - defaultMode: - description: 'Optional: mode bits to use on created files - by default. Must be a value between 0 and 0777. Defaults - to 0644. Directories within the path are not affected - by this setting. This might be in conflict with other - options that affect the file mode, like fsGroup, and - the result can be other mode bits set.' - format: int32 - type: integer - items: - description: If unspecified, each key-value pair in the - Data field of the referenced ConfigMap will be projected - into the volume as a file whose name is the key and - content is the value. If specified, the listed keys - will be projected into the specified paths, and unlisted - keys will not be present. If a key is specified which - is not present in the ConfigMap, the volume setup will - error unless it is marked optional. Paths must be relative - and may not contain the '..' path or start with '..'. - items: - description: Maps a string key to a path within a volume. - properties: - key: - description: The key to project. - type: string - mode: - description: 'Optional: mode bits to use on this - file, must be a value between 0 and 0777. If not - specified, the volume defaultMode will be used. - This might be in conflict with other options that - affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - path: - description: The relative path of the file to map - the key to. May not be an absolute path. May not - contain the path element '..'. May not start with - the string '..'. - type: string - required: - - key - - path - type: object - type: array - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap or its keys - must be defined - type: boolean - type: object - csi: - description: CSI (Container Storage Interface) represents - storage that is handled by an external CSI driver (Alpha - feature). - properties: - driver: - description: Driver is the name of the CSI driver that - handles this volume. Consult with your admin for the - correct name as registered in the cluster. - type: string - fsType: - description: Filesystem type to mount. Ex. "ext4", "xfs", - "ntfs". If not provided, the empty value is passed to - the associated CSI driver which will determine the default - filesystem to apply. - type: string - nodePublishSecretRef: - description: NodePublishSecretRef is a reference to the - secret object containing sensitive information to pass - to the CSI driver to complete the CSI NodePublishVolume - and NodeUnpublishVolume calls. This field is optional, - and may be empty if no secret is required. If the secret - object contains more than one secret, all secret references - are passed. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - readOnly: - description: Specifies a read-only configuration for the - volume. Defaults to false (read/write). - type: boolean - volumeAttributes: - additionalProperties: - type: string - description: VolumeAttributes stores driver-specific properties - that are passed to the CSI driver. Consult your driver's - documentation for supported values. - type: object - required: - - driver - type: object - downwardAPI: - description: DownwardAPI represents downward API about the - pod that should populate this volume - properties: - defaultMode: - description: 'Optional: mode bits to use on created files - by default. Must be a value between 0 and 0777. Defaults - to 0644. Directories within the path are not affected - by this setting. This might be in conflict with other - options that affect the file mode, like fsGroup, and - the result can be other mode bits set.' - format: int32 - type: integer - items: - description: Items is a list of downward API volume file - items: - description: DownwardAPIVolumeFile represents information - to create the file containing the pod field - properties: - fieldRef: - description: 'Required: Selects a field of the pod: - only annotations, labels, name and namespace are - supported.' - properties: - apiVersion: - description: Version of the schema the FieldPath - is written in terms of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to select in - the specified API version. - type: string - required: - - fieldPath - type: object - mode: - description: 'Optional: mode bits to use on this - file, must be a value between 0 and 0777. If not - specified, the volume defaultMode will be used. - This might be in conflict with other options that - affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - path: - description: 'Required: Path is the relative path - name of the file to be created. Must not be absolute - or contain the ''..'' path. Must be utf-8 encoded. - The first item of the relative path must not start - with ''..''' - type: string - resourceFieldRef: - description: 'Selects a resource of the container: - only resources limits and requests (limits.cpu, - limits.memory, requests.cpu and requests.memory) - are currently supported.' - properties: - containerName: - description: 'Container name: required for volumes, - optional for env vars' - type: string - divisor: - description: Specifies the output format of - the exposed resources, defaults to "1" - type: string - resource: - description: 'Required: resource to select' - type: string - required: - - resource - type: object - required: - - path - type: object - type: array - type: object - emptyDir: - description: 'EmptyDir represents a temporary directory that - shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - properties: - medium: - description: 'What type of storage medium should back - this directory. The default is "" which means to use - the node''s default medium. Must be an empty string - (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' - type: string - sizeLimit: - description: 'Total amount of local storage required for - this EmptyDir volume. The size limit is also applicable - for memory medium. The maximum usage on memory medium - EmptyDir would be the minimum value between the SizeLimit - specified here and the sum of memory limits of all containers - in a pod. The default is nil which means that the limit - is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' - type: string - type: object - fc: - description: FC represents a Fibre Channel resource that is - attached to a kubelet's host machine and then exposed to - the pod. - properties: - fsType: - description: 'Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Ex. "ext4", - "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - lun: - description: 'Optional: FC target lun number' - format: int32 - type: integer - readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting in VolumeMounts.' - type: boolean - targetWWNs: - description: 'Optional: FC target worldwide names (WWNs)' - items: - type: string - type: array - wwids: - description: 'Optional: FC volume world wide identifiers - (wwids) Either wwids or combination of targetWWNs and - lun must be set, but not both simultaneously.' - items: - type: string - type: array - type: object - flexVolume: - description: FlexVolume represents a generic volume resource - that is provisioned/attached using an exec based plugin. - properties: - driver: - description: Driver is the name of the driver to use for - this volume. - type: string - fsType: - description: Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Ex. "ext4", - "xfs", "ntfs". The default filesystem depends on FlexVolume - script. - type: string - options: - additionalProperties: - type: string - description: 'Optional: Extra command options if any.' - type: object - readOnly: - description: 'Optional: Defaults to false (read/write). - ReadOnly here will force the ReadOnly setting in VolumeMounts.' - type: boolean - secretRef: - description: 'Optional: SecretRef is reference to the - secret object containing sensitive information to pass - to the plugin scripts. This may be empty if no secret - object is specified. If the secret object contains more - than one secret, all secrets are passed to the plugin - scripts.' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - required: - - driver - type: object - flocker: - description: Flocker represents a Flocker volume attached - to a kubelet's host machine. This depends on the Flocker - control service being running - properties: - datasetName: - description: Name of the dataset stored as metadata -> - name on the dataset for Flocker should be considered - as deprecated - type: string - datasetUUID: - description: UUID of the dataset. This is unique identifier - of a Flocker dataset - type: string - type: object - gcePersistentDisk: - description: 'GCEPersistentDisk represents a GCE Disk resource - that is attached to a kubelet''s host machine and then exposed - to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - properties: - fsType: - description: 'Filesystem type of the volume that you want - to mount. Tip: Ensure that the filesystem type is supported - by the host operating system. Examples: "ext4", "xfs", - "ntfs". Implicitly inferred to be "ext4" if unspecified. - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - partition: - description: 'The partition in the volume that you want - to mount. If omitted, the default is to mount by volume - name. Examples: For volume /dev/sda1, you specify the - partition as "1". Similarly, the volume partition for - /dev/sda is "0" (or you can leave the property empty). - More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - format: int32 - type: integer - pdName: - description: 'Unique name of the PD resource in GCE. Used - to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - type: string - readOnly: - description: 'ReadOnly here will force the ReadOnly setting - in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' - type: boolean - required: - - pdName - type: object - gitRepo: - description: 'GitRepo represents a git repository at a particular - revision. DEPRECATED: GitRepo is deprecated. To provision - a container with a git repo, mount an EmptyDir into an InitContainer - that clones the repo using git, then mount the EmptyDir - into the Pod''s container.' - properties: - directory: - description: Target directory name. Must not contain or - start with '..'. If '.' is supplied, the volume directory - will be the git repository. Otherwise, if specified, - the volume will contain the git repository in the subdirectory - with the given name. - type: string - repository: - description: Repository URL - type: string - revision: - description: Commit hash for the specified revision. - type: string - required: - - repository - type: object - glusterfs: - description: 'Glusterfs represents a Glusterfs mount on the - host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' - properties: - endpoints: - description: 'EndpointsName is the endpoint name that - details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' - type: string - path: - description: 'Path is the Glusterfs volume path. More - info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' - type: string - readOnly: - description: 'ReadOnly here will force the Glusterfs volume - to be mounted with read-only permissions. Defaults to - false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' - type: boolean - required: - - endpoints - - path - type: object - hostPath: - description: 'HostPath represents a pre-existing file or directory - on the host machine that is directly exposed to the container. - This is generally used for system agents or other privileged - things that are allowed to see the host machine. Most containers - will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath - --- TODO(jonesdl) We need to restrict who can use host directory - mounts and who can/can not mount host directories as read/write.' - properties: - path: - description: 'Path of the directory on the host. If the - path is a symlink, it will follow the link to the real - path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' - type: string - type: - description: 'Type for HostPath Volume Defaults to "" - More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' - type: string - required: - - path - type: object - iscsi: - description: 'ISCSI represents an ISCSI Disk resource that - is attached to a kubelet''s host machine and then exposed - to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' - properties: - chapAuthDiscovery: - description: whether support iSCSI Discovery CHAP authentication - type: boolean - chapAuthSession: - description: whether support iSCSI Session CHAP authentication - type: boolean - fsType: - description: 'Filesystem type of the volume that you want - to mount. Tip: Ensure that the filesystem type is supported - by the host operating system. Examples: "ext4", "xfs", - "ntfs". Implicitly inferred to be "ext4" if unspecified. - More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - initiatorName: - description: Custom iSCSI Initiator Name. If initiatorName - is specified with iscsiInterface simultaneously, new - iSCSI interface : will be - created for the connection. - type: string - iqn: - description: Target iSCSI Qualified Name. - type: string - iscsiInterface: - description: iSCSI Interface Name that uses an iSCSI transport. - Defaults to 'default' (tcp). - type: string - lun: - description: iSCSI Target Lun number. - format: int32 - type: integer - portals: - description: iSCSI Target Portal List. The portal is either - an IP or ip_addr:port if the port is other than default - (typically TCP ports 860 and 3260). - items: - type: string - type: array - readOnly: - description: ReadOnly here will force the ReadOnly setting - in VolumeMounts. Defaults to false. - type: boolean - secretRef: - description: CHAP Secret for iSCSI target and initiator - authentication - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - targetPortal: - description: iSCSI Target Portal. The Portal is either - an IP or ip_addr:port if the port is other than default - (typically TCP ports 860 and 3260). - type: string - required: - - iqn - - lun - - targetPortal - type: object - name: - description: 'Volume''s name. Must be a DNS_LABEL and unique - within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' - type: string - nfs: - description: 'NFS represents an NFS mount on the host that - shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - properties: - path: - description: 'Path that is exported by the NFS server. - More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - type: string - readOnly: - description: 'ReadOnly here will force the NFS export - to be mounted with read-only permissions. Defaults to - false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - type: boolean - server: - description: 'Server is the hostname or IP address of - the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' - type: string - required: - - path - - server - type: object - persistentVolumeClaim: - description: 'PersistentVolumeClaimVolumeSource represents - a reference to a PersistentVolumeClaim in the same namespace. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - properties: - claimName: - description: 'ClaimName is the name of a PersistentVolumeClaim - in the same namespace as the pod using this volume. - More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' - type: string - readOnly: - description: Will force the ReadOnly setting in VolumeMounts. - Default false. - type: boolean - required: - - claimName - type: object - photonPersistentDisk: - description: PhotonPersistentDisk represents a PhotonController - persistent disk attached and mounted on kubelets host machine - properties: - fsType: - description: Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Ex. "ext4", - "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - type: string - pdID: - description: ID that identifies Photon Controller persistent - disk - type: string - required: - - pdID - type: object - portworxVolume: - description: PortworxVolume represents a portworx volume attached - and mounted on kubelets host machine - properties: - fsType: - description: FSType represents the filesystem type to - mount Must be a filesystem type supported by the host - operating system. Ex. "ext4", "xfs". Implicitly inferred - to be "ext4" if unspecified. - type: string - readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - volumeID: - description: VolumeID uniquely identifies a Portworx volume - type: string - required: - - volumeID - type: object - projected: - description: Items for all in one resources secrets, configmaps, - and downward API - properties: - defaultMode: - description: Mode bits to use on created files by default. - Must be a value between 0 and 0777. Directories within - the path are not affected by this setting. This might - be in conflict with other options that affect the file - mode, like fsGroup, and the result can be other mode - bits set. - format: int32 - type: integer - sources: - description: list of volume projections - items: - description: Projection that may be projected along - with other supported volume types - properties: - configMap: - description: information about the configMap data - to project - properties: - items: - description: If unspecified, each key-value - pair in the Data field of the referenced ConfigMap - will be projected into the volume as a file - whose name is the key and content is the value. - If specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified - which is not present in the ConfigMap, the - volume setup will error unless it is marked - optional. Paths must be relative and may not - contain the '..' path or start with '..'. - items: - description: Maps a string key to a path within - a volume. - properties: - key: - description: The key to project. - type: string - mode: - description: 'Optional: mode bits to use - on this file, must be a value between - 0 and 0777. If not specified, the volume - defaultMode will be used. This might - be in conflict with other options that - affect the file mode, like fsGroup, - and the result can be other mode bits - set.' - format: int32 - type: integer - path: - description: The relative path of the - file to map the key to. May not be an - absolute path. May not contain the path - element '..'. May not start with the - string '..'. - type: string - required: - - key - - path - type: object - type: array - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the ConfigMap or - its keys must be defined - type: boolean - type: object - downwardAPI: - description: information about the downwardAPI data - to project - properties: - items: - description: Items is a list of DownwardAPIVolume - file - items: - description: DownwardAPIVolumeFile represents - information to create the file containing - the pod field - properties: - fieldRef: - description: 'Required: Selects a field - of the pod: only annotations, labels, - name and namespace are supported.' - properties: - apiVersion: - description: Version of the schema - the FieldPath is written in terms - of, defaults to "v1". - type: string - fieldPath: - description: Path of the field to - select in the specified API version. - type: string - required: - - fieldPath - type: object - mode: - description: 'Optional: mode bits to use - on this file, must be a value between - 0 and 0777. If not specified, the volume - defaultMode will be used. This might - be in conflict with other options that - affect the file mode, like fsGroup, - and the result can be other mode bits - set.' - format: int32 - type: integer - path: - description: 'Required: Path is the relative - path name of the file to be created. - Must not be absolute or contain the - ''..'' path. Must be utf-8 encoded. - The first item of the relative path - must not start with ''..''' - type: string - resourceFieldRef: - description: 'Selects a resource of the - container: only resources limits and - requests (limits.cpu, limits.memory, - requests.cpu and requests.memory) are - currently supported.' - properties: - containerName: - description: 'Container name: required - for volumes, optional for env vars' - type: string - divisor: - description: Specifies the output - format of the exposed resources, - defaults to "1" - type: string - resource: - description: 'Required: resource to - select' - type: string - required: - - resource - type: object - required: - - path - type: object - type: array - type: object - secret: - description: information about the secret data to - project - properties: - items: - description: If unspecified, each key-value - pair in the Data field of the referenced Secret - will be projected into the volume as a file - whose name is the key and content is the value. - If specified, the listed keys will be projected - into the specified paths, and unlisted keys - will not be present. If a key is specified - which is not present in the Secret, the volume - setup will error unless it is marked optional. - Paths must be relative and may not contain - the '..' path or start with '..'. - items: - description: Maps a string key to a path within - a volume. - properties: - key: - description: The key to project. - type: string - mode: - description: 'Optional: mode bits to use - on this file, must be a value between - 0 and 0777. If not specified, the volume - defaultMode will be used. This might - be in conflict with other options that - affect the file mode, like fsGroup, - and the result can be other mode bits - set.' - format: int32 - type: integer - path: - description: The relative path of the - file to map the key to. May not be an - absolute path. May not contain the path - element '..'. May not start with the - string '..'. - type: string - required: - - key - - path - type: object - type: array - name: - description: 'Name of the referent. More info: - https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, - kind, uid?' - type: string - optional: - description: Specify whether the Secret or its - key must be defined - type: boolean - type: object - serviceAccountToken: - description: information about the serviceAccountToken - data to project - properties: - audience: - description: Audience is the intended audience - of the token. A recipient of a token must - identify itself with an identifier specified - in the audience of the token, and otherwise - should reject the token. The audience defaults - to the identifier of the apiserver. - type: string - expirationSeconds: - description: ExpirationSeconds is the requested - duration of validity of the service account - token. As the token approaches expiration, - the kubelet volume plugin will proactively - rotate the service account token. The kubelet - will start trying to rotate the token if the - token is older than 80 percent of its time - to live or if the token is older than 24 hours.Defaults - to 1 hour and must be at least 10 minutes. - format: int64 - type: integer - path: - description: Path is the path relative to the - mount point of the file to project the token - into. - type: string - required: - - path - type: object - type: object - type: array - required: - - sources - type: object - quobyte: - description: Quobyte represents a Quobyte mount on the host - that shares a pod's lifetime - properties: - group: - description: Group to map volume access to Default is - no group - type: string - readOnly: - description: ReadOnly here will force the Quobyte volume - to be mounted with read-only permissions. Defaults to - false. - type: boolean - registry: - description: Registry represents a single or multiple - Quobyte Registry services specified as a string as host:port - pair (multiple entries are separated with commas) which - acts as the central registry for volumes - type: string - tenant: - description: Tenant owning the given Quobyte volume in - the Backend Used with dynamically provisioned Quobyte - volumes, value is set by the plugin - type: string - user: - description: User to map volume access to Defaults to - serivceaccount user - type: string - volume: - description: Volume is a string that references an already - created Quobyte volume by name. - type: string - required: - - registry - - volume - type: object - rbd: - description: 'RBD represents a Rados Block Device mount on - the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' - properties: - fsType: - description: 'Filesystem type of the volume that you want - to mount. Tip: Ensure that the filesystem type is supported - by the host operating system. Examples: "ext4", "xfs", - "ntfs". Implicitly inferred to be "ext4" if unspecified. - More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd - TODO: how do we prevent errors in the filesystem from - compromising the machine' - type: string - image: - description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - keyring: - description: 'Keyring is the path to key ring for RBDUser. - Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - monitors: - description: 'A collection of Ceph monitors. More info: - https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - items: - type: string - type: array - pool: - description: 'The rados pool name. Default is rbd. More - info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - readOnly: - description: 'ReadOnly here will force the ReadOnly setting - in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: boolean - secretRef: - description: 'SecretRef is name of the authentication - secret for RBDUser. If provided overrides keyring. Default - is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - user: - description: 'The rados user name. Default is admin. More - info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' - type: string - required: - - image - - monitors - type: object - scaleIO: - description: ScaleIO represents a ScaleIO persistent volume - attached and mounted on Kubernetes nodes. - properties: - fsType: - description: Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Ex. "ext4", - "xfs", "ntfs". Default is "xfs". - type: string - gateway: - description: The host address of the ScaleIO API Gateway. - type: string - protectionDomain: - description: The name of the ScaleIO Protection Domain - for the configured storage. - type: string - readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - secretRef: - description: SecretRef references to the secret for ScaleIO - user and other sensitive information. If this is not - provided, Login operation will fail. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - sslEnabled: - description: Flag to enable/disable SSL communication - with Gateway, default false - type: boolean - storageMode: - description: Indicates whether the storage for a volume - should be ThickProvisioned or ThinProvisioned. Default - is ThinProvisioned. - type: string - storagePool: - description: The ScaleIO Storage Pool associated with - the protection domain. - type: string - system: - description: The name of the storage system as configured - in ScaleIO. - type: string - volumeName: - description: The name of a volume already created in the - ScaleIO system that is associated with this volume source. - type: string - required: - - gateway - - secretRef - - system - type: object - secret: - description: 'Secret represents a secret that should populate - this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' - properties: - defaultMode: - description: 'Optional: mode bits to use on created files - by default. Must be a value between 0 and 0777. Defaults - to 0644. Directories within the path are not affected - by this setting. This might be in conflict with other - options that affect the file mode, like fsGroup, and - the result can be other mode bits set.' - format: int32 - type: integer - items: - description: If unspecified, each key-value pair in the - Data field of the referenced Secret will be projected - into the volume as a file whose name is the key and - content is the value. If specified, the listed keys - will be projected into the specified paths, and unlisted - keys will not be present. If a key is specified which - is not present in the Secret, the volume setup will - error unless it is marked optional. Paths must be relative - and may not contain the '..' path or start with '..'. - items: - description: Maps a string key to a path within a volume. - properties: - key: - description: The key to project. - type: string - mode: - description: 'Optional: mode bits to use on this - file, must be a value between 0 and 0777. If not - specified, the volume defaultMode will be used. - This might be in conflict with other options that - affect the file mode, like fsGroup, and the result - can be other mode bits set.' - format: int32 - type: integer - path: - description: The relative path of the file to map - the key to. May not be an absolute path. May not - contain the path element '..'. May not start with - the string '..'. - type: string - required: - - key - - path - type: object - type: array - optional: - description: Specify whether the Secret or its keys must - be defined - type: boolean - secretName: - description: 'Name of the secret in the pod''s namespace - to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' - type: string - type: object - storageos: - description: StorageOS represents a StorageOS volume attached - and mounted on Kubernetes nodes. - properties: - fsType: - description: Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Ex. "ext4", - "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - type: string - readOnly: - description: Defaults to false (read/write). ReadOnly - here will force the ReadOnly setting in VolumeMounts. - type: boolean - secretRef: - description: SecretRef specifies the secret to use for - obtaining the StorageOS API credentials. If not specified, - default values will be attempted. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - volumeName: - description: VolumeName is the human-readable name of - the StorageOS volume. Volume names are only unique - within a namespace. - type: string - volumeNamespace: - description: VolumeNamespace specifies the scope of the - volume within StorageOS. If no namespace is specified - then the Pod's namespace will be used. This allows - the Kubernetes name scoping to be mirrored within StorageOS - for tighter integration. Set VolumeName to any name - to override the default behaviour. Set to "default" - if you are not using namespaces within StorageOS. Namespaces - that do not pre-exist within StorageOS will be created. - type: string - type: object - vsphereVolume: - description: VsphereVolume represents a vSphere volume attached - and mounted on kubelets host machine - properties: - fsType: - description: Filesystem type to mount. Must be a filesystem - type supported by the host operating system. Ex. "ext4", - "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. - type: string - storagePolicyID: - description: Storage Policy Based Management (SPBM) profile - ID associated with the StoragePolicyName. - type: string - storagePolicyName: - description: Storage Policy Based Management (SPBM) profile - name. - type: string - volumePath: - description: Path that identifies vSphere volume vmdk - type: string - required: - - volumePath - type: object - required: - - name - type: object - type: array - required: - - disableCSRFProtection - type: object - notifications: - description: Notifications defines list of a services which are used - to inform about Jenkins status Can be used to integrate chat services - like Slack, Microsoft Teams or Mailgun - items: - description: Notification is a service configuration used to send - notifications about Jenkins status - properties: - level: - description: NotificationLevel defines the level of a Notification - type: string - mailgun: - description: Mailgun is handler for Mailgun email service notification - channel - properties: - apiKeySecretKeySelector: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - secret: - description: The name of the secret in the pod's namespace - to select from. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - required: - - key - - secret - type: object - domain: - type: string - from: - type: string - recipient: - type: string - required: - - apiKeySecretKeySelector - - domain - - from - - recipient - type: object - name: - type: string - slack: - description: Slack is handler for Slack notification channel - properties: - webHookURLSecretKeySelector: - description: The web hook URL to Slack App - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - secret: - description: The name of the secret in the pod's namespace - to select from. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - required: - - key - - secret - type: object - required: - - webHookURLSecretKeySelector - type: object - smtp: - description: SMTP is handler for sending emails via this protocol - properties: - from: - type: string - passwordSecretKeySelector: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - secret: - description: The name of the secret in the pod's namespace - to select from. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - required: - - key - - secret - type: object - port: - type: integer - server: - type: string - tlsInsecureSkipVerify: - type: boolean - to: - type: string - usernameSecretKeySelector: - description: SecretKeySelector selects a key of a Secret. - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - secret: - description: The name of the secret in the pod's namespace - to select from. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - required: - - key - - secret - type: object - required: - - from - - passwordSecretKeySelector - - port - - server - - to - - usernameSecretKeySelector - type: object - teams: - description: MicrosoftTeams is handler for Microsoft MicrosoftTeams - notification channel - properties: - webHookURLSecretKeySelector: - description: The web hook URL to MicrosoftTeams App - properties: - key: - description: The key of the secret to select from. Must - be a valid secret key. - type: string - secret: - description: The name of the secret in the pod's namespace - to select from. - properties: - name: - description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names - TODO: Add other useful fields. apiVersion, kind, - uid?' - type: string - type: object - required: - - key - - secret - type: object - required: - - webHookURLSecretKeySelector - type: object - verbose: - type: boolean - required: - - level - - name - - verbose - type: object - type: array - restore: - description: 'Backup defines configuration of Jenkins backup restore - More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' - properties: - action: - description: Action defines action which performs restore backup - in restore container sidecar - properties: - exec: - description: Exec specifies the action to take. - properties: - command: - description: Command is the command line to execute inside - the container, the working directory for the command is - root ('/') in the container's filesystem. The command - is simply exec'd, it is not run inside a shell, so traditional - shell instructions ('|', etc) won't work. To use a shell, - you need to explicitly call out to that shell. Exit status - of 0 is treated as live/healthy and non-zero is unhealthy. - items: - type: string - type: array - type: object - type: object - containerName: - description: ContainerName is the container name responsible for - restore backup operation - type: string - recoveryOnce: - description: RecoveryOnce if want to restore specific backup set - this field and then Jenkins will be restarted and desired backup - will be restored - format: int64 - type: integer - required: - - action - - containerName - type: object - roles: - description: Roles defines list of extra RBAC roles for the Jenkins - Master pod service account - items: - description: RoleRef contains information that points to the role - being used - properties: - apiGroup: - description: APIGroup is the group for the resource being referenced - type: string - kind: - description: Kind is the type of resource being referenced - type: string - name: - description: Name is the name of resource being referenced - type: string - required: - - apiGroup - - kind - - name - type: object - type: array - seedJobs: - description: 'SeedJobs defines list of Jenkins Seed Job configurations - More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines' - items: - description: 'SeedJob defines configuration for seed job More info: - https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines' - properties: - additionalClasspath: - description: AdditionalClasspath is setting for Job DSL API plugin - to set Additional Classpath - type: string - bitbucketPushTrigger: - description: BitbucketPushTrigger is used for Bitbucket web hooks - type: boolean - buildPeriodically: - description: BuildPeriodically is setting for scheduled trigger - type: string - credentialID: - description: CredentialID is the Kubernetes secret name which - stores repository access credentials - type: string - credentialType: - description: JenkinsCredentialType is the https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ - credential type - type: string - description: - description: Description is the description of the seed job - type: string - failOnMissingPlugin: - description: FailOnMissingPlugin is setting for Job DSL API plugin - that fails job if required plugin is missing - type: boolean - githubPushTrigger: - description: GitHubPushTrigger is used for GitHub web hooks - type: boolean - id: - description: ID is the unique seed job name - type: string - ignoreMissingFiles: - description: IgnoreMissingFiles is setting for Job DSL API plugin - to ignore files that miss - type: boolean - pollSCM: - description: PollSCM is setting for polling changes in SCM - type: string - repositoryBranch: - description: RepositoryBranch is the repository branch where are - seed job definitions - type: string - repositoryUrl: - description: RepositoryURL is the repository access URL. Can be - SSH or HTTPS. - type: string - targets: - description: Targets is the repository path where are seed job - definitions - type: string - unstableOnDeprecation: - description: UnstableOnDeprecation is setting for Job DSL API - plugin that sets build status as unstable if build using deprecated - features - type: boolean - type: object - type: array - service: - description: 'Service is Kubernetes service of Jenkins master HTTP pod - Defaults to : port: 8080 type: ClusterIP' - 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 - labels: - additionalProperties: - type: string - description: 'Route service traffic to pods with label keys and - values matching this selector. If empty or not present, the service - is assumed to have an external process managing its endpoints, - which Kubernetes will not modify. Only applies to types ClusterIP, - NodePort, and LoadBalancer. Ignored if type is ExternalName. More - info: https://kubernetes.io/docs/concepts/services-networking/service/' - type: object - loadBalancerIP: - description: 'Only applies to Service Type: LoadBalancer LoadBalancer - will get created with the IP specified in this field. This feature - depends on whether the underlying cloud-provider supports specifying - the loadBalancerIP when a load balancer is created. This field - will be ignored if the cloud-provider does not support the feature.' - type: string - loadBalancerSourceRanges: - description: 'If specified and supported by the platform, this will - restrict traffic through the cloud-provider load-balancer will - be restricted to the specified client IPs. This field will be - ignored if the cloud-provider does not support the feature." More - info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' - items: - type: string - type: array - nodePort: - description: 'The port on each node on which this service is exposed - when type=NodePort or LoadBalancer. Usually assigned by the system. - If specified, it will be allocated to the service if unused or - else creation of the service will fail. Default is to auto-allocate - a port if the ServiceType of this Service requires one. More info: - https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' - format: int32 - type: integer - port: - description: 'The port that are exposed by this service. More info: - https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' - format: int32 - type: integer - type: - description: 'Type determines how the Service is exposed. Defaults - to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, - and LoadBalancer. "ExternalName" maps to the specified externalName. - "ClusterIP" allocates a cluster-internal IP address for load-balancing - to endpoints. Endpoints are determined by the selector or if that - is not specified, by manual construction of an Endpoints object. - If clusterIP is "None", no virtual IP is allocated and the endpoints - are published as a set of endpoints rather than a stable IP. "NodePort" - builds on ClusterIP and allocates a port on every node which routes - to the clusterIP. "LoadBalancer" builds on NodePort and creates - an external load-balancer (if supported in the current cloud) - which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' - type: string - type: object - serviceAccount: - description: ServiceAccount defines Jenkins master service account attributes - 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 - type: object - slaveService: - description: 'Service is Kubernetes service of Jenkins slave pods Defaults - to : port: 50000 type: ClusterIP' - 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 - labels: - additionalProperties: - type: string - description: 'Route service traffic to pods with label keys and - values matching this selector. If empty or not present, the service - is assumed to have an external process managing its endpoints, - which Kubernetes will not modify. Only applies to types ClusterIP, - NodePort, and LoadBalancer. Ignored if type is ExternalName. More - info: https://kubernetes.io/docs/concepts/services-networking/service/' - type: object - loadBalancerIP: - description: 'Only applies to Service Type: LoadBalancer LoadBalancer - will get created with the IP specified in this field. This feature - depends on whether the underlying cloud-provider supports specifying - the loadBalancerIP when a load balancer is created. This field - will be ignored if the cloud-provider does not support the feature.' - type: string - loadBalancerSourceRanges: - description: 'If specified and supported by the platform, this will - restrict traffic through the cloud-provider load-balancer will - be restricted to the specified client IPs. This field will be - ignored if the cloud-provider does not support the feature." More - info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' - items: - type: string - type: array - nodePort: - description: 'The port on each node on which this service is exposed - when type=NodePort or LoadBalancer. Usually assigned by the system. - If specified, it will be allocated to the service if unused or - else creation of the service will fail. Default is to auto-allocate - a port if the ServiceType of this Service requires one. More info: - https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' - format: int32 - type: integer - port: - description: 'The port that are exposed by this service. More info: - https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' - format: int32 - type: integer - type: - description: 'Type determines how the Service is exposed. Defaults - to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, - and LoadBalancer. "ExternalName" maps to the specified externalName. - "ClusterIP" allocates a cluster-internal IP address for load-balancing - to endpoints. Endpoints are determined by the selector or if that - is not specified, by manual construction of an Endpoints object. - If clusterIP is "None", no virtual IP is allocated and the endpoints - are published as a set of endpoints rather than a stable IP. "NodePort" - builds on ClusterIP and allocates a port on every node which routes - to the clusterIP. "LoadBalancer" builds on NodePort and creates - an external load-balancer (if supported in the current cloud) - which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' - type: string - type: object - required: - - jenkinsAPISettings - - master - type: object - status: - description: Status defines the observed state of Jenkins - properties: - appliedGroovyScripts: - description: AppliedGroovyScripts is a list with all applied groovy - scripts in Jenkins by the operator - items: - description: AppliedGroovyScript is the applied groovy script in Jenkins - by the operator - properties: - configurationType: - description: ConfigurationType is the name of the configuration - type(base-groovy, user-groovy, user-casc) - type: string - hash: - description: Hash is the hash of the groovy script and secrets - which it uses - type: string - name: - description: Name is the name of the groovy script - type: string - source: - description: Source is the name of source where is located groovy - script - type: string - required: - - configurationType - - hash - - name - - source - type: object - type: array - backupDoneBeforePodDeletion: - description: BackupDoneBeforePodDeletion tells if backup before pod - deletion has been made - type: boolean - baseConfigurationCompletedTime: - description: BaseConfigurationCompletedTime is a time when Jenkins base - configuration phase has been completed - format: date-time - type: string - createdSeedJobs: - description: CreatedSeedJobs contains list of seed job id already created - in Jenkins - items: - type: string - type: array - lastBackup: - description: LastBackup is the latest backup number - format: int64 - type: integer - operatorVersion: - description: OperatorVersion is the operator version which manages this - CR - type: string - pendingBackup: - description: PendingBackup is the pending backup number - format: int64 - type: integer - provisionStartTime: - description: ProvisionStartTime is a time when Jenkins master pod has - been created - format: date-time - type: string - restoredBackup: - description: RestoredBackup is the restored backup number after Jenkins - master pod restart - format: int64 - type: integer - userAndPasswordHash: - description: UserAndPasswordHash is a SHA256 hash made from user and - password - type: string - userConfigurationCompletedTime: - description: UserConfigurationCompletedTime is a time when Jenkins user - configuration phase has been completed - format: date-time - type: string - type: object - type: object - version: v1alpha2 - versions: - - name: v1alpha2 - served: true - storage: true diff --git a/deploy/crds/jenkins.io_jenkinsimages_crd.yaml b/deploy/crds/jenkins.io_jenkinsimages_crd.yaml deleted file mode 100644 index acf4f081..00000000 --- a/deploy/crds/jenkins.io_jenkinsimages_crd.yaml +++ /dev/null @@ -1,85 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: jenkinsimages.jenkins.io -spec: - group: jenkins.io - names: - kind: JenkinsImage - listKind: JenkinsImageList - plural: jenkinsimages - singular: jenkinsimage - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: JenkinsImageSpec defines the desired state of JenkinsImage - properties: - image: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - plugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - required: - - image - - plugins - type: object - status: - description: JenkinsImageStatus defines the observed state of JenkinsImage - properties: - image: - type: string - installedPlugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - md5sum: - type: string - type: object - type: object - version: v1alpha2 - versions: - - name: v1alpha2 - served: true - storage: true diff --git a/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml b/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml deleted file mode 100644 index 582c8977..00000000 --- a/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: jenkins.io/v1alpha2 -kind: Jenkins -metadata: - name: example -spec: - master: - containers: - - name: jenkins-master - image: jenkins/jenkins:2.263.2-lts-alpine - seedJobs: - - id: jenkins-operator - targets: "cicd/jobs/*.jenkins" - description: "Jenkins Operator repository" - repositoryBranch: master - repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git diff --git a/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml b/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml deleted file mode 100644 index 8206d650..00000000 --- a/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml +++ /dev/null @@ -1,106 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: jenkins.jenkins.io -spec: - group: jenkins.io - names: - kind: Jenkins - listKind: JenkinsList - plural: jenkins - singular: jenkins - scope: Namespaced - versions: - - name : v1alpha2 - served: true - storage: true - - name : v1alpha1 - served: true - storage: false ---- -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: jenkinsimages.jenkins.io -spec: - group: jenkins.io - names: - kind: JenkinsImage - listKind: JenkinsImageList - plural: jenkinsimages - singular: jenkinsimage - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: JenkinsImageSpec defines the desired state of JenkinsImage - properties: - image: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - plugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - required: - - image - - plugins - type: object - status: - description: JenkinsImageStatus defines the observed state of JenkinsImage - properties: - image: - type: string - installedPlugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - md5sum: - type: string - type: object - type: object - version: v1alpha2 - versions: - - name: v1alpha2 - served: true - storage: true - diff --git a/deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml b/deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml deleted file mode 100644 index cda0a0fc..00000000 --- a/deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml +++ /dev/null @@ -1,24 +0,0 @@ -apiVersion: jenkins.io/v1alpha2 -kind: JenkinsImage -metadata: - name: simple-jenkinsimage -spec: - image: - name: jenkins/jenkins - tag: 2.263.1-lts-alpine - plugins: - - name: kubernetes - version: "1.28.6" - - name: workflow-job - version: "2.40" - - name: workflow-aggregator - version: "2.6" - - name: git - version: "4.5.0" - - name: job-dsl - version: "1.77" - - name: configuration-as-code - version: "1.46" - - name: kubernetes-credentials-provider - version: "0.15" - diff --git a/deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml b/deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml deleted file mode 100644 index acf4f081..00000000 --- a/deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml +++ /dev/null @@ -1,85 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: jenkinsimages.jenkins.io -spec: - group: jenkins.io - names: - kind: JenkinsImage - listKind: JenkinsImageList - plural: jenkinsimages - singular: jenkinsimage - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' - type: string - metadata: - type: object - spec: - description: JenkinsImageSpec defines the desired state of JenkinsImage - properties: - image: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - plugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - required: - - image - - plugins - type: object - status: - description: JenkinsImageStatus defines the observed state of JenkinsImage - properties: - image: - type: string - installedPlugins: - items: - description: Defines Jenkins Plugin structure - properties: - name: - type: string - version: - type: string - required: - - name - type: object - type: array - md5sum: - type: string - type: object - type: object - version: v1alpha2 - versions: - - name: v1alpha2 - served: true - storage: true diff --git a/deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml b/deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml deleted file mode 100644 index 9942813a..00000000 --- a/deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml +++ /dev/null @@ -1,73 +0,0 @@ -apiVersion: jenkins.io/v1alpha2 -kind: Jenkins -metadata: - annotations: - jenkins.io/openshift-mode: 'true' - name: jenkins -spec: - master: - containers: - - name: jenkins-master - command: - - /usr/bin/go-init - - '-main' - - /usr/libexec/s2i/run - env: - - name: OPENSHIFT_ENABLE_OAUTH - value: 'true' - - name: OPENSHIFT_ENABLE_REDIRECT_PROMPT - value: 'true' - - name: DISABLE_ADMINISTRATIVE_MONITORS - value: 'false' - - name: KUBERNETES_MASTER - value: 'https://kubernetes.default:443' - - name: KUBERNETES_TRUST_CERTIFICATES - value: 'true' - - name: JENKINS_SERVICE_NAME - value: jenkins-operator-http-jenkins - - name: JNLP_SERVICE_NAME - value: jenkins-operator-slave-jenkins - - name: JENKINS_UC_INSECURE - value: 'false' - - name: JENKINS_HOME - value: /var/lib/jenkins - - name: JAVA_OPTS - value: >- - -XX:+UnlockExperimentalVMOptions -XX:+UnlockExperimentalVMOptions - -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 - -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true - image: 'quay.io/openshift/origin-jenkins:latest' - imagePullPolicy: Always - livenessProbe: - httpGet: - path: /login - port: 8080 - scheme: HTTP - initialDelaySeconds: 420 - periodSeconds: 360 - timeoutSeconds: 240 - readinessProbe: - httpGet: - path: /login - port: 8080 - scheme: HTTP - initialDelaySeconds: 3 - periodSeconds: 0 - timeoutSeconds: 240 - resources: - limits: - cpu: 600m - memory: 4Gi - requests: - cpu: 500m - memory: 3Gi - service: - port: 8080 - type: ClusterIP - slaveService: - port: 50000 - type: ClusterIP - serviceAccount: - annotations: - serviceaccounts.openshift.io/oauth-redirectreference.jenkins: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"jenkins-operator"}}' - diff --git a/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml b/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml deleted file mode 100644 index de2d8c6d..00000000 --- a/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml +++ /dev/null @@ -1,244 +0,0 @@ -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - annotations: - alm-examples: >- - [{"apiVersion":"jenkins.io/v1alpha2","kind":"Jenkins","metadata":{"name":"example"},"spec":{"master":{"containers":[{"name":"jenkins-master","image":"jenkins/jenkins:lts","imagePullPolicy":"Always","livenessProbe":{"failureThreshold":12,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":80,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":5},"readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":30,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{"cpu":"1500m","memory":"3Gi"},"requests":{"cpu":"1","memory":"500Mi"}}}]},"seedJobs":[{"id":"jenkins-operator","targets":"cicd/jobs/*.jenkins","description":"Jenkins - Operator - repository","repositoryBranch":"master","repositoryUrl":"https://github.com/jenkinsci/kubernetes-operator.git"}]}}] - categories: Integration & Delivery - certified: 'false' - description: >- - Kubernetes native operator which fully manages Jenkins on Kubernetes. - containerImage: 'virtuslab/jenkins-operator:v0.2.2' - support: 'VirtusLab' - capabilities: Basic Install - repository: 'https://github.com/jenkinsci/kubernetes-operator' - name: jenkins-operator.v0.2.2 - namespace: placeholder -spec: - apiservicedefinitions: {} - displayName: Jenkins Operator - description: >+ - ##What's the Jenkins Operator? - - Jenkins operator is a Kubernetes native operator which fully manages Jenkins on Kubernetes. It was built with immutability and declarative configuration as code in mind. - - - Out of the box it provides: - - - integration with Kubernetes - - pipelines as code - - extensibility via groovy scripts or configuration as code plugin - - security and hardening - - Problem statement and goals - - The main reason why we decided to implement the Jenkins Operator is the fact that we faced a lot of problems with standard Jenkins deployment. We want to make Jenkins more robust, suitable for dynamic and multi-tenant environments. - - - Some of the problems we want to solve: - - installing plugins with incompatible versions or security vulnerabilities - - better configuration as code - - lack of end to end tests - - handle graceful shutdown properly - - security and hardening out of the box - - orphaned jobs with no jnlp connection - - make errors more visible for end users - - backup and restore for jobs history - - - version: 0.2.2 - minKubeVersion: 1.11.0 - maturity: alpha - keywords: - - jenkins - - operator - - CI/CD - maintainers: - - name: Tomasz SÄ™k - email: tomasz.sek.88@gmail.com - - name: Jakub Al-Khalili - email: jal-khalili@virtuslab.com - provider: - name: VirtusLab - labels: {} - selector: - matchLabels: {} - links: - - name: GitHub - url: 'https://github.com/jenkinsci/kubernetes-operator' - - name: Website - url: 'https://jenkinsci.github.io/kubernetes-operator/' - icon: - - base64data: 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 - mediatype: image/png - customresourcedefinitions: - owned: - - name: jenkins.jenkins.io - displayName: Jenkins - kind: Jenkins - version: v1alpha2 - description: Jenkins - resources: - - version: v1 - kind: Deployment - - version: v1 - kind: Service - - version: v1 - kind: ReplicaSet - - version: v1 - kind: Pod - - version: v1 - kind: Secret - - version: v1 - kind: ConfigMap - specDescriptors: [] - statusDescriptors: [] - required: [] - install: - spec: - deployments: - - name: jenkins-operator - spec: - replicas: 1 - selector: - matchLabels: - name: jenkins-operator - strategy: {} - template: - metadata: - labels: - name: jenkins-operator - spec: - containers: - - command: - - jenkins-operator - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.annotations['olm.targetNamespaces'] - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: OPERATOR_NAME - value: jenkins-operator - image: virtuslab/jenkins-operator:v0.2.2 - imagePullPolicy: IfNotPresent - name: jenkins-operator - resources: {} - serviceAccountName: jenkins-operator - permissions: - - rules: - - apiGroups: - - "" - resources: - - services - - configmaps - - secrets - verbs: - - get - - create - - update - - list - - watch - - apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' - - apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - - rolebindings - verbs: - - create - - update - - apiGroups: - - "" - resources: - - pods/portforward - verbs: - - create - - apiGroups: - - "" - resources: - - pods/log - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods - - pods/exec - verbs: - - '*' - - apiGroups: - - "" - resources: - - events - verbs: - - watch - - list - - create - - patch - - apiGroups: - - apps - resourceNames: - - jenkins-operator - resources: - - deployments/finalizers - verbs: - - update - - apiGroups: - - jenkins.io - resources: - - '*' - verbs: - - '*' - - apiGroups: - - "" - resources: - - persistentvolumeclaims - verbs: - - get - - list - - watch - serviceAccountName: jenkins-operator - strategy: deployment - installModes: - - supported: true - type: OwnNamespace - - supported: true - type: SingleNamespace - - supported: false - type: MultiNamespace - - supported: false - type: AllNamespaces diff --git a/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml b/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml deleted file mode 100644 index b74dcc75..00000000 --- a/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: jenkins.jenkins.io -spec: - group: jenkins.io - names: - kind: Jenkins - listKind: JenkinsList - plural: jenkins - singular: jenkins - scope: Namespaced - version: v1alpha2 - versions: - - name : v1alpha2 - served: true - storage: true - - name : v1alpha1 - served: true - storage: false diff --git a/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml b/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml deleted file mode 100644 index 4853d9c4..00000000 --- a/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml +++ /dev/null @@ -1,226 +0,0 @@ -apiVersion: operators.coreos.com/v1alpha1 -kind: ClusterServiceVersion -metadata: - annotations: - alm-examples: '[{"apiVersion":"jenkins.io/v1alpha2","kind":"Jenkins","metadata":{"name":"example"},"spec":{"master":{"containers":[{"image":"jenkins/jenkins:lts","imagePullPolicy":"Always","livenessProbe":{"failureThreshold":12,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":80,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":5},"name":"jenkins-master","readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":30,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{"cpu":"1500m","memory":"3Gi"},"requests":{"cpu":"1","memory":"500Mi"}}}]},"seedJobs":[{"description":"Jenkins - Operator repository","id":"jenkins-operator","repositoryBranch":"master","repositoryUrl":"https://github.com/jenkinsci/kubernetes-operator.git","targets":"cicd/jobs/*.jenkins"}]}},{"apiVersion":"jenkins.io/v1alpha2","kind":"Jenkins","metadata":{"name":"jenkins"},"spec":{"master":{"containers":[{"image":"quay.io/openshift/origin-jenkins:latest","name":"jenkins-master","resources":{"limits":{"cpu":"1500m","memory":"3Gi"},"requests":{"cpu":"1","memory":"500Mi"}}}]}}}]' - capabilities: Basic Install - categories: Integration & Delivery - certified: "false" - containerImage: virtuslab/jenkins-operator:v0.3.0 - description: Kubernetes native operator which fully manages Jenkins on Kubernetes. - repository: https://github.com/jenkinsci/kubernetes-operator - support: VirtusLab - name: jenkins-operator.v0.3.0 - namespace: placeholder -spec: - apiservicedefinitions: {} - customresourcedefinitions: - owned: - - description: Jenkins - displayName: Jenkins - kind: Jenkins - name: jenkins.jenkins.io - resources: - - kind: Deployment - name: "" - version: v1 - - kind: Service - name: "" - version: v1 - - kind: ReplicaSet - name: "" - version: v1 - - kind: Pod - name: "" - version: v1 - - kind: Secret - name: "" - version: v1 - - kind: ConfigMap - name: "" - version: v1 - version: v1alpha2 - description: |+ - ##What's the Jenkins Operator? - Jenkins operator is a Kubernetes native operator which fully manages Jenkins on Kubernetes. It was built with immutability and declarative configuration as code in mind. - - Out of the box it provides: - - integration with Kubernetes - pipelines as code - extensibility via groovy scripts or configuration as code plugin - security and hardening - Problem statement and goals - The main reason why we decided to implement the Jenkins Operator is the fact that we faced a lot of problems with standard Jenkins deployment. We want to make Jenkins more robust, suitable for dynamic and multi-tenant environments. - - Some of the problems we want to solve: - installing plugins with incompatible versions or security vulnerabilities - better configuration as code - lack of end to end tests - handle graceful shutdown properly - security and hardening out of the box - orphaned jobs with no jnlp connection - make errors more visible for end users - backup and restore for jobs history - - - displayName: Jenkins Operator - icon: - - base64data: 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 - mediatype: image/png - install: - spec: - deployments: - - name: jenkins-operator - spec: - replicas: 1 - selector: - matchLabels: - name: jenkins-operator - strategy: {} - template: - metadata: - labels: - name: jenkins-operator - spec: - containers: - - command: - - jenkins-operator - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.annotations['olm.targetNamespaces'] - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: OPERATOR_NAME - value: jenkins-operator - image: virtuslab/jenkins-operator:v0.3.0 - imagePullPolicy: IfNotPresent - name: jenkins-operator - resources: {} - serviceAccountName: jenkins-operator - permissions: - - rules: - - apiGroups: - - "" - resources: - - services - - configmaps - - secrets - verbs: - - get - - create - - update - - list - - watch - - apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' - - apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - - rolebindings - verbs: - - create - - update - - apiGroups: - - "" - resources: - - pods/portforward - verbs: - - create - - apiGroups: - - "" - resources: - - pods/log - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods - - pods/exec - verbs: - - '*' - - apiGroups: - - "" - resources: - - events - verbs: - - watch - - list - - create - - patch - - apiGroups: - - apps - resourceNames: - - jenkins-operator - resources: - - deployments/finalizers - verbs: - - update - - apiGroups: - - jenkins.io - resources: - - '*' - verbs: - - '*' - - apiGroups: - - "" - resources: - - persistentvolumeclaims - verbs: - - get - - list - - watch - serviceAccountName: jenkins-operator - strategy: deployment - installModes: - - supported: true - type: OwnNamespace - - supported: true - type: SingleNamespace - - supported: false - type: MultiNamespace - - supported: false - type: AllNamespaces - keywords: - - jenkins - - operator - - CI/CD - links: - - name: GitHub - url: https://github.com/jenkinsci/kubernetes-operator - - name: Website - url: https://jenkinsci.github.io/kubernetes-operator/ - maintainers: - - email: tomasz.sek.88@gmail.com - name: Tomasz Sęk - - email: jal-khalili@virtuslab.com - name: Jakub Al-Khalili - maturity: alpha - minKubeVersion: 1.11.0 - provider: - name: VirtusLab - replaces: jenkins-operator.v0.2.2 - selector: {} - version: 0.3.0 diff --git a/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml b/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml deleted file mode 100644 index b74dcc75..00000000 --- a/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml +++ /dev/null @@ -1,20 +0,0 @@ -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: jenkins.jenkins.io -spec: - group: jenkins.io - names: - kind: Jenkins - listKind: JenkinsList - plural: jenkins - singular: jenkins - scope: Namespaced - version: v1alpha2 - versions: - - name : v1alpha2 - served: true - storage: true - - name : v1alpha1 - served: true - storage: false diff --git a/deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml b/deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml deleted file mode 100644 index 69971831..00000000 --- a/deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml +++ /dev/null @@ -1,5 +0,0 @@ -channels: -- currentCSV: jenkins-operator.v0.3.0 - name: alpha -defaultChannel: alpha -packageName: jenkins-operator diff --git a/deploy/operator.yaml b/deploy/operator.yaml deleted file mode 100644 index 95ba9a28..00000000 --- a/deploy/operator.yaml +++ /dev/null @@ -1,34 +0,0 @@ ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: jenkins-operator -spec: - replicas: 1 - selector: - matchLabels: - name: jenkins-operator - template: - metadata: - labels: - name: jenkins-operator - spec: - serviceAccountName: jenkins-operator - containers: - - name: jenkins-operator - image: virtuslab/jenkins-operator:v0.5.0 - command: - - jenkins-operator - args: [] - imagePullPolicy: IfNotPresent - env: - - name: WATCH_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: OPERATOR_NAME - value: "jenkins-operator" diff --git a/deploy/role.yaml b/deploy/role.yaml deleted file mode 100644 index a2653abc..00000000 --- a/deploy/role.yaml +++ /dev/null @@ -1,117 +0,0 @@ ---- -kind: Role -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: jenkins-operator -rules: - - apiGroups: - - "" - resources: - - services - - configmaps - - secrets - - serviceaccounts - verbs: - - get - - create - - update - - list - - watch - - apiGroups: - - apps - resources: - - deployments - - daemonsets - - replicasets - - statefulsets - verbs: - - '*' - - apiGroups: - - rbac.authorization.k8s.io - resources: - - roles - - rolebindings - verbs: - - create - - update - - list - - watch - - apiGroups: - - "" - resources: - - pods/portforward - verbs: - - create - - apiGroups: - - "" - resources: - - pods/log - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - pods - - pods/exec - verbs: - - "*" - - apiGroups: - - "" - resources: - - events - verbs: - - watch - - list - - create - - patch - - apiGroups: - - apps - resourceNames: - - jenkins-operator - resources: - - deployments/finalizers - verbs: - - update - - apiGroups: - - jenkins.io - resources: - - '*' - verbs: - - '*' - - apiGroups: - - "" - resources: - - persistentvolumeclaims - verbs: - - get - - list - - watch - - apiGroups: - - "route.openshift.io" - resources: - - routes - verbs: - - get - - list - - watch - - create - - update - - apiGroups: - - "image.openshift.io" - resources: - - imagestreams - verbs: - - get - - list - - watch - - apiGroups: - - "build.openshift.io" - resources: - - builds - - buildconfigs - verbs: - - get - - list - - watch diff --git a/deploy/role_binding.yaml b/deploy/role_binding.yaml deleted file mode 100644 index 8224b7c1..00000000 --- a/deploy/role_binding.yaml +++ /dev/null @@ -1,12 +0,0 @@ ---- -kind: RoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: jenkins-operator -subjects: -- kind: ServiceAccount - name: jenkins-operator -roleRef: - kind: Role - name: jenkins-operator - apiGroup: rbac.authorization.k8s.io diff --git a/deploy/service_account.yaml b/deploy/service_account.yaml deleted file mode 100644 index 21b293ce..00000000 --- a/deploy/service_account.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: jenkins-operator diff --git a/test/e2e/jenkins_configuration_test.go b/test/e2e/jenkins_configuration_test.go index 7e454f36..dbaa9aeb 100644 --- a/test/e2e/jenkins_configuration_test.go +++ b/test/e2e/jenkins_configuration_test.go @@ -176,7 +176,7 @@ var _ = Describe("Jenkins controller plugins test", func() { BeforeEach(func() { namespace = createNamespace() - jenkins = createJenkinsCRSafe(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) + jenkins = createJenkinsCR(jenkinsCRName, namespace.Name, &[]v1alpha2.SeedJob{mySeedJob.SeedJob}, groovyScripts, casc, priorityClassName) }) AfterEach(func() { diff --git a/test/e2e/jenkins_test.go b/test/e2e/jenkins_test.go index 8a316464..725925b7 100644 --- a/test/e2e/jenkins_test.go +++ b/test/e2e/jenkins_test.go @@ -80,9 +80,9 @@ func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovy Scheme: corev1.URISchemeHTTP, }, }, - InitialDelaySeconds: int32(80), + InitialDelaySeconds: int32(100), TimeoutSeconds: int32(4), - FailureThreshold: int32(10), + FailureThreshold: int32(12), SuccessThreshold: int32(1), PeriodSeconds: int32(1), }, @@ -116,7 +116,7 @@ func createJenkinsCR(name, namespace string, seedJob *[]v1alpha2.SeedJob, groovy {Name: "audit-trail", Version: "3.7"}, {Name: "simple-theme-plugin", Version: "0.6"}, {Name: "github", Version: "1.32.0"}, - {Name: "devoptics", Version: "1.1905", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1905/devoptics.hpi"}, + {Name: "devoptics", Version: "1.1934", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1934/devoptics.hpi"}, }, PriorityClassName: priorityClassName, NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, @@ -186,11 +186,11 @@ func createJenkinsCRSafeRestart(name, namespace string, seedJob *[]v1alpha2.Seed Scheme: corev1.URISchemeHTTP, }, }, - InitialDelaySeconds: int32(80), + InitialDelaySeconds: int32(100), TimeoutSeconds: int32(4), - FailureThreshold: int32(10), + FailureThreshold: int32(12), SuccessThreshold: int32(1), - PeriodSeconds: int32(1), + PeriodSeconds: int32(10), }, LivenessProbe: &corev1.Probe{ Handler: corev1.Handler{ @@ -222,7 +222,7 @@ func createJenkinsCRSafeRestart(name, namespace string, seedJob *[]v1alpha2.Seed {Name: "audit-trail", Version: "3.7"}, {Name: "simple-theme-plugin", Version: "0.6"}, {Name: "github", Version: "1.32.0"}, - {Name: "devoptics", Version: "1.1905", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1905/devoptics.hpi"}, + {Name: "devoptics", Version: "1.1934", DownloadURL: "https://jenkins-updates.cloudbees.com/download/plugins/devoptics/1.1934/devoptics.hpi"}, }, PriorityClassName: priorityClassName, NodeSelector: map[string]string{"kubernetes.io/os": "linux"}, diff --git a/variables.mk b/variables.mk index 2d4b2e28..6ed1aef0 100644 --- a/variables.mk +++ b/variables.mk @@ -69,6 +69,7 @@ OPERATOR_ARGS ?= --jenkins-api-hostname=$(JENKINS_API_HOSTNAME) --jenkins-api-po .DEFAULT_GOAL := help +PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) ##################### FROM OPERATOR SDK ######################## # Default bundle image tag From ce081bfd28e43e9b31c60c67ce87692e0c830072 Mon Sep 17 00:00:00 2001 From: Sylwia Brant Date: Wed, 10 Feb 2021 16:14:50 +0100 Subject: [PATCH 14/15] Add preview docs and Github Actions e2e test workflow --- .github/workflows/auto-tests.yaml | 16 +- Makefile | 15 +- config/all_in_one_v1alpha2.yaml | 227 ++ config/certmanager/certificate.yaml | 4 +- config/crd/patches/webhook_in_jenkins.yaml | 2 +- config/default/kustomization.yaml | 2 +- config/default/manager_auth_proxy_patch.yaml | 2 +- config/default/manager_config_patch.yaml | 2 +- config/manager/manager.yaml | 15 +- config/prometheus/monitor.yaml | 2 +- config/rbac/auth_proxy_role_binding.yaml | 2 +- config/rbac/auth_proxy_service.yaml | 2 +- config/rbac/leader_election_role.yaml | 1 + config/rbac/leader_election_role_binding.yaml | 3 +- config/rbac/role.yaml | 118 +- config/rbac/role_binding.yaml | 3 +- .../samples/jenkins.io_v1alpha2_jenkins.yaml | 2 +- deploy/all-in-one-v1alpha2.yaml | 168 + deploy/crds/jenkins.io_jenkins_crd.yaml | 3027 +++++++++++++++++ deploy/crds/jenkins.io_jenkinsimages_crd.yaml | 85 + deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml | 15 + deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml | 106 + .../jenkins_v1alpha2_jenkinsimage_cr.yaml | 24 + .../jenkins_v1alpha2_jenkinsimage_crd.yaml | 85 + ...openshift_jenkins_v1alpha2_jenkins_cr.yaml | 73 + ...operator.v0.2.2.clusterserviceversion.yaml | 244 ++ .../0.2.2/jenkins_v1alpha2_jenkins_crd.yaml | 20 + ...operator.v0.3.0.clusterserviceversion.yaml | 226 ++ .../0.3.0/jenkins_v1alpha2_jenkins_crd.yaml | 20 + .../jenkins-operator.package.yaml | 5 + deploy/operator.yaml | 34 + deploy/role.yaml | 117 + deploy/role_binding.yaml | 12 + deploy/service_account.yaml | 5 + variables.mk | 3 +- .../en/docs/Developer Guide/Preview/_index.md | 301 ++ .../content/en/docs/Developer Guide/_index.md | 2 +- .../en/docs/Getting Started/Preview/_index.md | 18 + .../en/docs/Getting Started/Preview/aks.md | 24 + .../Getting Started/Preview/configuration.md | 315 ++ .../Preview/configure-backup-and-restore.md | 90 + .../Preview/custom-backup-and-restore.md | 184 + .../Getting Started/Preview/customization.md | 202 ++ .../Getting Started/Preview/deploy-jenkins.md | 90 + .../Getting Started/Preview/diagnostics.md | 42 + .../Getting Started/Preview/notifications.md | 114 + .../docs/Getting Started/Preview/openshift.md | 104 + .../en/docs/Getting Started/Preview/schema.md | 2642 ++++++++++++++ .../Getting Started/latest/configuration.md | 12 +- .../How it works/jenkins-docker-images.md | 4 +- .../en/docs/Installation/Preview/_index.md | 881 +++++ 51 files changed, 9636 insertions(+), 76 deletions(-) create mode 100644 config/all_in_one_v1alpha2.yaml create mode 100644 deploy/all-in-one-v1alpha2.yaml create mode 100644 deploy/crds/jenkins.io_jenkins_crd.yaml create mode 100644 deploy/crds/jenkins.io_jenkinsimages_crd.yaml create mode 100644 deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml create mode 100644 deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml create mode 100644 deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml create mode 100644 deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml create mode 100644 deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml create mode 100644 deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml create mode 100644 deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml create mode 100644 deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml create mode 100644 deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml create mode 100644 deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml create mode 100644 deploy/operator.yaml create mode 100644 deploy/role.yaml create mode 100644 deploy/role_binding.yaml create mode 100644 deploy/service_account.yaml create mode 100644 website/content/en/docs/Developer Guide/Preview/_index.md create mode 100644 website/content/en/docs/Getting Started/Preview/_index.md create mode 100644 website/content/en/docs/Getting Started/Preview/aks.md create mode 100644 website/content/en/docs/Getting Started/Preview/configuration.md create mode 100644 website/content/en/docs/Getting Started/Preview/configure-backup-and-restore.md create mode 100644 website/content/en/docs/Getting Started/Preview/custom-backup-and-restore.md create mode 100644 website/content/en/docs/Getting Started/Preview/customization.md create mode 100644 website/content/en/docs/Getting Started/Preview/deploy-jenkins.md create mode 100644 website/content/en/docs/Getting Started/Preview/diagnostics.md create mode 100644 website/content/en/docs/Getting Started/Preview/notifications.md create mode 100644 website/content/en/docs/Getting Started/Preview/openshift.md create mode 100644 website/content/en/docs/Getting Started/Preview/schema.md create mode 100644 website/content/en/docs/Installation/Preview/_index.md diff --git a/.github/workflows/auto-tests.yaml b/.github/workflows/auto-tests.yaml index 359fcb2d..6231e0d4 100644 --- a/.github/workflows/auto-tests.yaml +++ b/.github/workflows/auto-tests.yaml @@ -23,9 +23,6 @@ jobs: echo "MINIKUBE_WANTUPDATENOTIFICATION=false" >> $GITHUB_ENV echo "MINIKUBE_WANTREPORTERRORPROMPT=false" >> $GITHUB_ENV echo "GO_VERSION=v$(sed -n 's/GO_VERSION=//p' config.base.env)" >> $GITHUB_ENV - echo "MINIKUBE_VERSION=v$(sed -n 's/MINIKUBE_VERSION=//p' config.minikube.env)" >> $GITHUB_ENV - echo "OPERATOR_SDK_VERSION=v$(sed -n 's/OPERATOR_SDK_VERSION=//p' config.base.env)" >> $GITHUB_ENV - echo "MINIKUBE_KUBERNETES_VERSION=$(sed -n 's/MINIKUBE_KUBERNETES_VERSION=//p' config.minikube.env)" >> $GITHUB_ENV echo "HELM_VERSION=v$(sed -n 's/HELM_VERSION=//p' config.base.env)" >> $GITHUB_ENV echo "GOPATH=/home/runner/go" >> $GITHUB_ENV @@ -44,17 +41,12 @@ jobs: run: | sudo apt-get update sudo apt-get install socat - curl -Lo kubectl https://storage.googleapis.com/kubernetes-release/release/$MINIKUBE_KUBERNETES_VERSION/bin/linux/amd64/kubectl && chmod +x kubectl && sudo mv kubectl /usr/local/bin/ - curl -Lo minikube https://storage.googleapis.com/minikube/releases/$MINIKUBE_VERSION/minikube-linux-amd64 && chmod +x minikube && sudo mv minikube /usr/local/bin/ - curl -Lo operator-sdk https://github.com/operator-framework/operator-sdk/releases/download/$OPERATOR_SDK_VERSION/operator-sdk-$OPERATOR_SDK_VERSION-x86_64-linux-gnu && chmod +x operator-sdk && sudo mv operator-sdk /usr/local/bin/ - curl -Lo helm.tar.gz https://get.helm.sh/helm-$HELM_VERSION-linux-amd64.tar.gz && tar xzfv helm.tar.gz && sudo mv linux-amd64/helm /usr/local/bin/ sudo mkdir -p $HOME/.kube $HOME/.minikube - touch KUBECONFIG - sudo minikube start --vm-driver=none --kubernetes-version=$MINIKUBE_KUBERNETES_VERSION sudo chown -R $USER $HOME/.kube $HOME/.minikube + make minikube-start MINIKUBE_DRIVER='docker' CPUS_NUMBER=2 - name: Jenkins Operator - e2e - run: make build e2e + run: | + make e2e E2E_TEST_ARGS='-ginkgo.v' - - name: Jenkins Operator Helm Chart - e2e - run: make e2e BUILDTAGS=Helm E2E_TEST_SELECTOR='^.*Helm.*$' \ No newline at end of file +#TODO Helm e2e test \ No newline at end of file diff --git a/Makefile b/Makefile index 254a96b6..823aa480 100644 --- a/Makefile +++ b/Makefile @@ -315,7 +315,7 @@ endif minikube-start: minikube check-minikube ## Start minikube @echo "+ $@" bin/minikube status && exit 0 || \ - bin/minikube start --kubernetes-version $(MINIKUBE_KUBERNETES_VERSION) --dns-domain=$(CLUSTER_DOMAIN) --extra-config=kubelet.cluster-domain=$(CLUSTER_DOMAIN) --vm-driver=$(MINIKUBE_DRIVER) --memory 4096 --cpus 3 + bin/minikube start --kubernetes-version $(MINIKUBE_KUBERNETES_VERSION) --dns-domain=$(CLUSTER_DOMAIN) --extra-config=kubelet.cluster-domain=$(CLUSTER_DOMAIN) --driver=$(MINIKUBE_DRIVER) --memory 4096 --cpus $(CPUS_NUMBER) .PHONY: crc-start crc-start: check-crc ## Start CodeReady Containers Kubernetes cluster @@ -405,6 +405,17 @@ generate-docs: hugo ## Re-generate docs directory from the website directory rm -rf docs || echo "Cannot remove docs dir, ignoring" bin/hugo -s website -d ../docs +.PHONY: all-in-one-build +FILENAME := config/all_in_one_$(API_VERSION).yaml +all-in-one-build: ## Re-generate all-in-one yaml + @echo "+ $@" + > $(FILENAME) + cat config/rbac/leader_election_role.yaml >> $(FILENAME) + cat config/rbac/leader_election_role_binding.yaml >> $(FILENAME) + cat config/rbac/role.yaml >> $(FILENAME) + cat config/rbac/role_binding.yaml >> $(FILENAME) + cat config/manager/manager.yaml >> $(FILENAME) + ##################### FROM OPERATOR SDK ######################## # Install CRDs into a cluster install-crds: manifests kustomize @@ -424,7 +435,7 @@ undeploy: $(KUSTOMIZE) build config/default | kubectl delete -f - # Generate manifests e.g. CRD, RBAC etc. -manifests: controller-gen +manifests: controller-gen all-in-one-build $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases # Generate code diff --git a/config/all_in_one_v1alpha2.yaml b/config/all_in_one_v1alpha2.yaml new file mode 100644 index 00000000..04971a11 --- /dev/null +++ b/config/all_in_one_v1alpha2.yaml @@ -0,0 +1,227 @@ +--- +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: +- apiGroups: + - "" + - coordination.k8s.io + resources: + - configmaps + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: leader-election-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: leader-election-role +subjects: +- kind: ServiceAccount + name: default + namespace: default +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - "" + resources: + - services + - configmaps + - secrets + - serviceaccounts + verbs: + - get + - create + - update + - list + - watch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - create + - update + - list + - watch +- apiGroups: + - "" + resources: + - pods/portforward + verbs: + - create +- apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - "*" +- apiGroups: + - "" + resources: + - events + verbs: + - watch + - list + - create + - patch +- apiGroups: + - apps + resourceNames: + - jenkins-operator + resources: + - deployments/finalizers + verbs: + - update +- apiGroups: + - jenkins.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - get + - list + - watch +- apiGroups: + - "route.openshift.io" + resources: + - routes + verbs: + - get + - list + - watch + - create + - update +- apiGroups: + - "image.openshift.io" + resources: + - imagestreams + verbs: + - get + - list + - watch +- apiGroups: + - "build.openshift.io" + resources: + - builds + - buildconfigs + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: manager-rolebinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: manager-role +subjects: +- kind: ServiceAccount + name: default + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jenkins-operator + namespace: default + labels: + control-plane: controller-manager +spec: + selector: + matchLabels: + control-plane: controller-manager + replicas: 1 + template: + metadata: + labels: + control-plane: controller-manager + spec: + securityContext: + runAsUser: 65532 + containers: + - command: + - /manager + args: + - --leader-elect + image: jenkins-operator:305dbeda-dirty-dirty + name: jenkins-operator + imagePullPolicy: Never + securityContext: + allowPrivilegeEscalation: false + livenessProbe: + httpGet: + path: /healthz + port: 8081 + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: 8081 + initialDelaySeconds: 5 + periodSeconds: 10 + resources: + limits: + cpu: 100m + memory: 30Mi + requests: + cpu: 100m + memory: 20Mi + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + terminationGracePeriodSeconds: 10 diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml index 52d86618..12c86a38 100644 --- a/config/certmanager/certificate.yaml +++ b/config/certmanager/certificate.yaml @@ -5,7 +5,7 @@ apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: selfsigned-issuer - namespace: system + namespace: default spec: selfSigned: {} --- @@ -13,7 +13,7 @@ apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml - namespace: system + namespace: default spec: # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize dnsNames: diff --git a/config/crd/patches/webhook_in_jenkins.yaml b/config/crd/patches/webhook_in_jenkins.yaml index 37a2d93c..f34b8ecc 100644 --- a/config/crd/patches/webhook_in_jenkins.yaml +++ b/config/crd/patches/webhook_in_jenkins.yaml @@ -9,6 +9,6 @@ spec: webhook: clientConfig: service: - namespace: system + namespace: default name: webhook-service path: /convert diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index 62d52def..d903f4cf 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,5 +1,5 @@ # Adds namespace to all resources. -namespace: jenkins-operator-system +namespace: jenkins-operator # Value of this field is prepended to the # names of all resources, e.g. a deployment named diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml index 49b1f1ab..28a07150 100644 --- a/config/default/manager_auth_proxy_patch.yaml +++ b/config/default/manager_auth_proxy_patch.yaml @@ -4,7 +4,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: controller-manager - namespace: system + namespace: default spec: template: spec: diff --git a/config/default/manager_config_patch.yaml b/config/default/manager_config_patch.yaml index 6c400155..4043379a 100644 --- a/config/default/manager_config_patch.yaml +++ b/config/default/manager_config_patch.yaml @@ -2,7 +2,7 @@ apiVersion: apps/v1 kind: Deployment metadata: name: controller-manager - namespace: system + namespace: default spec: template: spec: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 4d62d526..c615e4de 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -1,15 +1,9 @@ -apiVersion: v1 -kind: Namespace -metadata: - labels: - control-plane: controller-manager - name: system --- apiVersion: apps/v1 kind: Deployment metadata: - name: controller-manager - namespace: system + name: jenkins-operator + namespace: default labels: control-plane: controller-manager spec: @@ -29,8 +23,9 @@ spec: - /manager args: - --leader-elect - image: controller:latest - name: manager + image: jenkins-operator:305dbeda-dirty-dirty + name: jenkins-operator + imagePullPolicy: Never securityContext: allowPrivilegeEscalation: false livenessProbe: diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml index 9b8047b7..713c6413 100644 --- a/config/prometheus/monitor.yaml +++ b/config/prometheus/monitor.yaml @@ -6,7 +6,7 @@ metadata: labels: control-plane: controller-manager name: controller-manager-metrics-monitor - namespace: system + namespace: default spec: endpoints: - path: /metrics diff --git a/config/rbac/auth_proxy_role_binding.yaml b/config/rbac/auth_proxy_role_binding.yaml index 48ed1e4b..95543354 100644 --- a/config/rbac/auth_proxy_role_binding.yaml +++ b/config/rbac/auth_proxy_role_binding.yaml @@ -9,4 +9,4 @@ roleRef: subjects: - kind: ServiceAccount name: default - namespace: system + namespace: default diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml index 6cf656be..022ddfd9 100644 --- a/config/rbac/auth_proxy_service.yaml +++ b/config/rbac/auth_proxy_service.yaml @@ -4,7 +4,7 @@ metadata: labels: control-plane: controller-manager name: controller-manager-metrics-service - namespace: system + namespace: default spec: ports: - name: https diff --git a/config/rbac/leader_election_role.yaml b/config/rbac/leader_election_role.yaml index 6334cc51..96f0afa7 100644 --- a/config/rbac/leader_election_role.yaml +++ b/config/rbac/leader_election_role.yaml @@ -1,3 +1,4 @@ +--- # permissions to do leader election. apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/config/rbac/leader_election_role_binding.yaml b/config/rbac/leader_election_role_binding.yaml index eed16906..b4857aa3 100644 --- a/config/rbac/leader_election_role_binding.yaml +++ b/config/rbac/leader_election_role_binding.yaml @@ -1,3 +1,4 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: @@ -9,4 +10,4 @@ roleRef: subjects: - kind: ServiceAccount name: default - namespace: system + namespace: default diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index f6fd0865..c0d31da6 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -1,4 +1,3 @@ - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -9,54 +8,111 @@ rules: - apiGroups: - "" resources: + - services - configmaps + - secrets + - serviceaccounts verbs: - get + - create + - update + - list + - watch +- apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' +- apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - create + - update - list - watch - apiGroups: - "" resources: - - pods + - pods/portforward verbs: - - create - - delete - - get - - list - - patch - - update - - watch + - create - apiGroups: - - jenkins.io + - "" resources: - - jenkins + - pods/log verbs: - - create - - delete - - get - - list - - patch - - update - - watch + - get + - list + - watch - apiGroups: - - jenkins.io + - "" resources: - - jenkins/finalizers + - pods + - pods/exec verbs: - - update + - "*" - apiGroups: - - jenkins.io + - "" resources: - - jenkins/status + - events verbs: - - get - - patch - - update + - watch + - list + - create + - patch - apiGroups: - - v1 + - apps + resourceNames: + - jenkins-operator resources: - - secrets + - deployments/finalizers verbs: - - get - - list - - watch + - update +- apiGroups: + - jenkins.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - get + - list + - watch +- apiGroups: + - "route.openshift.io" + resources: + - routes + verbs: + - get + - list + - watch + - create + - update +- apiGroups: + - "image.openshift.io" + resources: + - imagestreams + verbs: + - get + - list + - watch +- apiGroups: + - "build.openshift.io" + resources: + - builds + - buildconfigs + verbs: + - get + - list + - watch diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml index 8f265870..1d8f4c44 100644 --- a/config/rbac/role_binding.yaml +++ b/config/rbac/role_binding.yaml @@ -1,3 +1,4 @@ +--- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: @@ -9,4 +10,4 @@ roleRef: subjects: - kind: ServiceAccount name: default - namespace: system + namespace: default diff --git a/config/samples/jenkins.io_v1alpha2_jenkins.yaml b/config/samples/jenkins.io_v1alpha2_jenkins.yaml index 265b967c..c44357b6 100644 --- a/config/samples/jenkins.io_v1alpha2_jenkins.yaml +++ b/config/samples/jenkins.io_v1alpha2_jenkins.yaml @@ -1,7 +1,7 @@ apiVersion: jenkins.io/v1alpha2 kind: Jenkins metadata: - name: jenkins-example + name: example namespace: default spec: configurationAsCode: diff --git a/deploy/all-in-one-v1alpha2.yaml b/deploy/all-in-one-v1alpha2.yaml new file mode 100644 index 00000000..4f013d84 --- /dev/null +++ b/deploy/all-in-one-v1alpha2.yaml @@ -0,0 +1,168 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: jenkins-operator +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: jenkins-operator +rules: + - apiGroups: + - "" + resources: + - services + - configmaps + - secrets + - serviceaccounts + verbs: + - get + - create + - update + - list + - watch + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - create + - update + - list + - watch + - apiGroups: + - "" + resources: + - pods/portforward + verbs: + - create + - apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - "*" + - apiGroups: + - "" + resources: + - events + verbs: + - watch + - list + - create + - patch + - apiGroups: + - apps + resourceNames: + - jenkins-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - jenkins.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - get + - list + - watch + - apiGroups: + - "route.openshift.io" + resources: + - routes + verbs: + - get + - list + - watch + - create + - update + - apiGroups: + - "image.openshift.io" + resources: + - imagestreams + verbs: + - get + - list + - watch + - apiGroups: + - "build.openshift.io" + resources: + - builds + - buildconfigs + verbs: + - get + - list + - watch +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: jenkins-operator +subjects: +- kind: ServiceAccount + name: jenkins-operator +roleRef: + kind: Role + name: jenkins-operator + apiGroup: rbac.authorization.k8s.io +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jenkins-operator +spec: + replicas: 1 + selector: + matchLabels: + name: jenkins-operator + template: + metadata: + labels: + name: jenkins-operator + spec: + serviceAccountName: jenkins-operator + containers: + - name: jenkins-operator + image: virtuslab/jenkins-operator:v0.5.0 + command: + - jenkins-operator + args: [] + imagePullPolicy: IfNotPresent + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "jenkins-operator" diff --git a/deploy/crds/jenkins.io_jenkins_crd.yaml b/deploy/crds/jenkins.io_jenkins_crd.yaml new file mode 100644 index 00000000..1d4e96c4 --- /dev/null +++ b/deploy/crds/jenkins.io_jenkins_crd.yaml @@ -0,0 +1,3027 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: jenkins.jenkins.io +spec: + group: jenkins.io + names: + kind: Jenkins + listKind: JenkinsList + plural: jenkins + singular: jenkins + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: Jenkins is the Schema for the jenkins 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: Spec defines the desired state of the Jenkins + properties: + backup: + description: 'Backup defines configuration of Jenkins backup More info: + https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + properties: + action: + description: Action defines action which performs backup in backup + container sidecar + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + type: object + containerName: + description: ContainerName is the container name responsible for + backup operation + type: string + interval: + description: Interval tells how often make backup in seconds Defaults + to 30. + format: int64 + type: integer + makeBackupBeforePodDeletion: + description: MakeBackupBeforePodDeletion tells operator to make + backup before Jenkins master pod deletion + type: boolean + required: + - action + - containerName + - interval + - makeBackupBeforePodDeletion + type: object + configurationAsCode: + description: ConfigurationAsCode defines configuration of Jenkins customization + via Configuration as Code Jenkins plugin + properties: + configurations: + items: + description: ConfigMapRef is reference to Kubernetes ConfigMap + properties: + name: + type: string + required: + - name + type: object + type: array + secret: + description: SecretRef is reference to Kubernetes secret + properties: + name: + type: string + required: + - name + type: object + required: + - configurations + - secret + type: object + groovyScripts: + description: GroovyScripts defines configuration of Jenkins customization + via groovy scripts + properties: + configurations: + items: + description: ConfigMapRef is reference to Kubernetes ConfigMap + properties: + name: + type: string + required: + - name + type: object + type: array + secret: + description: SecretRef is reference to Kubernetes secret + properties: + name: + type: string + required: + - name + type: object + required: + - configurations + - secret + type: object + jenkinsAPISettings: + description: JenkinsAPISettings defines configuration used by the operator + to gain admin access to the Jenkins API + properties: + authorizationStrategy: + description: AuthorizationStrategy defines authorization strategy + of the operator for the Jenkins API + type: string + required: + - authorizationStrategy + type: object + master: + description: Master represents Jenkins master pod properties and Jenkins + plugins. Every single change here requires a pod restart. + 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 + basePlugins: + description: 'BasePlugins contains plugins required by operator + Defaults to : - name: kubernetes version: 1.15.7 - name: workflow-job + version: "2.39" - name: workflow-aggregator version: "2.6" - name: + git version: 3.10.0 - name: job-dsl version: "1.74" - name: configuration-as-code + version: "1.19" - name: kubernetes-credentials-provider version: 0.12.1' + items: + description: Plugin defines Jenkins plugin + properties: + name: + description: Name is the name of Jenkins plugin + type: string + version: + description: Version is the version of Jenkins plugin + type: string + required: + - name + - version + type: object + type: array + containers: + description: 'List of containers belonging to the pod. Containers + cannot currently be added or removed. There must be at least one + container in a Pod. Defaults to: - image: jenkins/jenkins:lts imagePullPolicy: + Always livenessProbe: failureThreshold: 12 httpGet: path: + /login port: http scheme: HTTP initialDelaySeconds: + 80 periodSeconds: 10 successThreshold: 1 timeoutSeconds: + 5 name: jenkins-master readinessProbe: failureThreshold: + 3 httpGet: path: /login port: http scheme: + HTTP initialDelaySeconds: 30 periodSeconds: 10 successThreshold: + 1 timeoutSeconds: 1 resources: limits: cpu: 1500m memory: + 3Gi requests: cpu: "1" memory: 600Mi' + items: + description: Container defines Kubernetes container attributes + properties: + args: + description: 'Arguments to the entrypoint. The docker image''s + CMD is used if this is not provided. Variable references + $(VAR_NAME) are expanded using the container''s environment. + If a variable cannot be resolved, the reference in the input + string will be unchanged. The $(VAR_NAME) syntax can be + escaped with a double $$, ie: $$(VAR_NAME). Escaped references + will never be expanded, regardless of whether the variable + exists or not. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + command: + description: 'Entrypoint array. Not executed within a shell. + The docker image''s ENTRYPOINT is used if this is not provided. + Variable references $(VAR_NAME) are expanded using the container''s + environment. If a variable cannot be resolved, the reference + in the input string will be unchanged. The $(VAR_NAME) syntax + can be escaped with a double $$, ie: $$(VAR_NAME). Escaped + references will never be expanded, regardless of whether + the variable exists or not. More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell' + items: + type: string + type: array + env: + description: List of environment variables to set in the container. + items: + description: EnvVar represents an environment variable present + in a Container. + properties: + name: + description: Name of the environment variable. Must + be a C_IDENTIFIER. + type: string + value: + description: 'Variable references $(VAR_NAME) are expanded + using the previous defined environment variables in + the container and any service environment variables. + If a variable cannot be resolved, the reference in + the input string will be unchanged. The $(VAR_NAME) + syntax can be escaped with a double $$, ie: $$(VAR_NAME). + Escaped references will never be expanded, regardless + of whether the variable exists or not. Defaults to + "".' + type: string + valueFrom: + description: Source for the environment variable's value. + Cannot be used if value is not empty. + properties: + configMapKeyRef: + description: Selects a key of a ConfigMap. + properties: + key: + description: The key to select. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or + its key must be defined + type: boolean + required: + - key + type: object + fieldRef: + description: 'Selects a field of the pod: supports + metadata.name, metadata.namespace, metadata.labels, + metadata.annotations, spec.nodeName, spec.serviceAccountName, + status.hostIP, status.podIP.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, limits.ephemeral-storage, requests.cpu, + requests.memory and requests.ephemeral-storage) + are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + description: Specifies the output format of + the exposed resources, defaults to "1" + type: string + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + secretKeyRef: + description: Selects a key of a secret in the pod's + namespace + properties: + key: + description: The key of the secret to select + from. Must be a valid secret key. + type: string + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + required: + - key + type: object + type: object + required: + - name + type: object + type: array + envFrom: + description: List of sources to populate environment variables + in the container. The keys defined within a source must + be a C_IDENTIFIER. All invalid keys will be reported as + an event when the container is starting. When a key exists + in multiple sources, the value associated with the last + source will take precedence. Values defined by an Env with + a duplicate key will take precedence. + items: + description: EnvFromSource represents the source of a set + of ConfigMaps + properties: + configMapRef: + description: The ConfigMap to select from + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the ConfigMap must + be defined + type: boolean + type: object + prefix: + description: An optional identifier to prepend to each + key in the ConfigMap. Must be a C_IDENTIFIER. + type: string + secretRef: + description: The Secret to select from + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + optional: + description: Specify whether the Secret must be + defined + type: boolean + type: object + type: object + type: array + image: + description: 'Docker image name. More info: https://kubernetes.io/docs/concepts/containers/images' + type: string + imagePullPolicy: + description: Image pull policy. One of Always, Never, IfNotPresent. + Defaults to Always. + type: string + lifecycle: + description: Actions that the management system should take + in response to container lifecycle events. + properties: + postStart: + description: 'PostStart is called immediately after a + container is created. If the handler fails, the container + is terminated and restarted according to its restart + policy. Other management of the container blocks until + the hook completes. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory + for the command is root ('/') in the container's + filesystem. The command is simply exec'd, it + is not run inside a shell, so traditional shell + instructions ('|', etc) won't work. To use a + shell, you need to explicitly call out to that + shell. Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request to + perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: implement + a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + preStop: + description: 'PreStop is called immediately before a container + is terminated due to an API request or management event + such as liveness/startup probe failure, preemption, + resource contention, etc. The handler is not called + if the container crashes or exits. The reason for termination + is passed to the handler. The Pod''s termination grace + period countdown begins before the PreStop hooked is + executed. Regardless of the outcome of the handler, + the container will eventually terminate within the Pod''s + termination grace period. Other management of the container + blocks until the hook completes or until the termination + grace period is reached. More info: https://kubernetes.io/docs/concepts/containers/container-lifecycle-hooks/#container-hooks' + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory + for the command is root ('/') in the container's + filesystem. The command is simply exec'd, it + is not run inside a shell, so traditional shell + instructions ('|', etc) won't work. To use a + shell, you need to explicitly call out to that + shell. Exit status of 0 is treated as live/healthy + and non-zero is unhealthy. + items: + type: string + type: array + type: object + httpGet: + description: HTTPGet specifies the http request to + perform. + properties: + host: + description: Host name to connect to, defaults + to the pod IP. You probably want to set "Host" + in httpHeaders instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the + host. Defaults to HTTP. + type: string + required: + - port + type: object + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: implement + a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, + defaults to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range + 1 to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + type: object + type: object + livenessProbe: + description: Periodic probe of container liveness. Container + will be restarted if the probe fails. + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's filesystem. + The command is simply exec'd, it is not run inside + a shell, so traditional shell instructions ('|', + etc) won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is treated + as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has + started before liveness probes are initiated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum value + is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: implement + a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + name: + description: Name of the container specified as a DNS_LABEL. + Each container in a pod must have a unique name (DNS_LABEL). + type: string + ports: + description: List of ports to expose from the container. Exposing + a port here gives the system additional information about + the network connections a container uses, but is primarily + informational. Not specifying a port here DOES NOT prevent + that port from being exposed. Any port which is listening + on the default "0.0.0.0" address inside a container will + be accessible from the network. + items: + description: ContainerPort represents a network port in + a single container. + properties: + containerPort: + description: Number of port to expose on the pod's IP + address. This must be a valid port number, 0 < x < + 65536. + format: int32 + type: integer + hostIP: + description: What host IP to bind the external port + to. + type: string + hostPort: + description: Number of port to expose on the host. If + specified, this must be a valid port number, 0 < x + < 65536. If HostNetwork is specified, this must match + ContainerPort. Most containers do not need this. + format: int32 + type: integer + name: + description: If specified, this must be an IANA_SVC_NAME + and unique within the pod. Each named port in a pod + must have a unique name. Name for the port that can + be referred to by services. + type: string + protocol: + description: Protocol for port. Must be UDP, TCP, or + SCTP. Defaults to "TCP". + type: string + required: + - containerPort + type: object + type: array + readinessProbe: + description: Periodic probe of container service readiness. + Container will be removed from service endpoints if the + probe fails. + properties: + exec: + description: One and only one of the following should + be specified. Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute + inside the container, the working directory for + the command is root ('/') in the container's filesystem. + The command is simply exec'd, it is not run inside + a shell, so traditional shell instructions ('|', + etc) won't work. To use a shell, you need to explicitly + call out to that shell. Exit status of 0 is treated + as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + failureThreshold: + description: Minimum consecutive failures for the probe + to be considered failed after having succeeded. Defaults + to 3. Minimum value is 1. + format: int32 + type: integer + httpGet: + description: HTTPGet specifies the http request to perform. + properties: + host: + description: Host name to connect to, defaults to + the pod IP. You probably want to set "Host" in httpHeaders + instead. + type: string + httpHeaders: + description: Custom headers to set in the request. + HTTP allows repeated headers. + items: + description: HTTPHeader describes a custom header + to be used in HTTP probes + properties: + name: + description: The header field name + type: string + value: + description: The header field value + type: string + required: + - name + - value + type: object + type: array + path: + description: Path to access on the HTTP server. + type: string + port: + anyOf: + - type: integer + - type: string + description: Name or number of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + scheme: + description: Scheme to use for connecting to the host. + Defaults to HTTP. + type: string + required: + - port + type: object + initialDelaySeconds: + description: 'Number of seconds after the container has + started before liveness probes are initiated. More info: + https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + periodSeconds: + description: How often (in seconds) to perform the probe. + Default to 10 seconds. Minimum value is 1. + format: int32 + type: integer + successThreshold: + description: Minimum consecutive successes for the probe + to be considered successful after having failed. Defaults + to 1. Must be 1 for liveness and startup. Minimum value + is 1. + format: int32 + type: integer + tcpSocket: + description: 'TCPSocket specifies an action involving + a TCP port. TCP hooks not yet supported TODO: implement + a realistic TCP lifecycle hook' + properties: + host: + description: 'Optional: Host name to connect to, defaults + to the pod IP.' + type: string + port: + anyOf: + - type: integer + - type: string + description: Number or name of the port to access + on the container. Number must be in the range 1 + to 65535. Name must be an IANA_SVC_NAME. + x-kubernetes-int-or-string: true + required: + - port + type: object + timeoutSeconds: + description: 'Number of seconds after which the probe + times out. Defaults to 1 second. Minimum value is 1. + More info: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#container-probes' + format: int32 + type: integer + type: object + resources: + description: 'Compute Resources required by this container. + More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + properties: + limits: + additionalProperties: + type: string + description: 'Limits describes the maximum amount of compute + resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + requests: + additionalProperties: + type: string + description: 'Requests describes the minimum amount of + compute resources required. If Requests is omitted for + a container, it defaults to Limits if that is explicitly + specified, otherwise to an implementation-defined value. + More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/' + type: object + type: object + securityContext: + description: 'Security options the pod should run with. More + info: https://kubernetes.io/docs/concepts/policy/security-context/ + More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/' + properties: + allowPrivilegeEscalation: + description: 'AllowPrivilegeEscalation controls whether + a process can gain more privileges than its parent process. + This bool directly controls if the no_new_privs flag + will be set on the container process. AllowPrivilegeEscalation + is true always when the container is: 1) run as Privileged + 2) has CAP_SYS_ADMIN' + type: boolean + capabilities: + description: The capabilities to add/drop when running + containers. Defaults to the default set of capabilities + granted by the container runtime. + properties: + add: + description: Added capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + drop: + description: Removed capabilities + items: + description: Capability represent POSIX capabilities + type + type: string + type: array + type: object + privileged: + description: Run container in privileged mode. Processes + in privileged containers are essentially equivalent + to root on the host. Defaults to false. + type: boolean + procMount: + description: procMount denotes the type of proc mount + to use for the containers. The default is DefaultProcMount + which uses the container runtime defaults for readonly + paths and masked paths. This requires the ProcMountType + feature flag to be enabled. + type: string + readOnlyRootFilesystem: + description: Whether this container has a read-only root + filesystem. Default is false. + type: boolean + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be + set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as + a non-root user. If true, the Kubelet will validate + the image at runtime to ensure that it does not run + as UID 0 (root) and fail to start the container if it + does. If unset or false, no such validation will be + performed. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata + if unspecified. May also be set in PodSecurityContext. If + set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to the + container. If unspecified, the container runtime will + allocate a random SELinux context for each container. May + also be set in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + properties: + level: + description: Level is SELinux level label that applies + to the container. + type: string + role: + description: Role is a SELinux role label that applies + to the container. + type: string + type: + description: Type is a SELinux type label that applies + to the container. + type: string + user: + description: User is a SELinux user label that applies + to the container. + type: string + type: object + windowsOptions: + description: The Windows specific settings applied to + all containers. If unspecified, the options from the + PodSecurityContext will be used. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA + admission webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec + named by the GMSACredentialSpecName field. This + field is alpha-level and is only honored by servers + that enable the WindowsGMSA feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of + the GMSA credential spec to use. This field is alpha-level + and is only honored by servers that enable the WindowsGMSA + feature flag. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set + in PodSecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence. This field is alpha-level and + it is only honored by servers that enable the WindowsRunAsUserName + feature flag. + type: string + type: object + type: object + volumeMounts: + description: Pod volumes to mount into the container's filesystem. + items: + description: VolumeMount describes a mounting of a Volume + within a container. + properties: + mountPath: + description: Path within the container at which the + volume should be mounted. Must not contain ':'. + type: string + mountPropagation: + description: mountPropagation determines how mounts + are propagated from the host to container and the + other way around. When not set, MountPropagationNone + is used. This field is beta in 1.10. + type: string + name: + description: This must match the Name of a Volume. + type: string + readOnly: + description: Mounted read-only if true, read-write otherwise + (false or unspecified). Defaults to false. + type: boolean + subPath: + description: Path within the volume from which the container's + volume should be mounted. Defaults to "" (volume's + root). + type: string + subPathExpr: + description: Expanded path within the volume from which + the container's volume should be mounted. Behaves + similarly to SubPath but environment variable references + $(VAR_NAME) are expanded using the container's environment. + Defaults to "" (volume's root). SubPathExpr and SubPath + are mutually exclusive. This field is beta in 1.15. + type: string + required: + - mountPath + - name + type: object + type: array + workingDir: + description: Container's working directory. If not specified, + the container runtime's default will be used, which might + be configured in the container image. + type: string + required: + - image + - imagePullPolicy + - name + - resources + type: object + type: array + disableCSRFProtection: + description: DisableCSRFProtection allows you to toggle CSRF Protection + on Jenkins + type: boolean + imagePullSecrets: + description: 'ImagePullSecrets is an optional list of references + to secrets in the same namespace to use for pulling any of the + images used by this PodSpec. If specified, these secrets will + be passed to individual puller implementations for them to use. + For example, in the case of docker, only DockerConfig type secrets + are honored. More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod' + items: + description: LocalObjectReference contains enough information + to let you locate the referenced object inside the same namespace. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + type: object + type: array + 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 + masterAnnotations: + 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 + Deprecated: will be removed in the future, please use Annotations(annotations)' + type: object + nodeSelector: + additionalProperties: + type: string + description: 'NodeSelector is a selector which must be true for + the pod to fit on a node. Selector which must match a node''s + labels for the pod to be scheduled on that node. More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/' + type: object + plugins: + description: Plugins contains plugins required by user + items: + description: Plugin defines Jenkins plugin + properties: + name: + description: Name is the name of Jenkins plugin + type: string + version: + description: Version is the version of Jenkins plugin + type: string + required: + - name + - version + type: object + type: array + securityContext: + description: 'SecurityContext that applies to all the containers + of the Jenkins Master. As per kubernetes specification, it can + be overridden for each container individually. Defaults to: runAsUser: + 1000 fsGroup: 1000' + properties: + fsGroup: + description: "A special supplemental group that applies to all + containers in a pod. Some volume types allow the Kubelet to + change the ownership of that volume to be owned by the pod: + \n 1. The owning GID will be the FSGroup 2. The setgid bit + is set (new files created in the volume will be owned by FSGroup) + 3. The permission bits are OR'd with rw-rw---- \n If unset, + the Kubelet will not modify the ownership and permissions + of any volume." + format: int64 + type: integer + runAsGroup: + description: The GID to run the entrypoint of the container + process. Uses runtime default if unset. May also be set in + SecurityContext. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence for + that container. + format: int64 + type: integer + runAsNonRoot: + description: Indicates that the container must run as a non-root + user. If true, the Kubelet will validate the image at runtime + to ensure that it does not run as UID 0 (root) and fail to + start the container if it does. If unset or false, no such + validation will be performed. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence. + type: boolean + runAsUser: + description: The UID to run the entrypoint of the container + process. Defaults to user specified in image metadata if unspecified. + May also be set in SecurityContext. If set in both SecurityContext + and PodSecurityContext, the value specified in SecurityContext + takes precedence for that container. + format: int64 + type: integer + seLinuxOptions: + description: The SELinux context to be applied to all containers. + If unspecified, the container runtime will allocate a random + SELinux context for each container. May also be set in SecurityContext. If + set in both SecurityContext and PodSecurityContext, the value + specified in SecurityContext takes precedence for that container. + properties: + level: + description: Level is SELinux level label that applies to + the container. + type: string + role: + description: Role is a SELinux role label that applies to + the container. + type: string + type: + description: Type is a SELinux type label that applies to + the container. + type: string + user: + description: User is a SELinux user label that applies to + the container. + type: string + type: object + supplementalGroups: + description: A list of groups applied to the first process run + in each container, in addition to the container's primary + GID. If unspecified, no groups will be added to any container. + items: + format: int64 + type: integer + type: array + sysctls: + description: Sysctls hold a list of namespaced sysctls used + for the pod. Pods with unsupported sysctls (by the container + runtime) might fail to launch. + items: + description: Sysctl defines a kernel parameter to be set + properties: + name: + description: Name of a property to set + type: string + value: + description: Value of a property to set + type: string + required: + - name + - value + type: object + type: array + windowsOptions: + description: The Windows specific settings applied to all containers. + If unspecified, the options within a container's SecurityContext + will be used. If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + properties: + gmsaCredentialSpec: + description: GMSACredentialSpec is where the GMSA admission + webhook (https://github.com/kubernetes-sigs/windows-gmsa) + inlines the contents of the GMSA credential spec named + by the GMSACredentialSpecName field. This field is alpha-level + and is only honored by servers that enable the WindowsGMSA + feature flag. + type: string + gmsaCredentialSpecName: + description: GMSACredentialSpecName is the name of the GMSA + credential spec to use. This field is alpha-level and + is only honored by servers that enable the WindowsGMSA + feature flag. + type: string + runAsUserName: + description: The UserName in Windows to run the entrypoint + of the container process. Defaults to the user specified + in image metadata if unspecified. May also be set in PodSecurityContext. + If set in both SecurityContext and PodSecurityContext, + the value specified in SecurityContext takes precedence. + This field is alpha-level and it is only honored by servers + that enable the WindowsRunAsUserName feature flag. + type: string + type: object + type: object + tolerations: + description: If specified, the pod's tolerations. + items: + description: The pod this Toleration is attached to tolerates + any taint that matches the triple using the + matching operator . + properties: + effect: + description: Effect indicates the taint effect to match. Empty + means match all taint effects. When specified, allowed values + are NoSchedule, PreferNoSchedule and NoExecute. + type: string + key: + description: Key is the taint key that the toleration applies + to. Empty means match all taint keys. If the key is empty, + operator must be Exists; this combination means to match + all values and all keys. + type: string + operator: + description: Operator represents a key's relationship to the + value. Valid operators are Exists and Equal. Defaults to + Equal. Exists is equivalent to wildcard for value, so that + a pod can tolerate all taints of a particular category. + type: string + tolerationSeconds: + description: TolerationSeconds represents the period of time + the toleration (which must be of effect NoExecute, otherwise + this field is ignored) tolerates the taint. By default, + it is not set, which means tolerate the taint forever (do + not evict). Zero and negative values will be treated as + 0 (evict immediately) by the system. + format: int64 + type: integer + value: + description: Value is the taint value the toleration matches + to. If the operator is Exists, the value should be empty, + otherwise just a regular string. + type: string + type: object + type: array + volumes: + description: 'List of volumes that can be mounted by containers + belonging to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes' + items: + description: Volume represents a named volume in a pod that may + be accessed by any container in the pod. + properties: + awsElasticBlockStore: + description: 'AWSElasticBlockStore represents an AWS Disk + resource that is attached to a kubelet''s host machine and + then exposed to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + properties: + fsType: + description: 'Filesystem type of the volume that you want + to mount. Tip: Ensure that the filesystem type is supported + by the host operating system. Examples: "ext4", "xfs", + "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify the + partition as "1". Similarly, the volume partition for + /dev/sda is "0" (or you can leave the property empty).' + format: int32 + type: integer + readOnly: + description: 'Specify "true" to force and set the ReadOnly + property in VolumeMounts to "true". If omitted, the + default is "false". More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: boolean + volumeID: + description: 'Unique ID of the persistent disk resource + in AWS (Amazon EBS volume). More info: https://kubernetes.io/docs/concepts/storage/volumes#awselasticblockstore' + type: string + required: + - volumeID + type: object + azureDisk: + description: AzureDisk represents an Azure Data Disk mount + on the host and bind mount to the pod. + properties: + cachingMode: + description: 'Host Caching mode: None, Read Only, Read + Write.' + type: string + diskName: + description: The Name of the data disk in the blob storage + type: string + diskURI: + description: The URI the data disk in the blob storage + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + kind: + description: 'Expected values Shared: multiple blob disks + per storage account Dedicated: single blob disk per + storage account Managed: azure managed data disk (only + in managed availability set). defaults to shared' + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + required: + - diskName + - diskURI + type: object + azureFile: + description: AzureFile represents an Azure File Service mount + on the host and bind mount to the pod. + properties: + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretName: + description: the name of secret that contains Azure Storage + Account Name and Key + type: string + shareName: + description: Share Name + type: string + required: + - secretName + - shareName + type: object + cephfs: + description: CephFS represents a Ceph FS mount on the host + that shares a pod's lifetime + properties: + monitors: + description: 'Required: Monitors is a collection of Ceph + monitors More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + items: + type: string + type: array + path: + description: 'Optional: Used as the mounted root, rather + than the full Ceph tree, default is /' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: boolean + secretFile: + description: 'Optional: SecretFile is the path to key + ring for User, default is /etc/ceph/user.secret More + info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + secretRef: + description: 'Optional: SecretRef is reference to the + authentication secret for User, default is empty. More + info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'Optional: User is the rados user name, default + is admin More info: https://examples.k8s.io/volumes/cephfs/README.md#how-to-use-it' + type: string + required: + - monitors + type: object + cinder: + description: 'Cinder represents a cinder volume attached and + mounted on kubelets host machine. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Examples: + "ext4", "xfs", "ntfs". Implicitly inferred to be "ext4" + if unspecified. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts. + More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: boolean + secretRef: + description: 'Optional: points to a secret object containing + parameters used to connect to OpenStack.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeID: + description: 'volume id used to identify the volume in + cinder. More info: https://examples.k8s.io/mysql-cinder-pd/README.md' + type: string + required: + - volumeID + type: object + configMap: + description: ConfigMap represents a configMap that should + populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created files + by default. Must be a value between 0 and 0777. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in the + Data field of the referenced ConfigMap will be projected + into the volume as a file whose name is the key and + content is the value. If specified, the listed keys + will be projected into the specified paths, and unlisted + keys will not be present. If a key is specified which + is not present in the ConfigMap, the volume setup will + error unless it is marked optional. Paths must be relative + and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use on this + file, must be a value between 0 and 0777. If not + specified, the volume defaultMode will be used. + This might be in conflict with other options that + affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the file to map + the key to. May not be an absolute path. May not + contain the path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or its keys + must be defined + type: boolean + type: object + csi: + description: CSI (Container Storage Interface) represents + storage that is handled by an external CSI driver (Alpha + feature). + properties: + driver: + description: Driver is the name of the CSI driver that + handles this volume. Consult with your admin for the + correct name as registered in the cluster. + type: string + fsType: + description: Filesystem type to mount. Ex. "ext4", "xfs", + "ntfs". If not provided, the empty value is passed to + the associated CSI driver which will determine the default + filesystem to apply. + type: string + nodePublishSecretRef: + description: NodePublishSecretRef is a reference to the + secret object containing sensitive information to pass + to the CSI driver to complete the CSI NodePublishVolume + and NodeUnpublishVolume calls. This field is optional, + and may be empty if no secret is required. If the secret + object contains more than one secret, all secret references + are passed. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + readOnly: + description: Specifies a read-only configuration for the + volume. Defaults to false (read/write). + type: boolean + volumeAttributes: + additionalProperties: + type: string + description: VolumeAttributes stores driver-specific properties + that are passed to the CSI driver. Consult your driver's + documentation for supported values. + type: object + required: + - driver + type: object + downwardAPI: + description: DownwardAPI represents downward API about the + pod that should populate this volume + properties: + defaultMode: + description: 'Optional: mode bits to use on created files + by default. Must be a value between 0 and 0777. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: Items is a list of downward API volume file + items: + description: DownwardAPIVolumeFile represents information + to create the file containing the pod field + properties: + fieldRef: + description: 'Required: Selects a field of the pod: + only annotations, labels, name and namespace are + supported.' + properties: + apiVersion: + description: Version of the schema the FieldPath + is written in terms of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to select in + the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits to use on this + file, must be a value between 0 and 0777. If not + specified, the volume defaultMode will be used. + This might be in conflict with other options that + affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative path + name of the file to be created. Must not be absolute + or contain the ''..'' path. Must be utf-8 encoded. + The first item of the relative path must not start + with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the container: + only resources limits and requests (limits.cpu, + limits.memory, requests.cpu and requests.memory) + are currently supported.' + properties: + containerName: + description: 'Container name: required for volumes, + optional for env vars' + type: string + divisor: + description: Specifies the output format of + the exposed resources, defaults to "1" + type: string + resource: + description: 'Required: resource to select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + emptyDir: + description: 'EmptyDir represents a temporary directory that + shares a pod''s lifetime. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + properties: + medium: + description: 'What type of storage medium should back + this directory. The default is "" which means to use + the node''s default medium. Must be an empty string + (default) or Memory. More info: https://kubernetes.io/docs/concepts/storage/volumes#emptydir' + type: string + sizeLimit: + description: 'Total amount of local storage required for + this EmptyDir volume. The size limit is also applicable + for memory medium. The maximum usage on memory medium + EmptyDir would be the minimum value between the SizeLimit + specified here and the sum of memory limits of all containers + in a pod. The default is nil which means that the limit + is undefined. More info: http://kubernetes.io/docs/user-guide/volumes#emptydir' + type: string + type: object + fc: + description: FC represents a Fibre Channel resource that is + attached to a kubelet's host machine and then exposed to + the pod. + properties: + fsType: + description: 'Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + lun: + description: 'Optional: FC target lun number' + format: int32 + type: integer + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + targetWWNs: + description: 'Optional: FC target worldwide names (WWNs)' + items: + type: string + type: array + wwids: + description: 'Optional: FC volume world wide identifiers + (wwids) Either wwids or combination of targetWWNs and + lun must be set, but not both simultaneously.' + items: + type: string + type: array + type: object + flexVolume: + description: FlexVolume represents a generic volume resource + that is provisioned/attached using an exec based plugin. + properties: + driver: + description: Driver is the name of the driver to use for + this volume. + type: string + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". The default filesystem depends on FlexVolume + script. + type: string + options: + additionalProperties: + type: string + description: 'Optional: Extra command options if any.' + type: object + readOnly: + description: 'Optional: Defaults to false (read/write). + ReadOnly here will force the ReadOnly setting in VolumeMounts.' + type: boolean + secretRef: + description: 'Optional: SecretRef is reference to the + secret object containing sensitive information to pass + to the plugin scripts. This may be empty if no secret + object is specified. If the secret object contains more + than one secret, all secrets are passed to the plugin + scripts.' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - driver + type: object + flocker: + description: Flocker represents a Flocker volume attached + to a kubelet's host machine. This depends on the Flocker + control service being running + properties: + datasetName: + description: Name of the dataset stored as metadata -> + name on the dataset for Flocker should be considered + as deprecated + type: string + datasetUUID: + description: UUID of the dataset. This is unique identifier + of a Flocker dataset + type: string + type: object + gcePersistentDisk: + description: 'GCEPersistentDisk represents a GCE Disk resource + that is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + properties: + fsType: + description: 'Filesystem type of the volume that you want + to mount. Tip: Ensure that the filesystem type is supported + by the host operating system. Examples: "ext4", "xfs", + "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + partition: + description: 'The partition in the volume that you want + to mount. If omitted, the default is to mount by volume + name. Examples: For volume /dev/sda1, you specify the + partition as "1". Similarly, the volume partition for + /dev/sda is "0" (or you can leave the property empty). + More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + format: int32 + type: integer + pdName: + description: 'Unique name of the PD resource in GCE. Used + to identify the disk in GCE. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. More info: https://kubernetes.io/docs/concepts/storage/volumes#gcepersistentdisk' + type: boolean + required: + - pdName + type: object + gitRepo: + description: 'GitRepo represents a git repository at a particular + revision. DEPRECATED: GitRepo is deprecated. To provision + a container with a git repo, mount an EmptyDir into an InitContainer + that clones the repo using git, then mount the EmptyDir + into the Pod''s container.' + properties: + directory: + description: Target directory name. Must not contain or + start with '..'. If '.' is supplied, the volume directory + will be the git repository. Otherwise, if specified, + the volume will contain the git repository in the subdirectory + with the given name. + type: string + repository: + description: Repository URL + type: string + revision: + description: Commit hash for the specified revision. + type: string + required: + - repository + type: object + glusterfs: + description: 'Glusterfs represents a Glusterfs mount on the + host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/glusterfs/README.md' + properties: + endpoints: + description: 'EndpointsName is the endpoint name that + details Glusterfs topology. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + path: + description: 'Path is the Glusterfs volume path. More + info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: string + readOnly: + description: 'ReadOnly here will force the Glusterfs volume + to be mounted with read-only permissions. Defaults to + false. More info: https://examples.k8s.io/volumes/glusterfs/README.md#create-a-pod' + type: boolean + required: + - endpoints + - path + type: object + hostPath: + description: 'HostPath represents a pre-existing file or directory + on the host machine that is directly exposed to the container. + This is generally used for system agents or other privileged + things that are allowed to see the host machine. Most containers + will NOT need this. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath + --- TODO(jonesdl) We need to restrict who can use host directory + mounts and who can/can not mount host directories as read/write.' + properties: + path: + description: 'Path of the directory on the host. If the + path is a symlink, it will follow the link to the real + path. More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + type: + description: 'Type for HostPath Volume Defaults to "" + More info: https://kubernetes.io/docs/concepts/storage/volumes#hostpath' + type: string + required: + - path + type: object + iscsi: + description: 'ISCSI represents an ISCSI Disk resource that + is attached to a kubelet''s host machine and then exposed + to the pod. More info: https://examples.k8s.io/volumes/iscsi/README.md' + properties: + chapAuthDiscovery: + description: whether support iSCSI Discovery CHAP authentication + type: boolean + chapAuthSession: + description: whether support iSCSI Session CHAP authentication + type: boolean + fsType: + description: 'Filesystem type of the volume that you want + to mount. Tip: Ensure that the filesystem type is supported + by the host operating system. Examples: "ext4", "xfs", + "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#iscsi + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + initiatorName: + description: Custom iSCSI Initiator Name. If initiatorName + is specified with iscsiInterface simultaneously, new + iSCSI interface : will be + created for the connection. + type: string + iqn: + description: Target iSCSI Qualified Name. + type: string + iscsiInterface: + description: iSCSI Interface Name that uses an iSCSI transport. + Defaults to 'default' (tcp). + type: string + lun: + description: iSCSI Target Lun number. + format: int32 + type: integer + portals: + description: iSCSI Target Portal List. The portal is either + an IP or ip_addr:port if the port is other than default + (typically TCP ports 860 and 3260). + items: + type: string + type: array + readOnly: + description: ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. + type: boolean + secretRef: + description: CHAP Secret for iSCSI target and initiator + authentication + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + targetPortal: + description: iSCSI Target Portal. The Portal is either + an IP or ip_addr:port if the port is other than default + (typically TCP ports 860 and 3260). + type: string + required: + - iqn + - lun + - targetPortal + type: object + name: + description: 'Volume''s name. Must be a DNS_LABEL and unique + within the pod. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names' + type: string + nfs: + description: 'NFS represents an NFS mount on the host that + shares a pod''s lifetime More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + properties: + path: + description: 'Path that is exported by the NFS server. + More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + readOnly: + description: 'ReadOnly here will force the NFS export + to be mounted with read-only permissions. Defaults to + false. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: boolean + server: + description: 'Server is the hostname or IP address of + the NFS server. More info: https://kubernetes.io/docs/concepts/storage/volumes#nfs' + type: string + required: + - path + - server + type: object + persistentVolumeClaim: + description: 'PersistentVolumeClaimVolumeSource represents + a reference to a PersistentVolumeClaim in the same namespace. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + properties: + claimName: + description: 'ClaimName is the name of a PersistentVolumeClaim + in the same namespace as the pod using this volume. + More info: https://kubernetes.io/docs/concepts/storage/persistent-volumes#persistentvolumeclaims' + type: string + readOnly: + description: Will force the ReadOnly setting in VolumeMounts. + Default false. + type: boolean + required: + - claimName + type: object + photonPersistentDisk: + description: PhotonPersistentDisk represents a PhotonController + persistent disk attached and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + pdID: + description: ID that identifies Photon Controller persistent + disk + type: string + required: + - pdID + type: object + portworxVolume: + description: PortworxVolume represents a portworx volume attached + and mounted on kubelets host machine + properties: + fsType: + description: FSType represents the filesystem type to + mount Must be a filesystem type supported by the host + operating system. Ex. "ext4", "xfs". Implicitly inferred + to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + volumeID: + description: VolumeID uniquely identifies a Portworx volume + type: string + required: + - volumeID + type: object + projected: + description: Items for all in one resources secrets, configmaps, + and downward API + properties: + defaultMode: + description: Mode bits to use on created files by default. + Must be a value between 0 and 0777. Directories within + the path are not affected by this setting. This might + be in conflict with other options that affect the file + mode, like fsGroup, and the result can be other mode + bits set. + format: int32 + type: integer + sources: + description: list of volume projections + items: + description: Projection that may be projected along + with other supported volume types + properties: + configMap: + description: information about the configMap data + to project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced ConfigMap + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified + which is not present in the ConfigMap, the + volume setup will error unless it is marked + optional. Paths must be relative and may not + contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might + be in conflict with other options that + affect the file mode, like fsGroup, + and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be an + absolute path. May not contain the path + element '..'. May not start with the + string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the ConfigMap or + its keys must be defined + type: boolean + type: object + downwardAPI: + description: information about the downwardAPI data + to project + properties: + items: + description: Items is a list of DownwardAPIVolume + file + items: + description: DownwardAPIVolumeFile represents + information to create the file containing + the pod field + properties: + fieldRef: + description: 'Required: Selects a field + of the pod: only annotations, labels, + name and namespace are supported.' + properties: + apiVersion: + description: Version of the schema + the FieldPath is written in terms + of, defaults to "v1". + type: string + fieldPath: + description: Path of the field to + select in the specified API version. + type: string + required: + - fieldPath + type: object + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might + be in conflict with other options that + affect the file mode, like fsGroup, + and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: 'Required: Path is the relative + path name of the file to be created. + Must not be absolute or contain the + ''..'' path. Must be utf-8 encoded. + The first item of the relative path + must not start with ''..''' + type: string + resourceFieldRef: + description: 'Selects a resource of the + container: only resources limits and + requests (limits.cpu, limits.memory, + requests.cpu and requests.memory) are + currently supported.' + properties: + containerName: + description: 'Container name: required + for volumes, optional for env vars' + type: string + divisor: + description: Specifies the output + format of the exposed resources, + defaults to "1" + type: string + resource: + description: 'Required: resource to + select' + type: string + required: + - resource + type: object + required: + - path + type: object + type: array + type: object + secret: + description: information about the secret data to + project + properties: + items: + description: If unspecified, each key-value + pair in the Data field of the referenced Secret + will be projected into the volume as a file + whose name is the key and content is the value. + If specified, the listed keys will be projected + into the specified paths, and unlisted keys + will not be present. If a key is specified + which is not present in the Secret, the volume + setup will error unless it is marked optional. + Paths must be relative and may not contain + the '..' path or start with '..'. + items: + description: Maps a string key to a path within + a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use + on this file, must be a value between + 0 and 0777. If not specified, the volume + defaultMode will be used. This might + be in conflict with other options that + affect the file mode, like fsGroup, + and the result can be other mode bits + set.' + format: int32 + type: integer + path: + description: The relative path of the + file to map the key to. May not be an + absolute path. May not contain the path + element '..'. May not start with the + string '..'. + type: string + required: + - key + - path + type: object + type: array + name: + description: 'Name of the referent. More info: + https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, + kind, uid?' + type: string + optional: + description: Specify whether the Secret or its + key must be defined + type: boolean + type: object + serviceAccountToken: + description: information about the serviceAccountToken + data to project + properties: + audience: + description: Audience is the intended audience + of the token. A recipient of a token must + identify itself with an identifier specified + in the audience of the token, and otherwise + should reject the token. The audience defaults + to the identifier of the apiserver. + type: string + expirationSeconds: + description: ExpirationSeconds is the requested + duration of validity of the service account + token. As the token approaches expiration, + the kubelet volume plugin will proactively + rotate the service account token. The kubelet + will start trying to rotate the token if the + token is older than 80 percent of its time + to live or if the token is older than 24 hours.Defaults + to 1 hour and must be at least 10 minutes. + format: int64 + type: integer + path: + description: Path is the path relative to the + mount point of the file to project the token + into. + type: string + required: + - path + type: object + type: object + type: array + required: + - sources + type: object + quobyte: + description: Quobyte represents a Quobyte mount on the host + that shares a pod's lifetime + properties: + group: + description: Group to map volume access to Default is + no group + type: string + readOnly: + description: ReadOnly here will force the Quobyte volume + to be mounted with read-only permissions. Defaults to + false. + type: boolean + registry: + description: Registry represents a single or multiple + Quobyte Registry services specified as a string as host:port + pair (multiple entries are separated with commas) which + acts as the central registry for volumes + type: string + tenant: + description: Tenant owning the given Quobyte volume in + the Backend Used with dynamically provisioned Quobyte + volumes, value is set by the plugin + type: string + user: + description: User to map volume access to Defaults to + serivceaccount user + type: string + volume: + description: Volume is a string that references an already + created Quobyte volume by name. + type: string + required: + - registry + - volume + type: object + rbd: + description: 'RBD represents a Rados Block Device mount on + the host that shares a pod''s lifetime. More info: https://examples.k8s.io/volumes/rbd/README.md' + properties: + fsType: + description: 'Filesystem type of the volume that you want + to mount. Tip: Ensure that the filesystem type is supported + by the host operating system. Examples: "ext4", "xfs", + "ntfs". Implicitly inferred to be "ext4" if unspecified. + More info: https://kubernetes.io/docs/concepts/storage/volumes#rbd + TODO: how do we prevent errors in the filesystem from + compromising the machine' + type: string + image: + description: 'The rados image name. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + keyring: + description: 'Keyring is the path to key ring for RBDUser. + Default is /etc/ceph/keyring. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + monitors: + description: 'A collection of Ceph monitors. More info: + https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + items: + type: string + type: array + pool: + description: 'The rados pool name. Default is rbd. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + readOnly: + description: 'ReadOnly here will force the ReadOnly setting + in VolumeMounts. Defaults to false. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: boolean + secretRef: + description: 'SecretRef is name of the authentication + secret for RBDUser. If provided overrides keyring. Default + is nil. More info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + user: + description: 'The rados user name. Default is admin. More + info: https://examples.k8s.io/volumes/rbd/README.md#how-to-use-it' + type: string + required: + - image + - monitors + type: object + scaleIO: + description: ScaleIO represents a ScaleIO persistent volume + attached and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Default is "xfs". + type: string + gateway: + description: The host address of the ScaleIO API Gateway. + type: string + protectionDomain: + description: The name of the ScaleIO Protection Domain + for the configured storage. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef references to the secret for ScaleIO + user and other sensitive information. If this is not + provided, Login operation will fail. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + sslEnabled: + description: Flag to enable/disable SSL communication + with Gateway, default false + type: boolean + storageMode: + description: Indicates whether the storage for a volume + should be ThickProvisioned or ThinProvisioned. Default + is ThinProvisioned. + type: string + storagePool: + description: The ScaleIO Storage Pool associated with + the protection domain. + type: string + system: + description: The name of the storage system as configured + in ScaleIO. + type: string + volumeName: + description: The name of a volume already created in the + ScaleIO system that is associated with this volume source. + type: string + required: + - gateway + - secretRef + - system + type: object + secret: + description: 'Secret represents a secret that should populate + this volume. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + properties: + defaultMode: + description: 'Optional: mode bits to use on created files + by default. Must be a value between 0 and 0777. Defaults + to 0644. Directories within the path are not affected + by this setting. This might be in conflict with other + options that affect the file mode, like fsGroup, and + the result can be other mode bits set.' + format: int32 + type: integer + items: + description: If unspecified, each key-value pair in the + Data field of the referenced Secret will be projected + into the volume as a file whose name is the key and + content is the value. If specified, the listed keys + will be projected into the specified paths, and unlisted + keys will not be present. If a key is specified which + is not present in the Secret, the volume setup will + error unless it is marked optional. Paths must be relative + and may not contain the '..' path or start with '..'. + items: + description: Maps a string key to a path within a volume. + properties: + key: + description: The key to project. + type: string + mode: + description: 'Optional: mode bits to use on this + file, must be a value between 0 and 0777. If not + specified, the volume defaultMode will be used. + This might be in conflict with other options that + affect the file mode, like fsGroup, and the result + can be other mode bits set.' + format: int32 + type: integer + path: + description: The relative path of the file to map + the key to. May not be an absolute path. May not + contain the path element '..'. May not start with + the string '..'. + type: string + required: + - key + - path + type: object + type: array + optional: + description: Specify whether the Secret or its keys must + be defined + type: boolean + secretName: + description: 'Name of the secret in the pod''s namespace + to use. More info: https://kubernetes.io/docs/concepts/storage/volumes#secret' + type: string + type: object + storageos: + description: StorageOS represents a StorageOS volume attached + and mounted on Kubernetes nodes. + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + readOnly: + description: Defaults to false (read/write). ReadOnly + here will force the ReadOnly setting in VolumeMounts. + type: boolean + secretRef: + description: SecretRef specifies the secret to use for + obtaining the StorageOS API credentials. If not specified, + default values will be attempted. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + volumeName: + description: VolumeName is the human-readable name of + the StorageOS volume. Volume names are only unique + within a namespace. + type: string + volumeNamespace: + description: VolumeNamespace specifies the scope of the + volume within StorageOS. If no namespace is specified + then the Pod's namespace will be used. This allows + the Kubernetes name scoping to be mirrored within StorageOS + for tighter integration. Set VolumeName to any name + to override the default behaviour. Set to "default" + if you are not using namespaces within StorageOS. Namespaces + that do not pre-exist within StorageOS will be created. + type: string + type: object + vsphereVolume: + description: VsphereVolume represents a vSphere volume attached + and mounted on kubelets host machine + properties: + fsType: + description: Filesystem type to mount. Must be a filesystem + type supported by the host operating system. Ex. "ext4", + "xfs", "ntfs". Implicitly inferred to be "ext4" if unspecified. + type: string + storagePolicyID: + description: Storage Policy Based Management (SPBM) profile + ID associated with the StoragePolicyName. + type: string + storagePolicyName: + description: Storage Policy Based Management (SPBM) profile + name. + type: string + volumePath: + description: Path that identifies vSphere volume vmdk + type: string + required: + - volumePath + type: object + required: + - name + type: object + type: array + required: + - disableCSRFProtection + type: object + notifications: + description: Notifications defines list of a services which are used + to inform about Jenkins status Can be used to integrate chat services + like Slack, Microsoft Teams or Mailgun + items: + description: Notification is a service configuration used to send + notifications about Jenkins status + properties: + level: + description: NotificationLevel defines the level of a Notification + type: string + mailgun: + description: Mailgun is handler for Mailgun email service notification + channel + properties: + apiKeySecretKeySelector: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + domain: + type: string + from: + type: string + recipient: + type: string + required: + - apiKeySecretKeySelector + - domain + - from + - recipient + type: object + name: + type: string + slack: + description: Slack is handler for Slack notification channel + properties: + webHookURLSecretKeySelector: + description: The web hook URL to Slack App + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + required: + - webHookURLSecretKeySelector + type: object + smtp: + description: SMTP is handler for sending emails via this protocol + properties: + from: + type: string + passwordSecretKeySelector: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + port: + type: integer + server: + type: string + tlsInsecureSkipVerify: + type: boolean + to: + type: string + usernameSecretKeySelector: + description: SecretKeySelector selects a key of a Secret. + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + required: + - from + - passwordSecretKeySelector + - port + - server + - to + - usernameSecretKeySelector + type: object + teams: + description: MicrosoftTeams is handler for Microsoft MicrosoftTeams + notification channel + properties: + webHookURLSecretKeySelector: + description: The web hook URL to MicrosoftTeams App + properties: + key: + description: The key of the secret to select from. Must + be a valid secret key. + type: string + secret: + description: The name of the secret in the pod's namespace + to select from. + properties: + name: + description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + TODO: Add other useful fields. apiVersion, kind, + uid?' + type: string + type: object + required: + - key + - secret + type: object + required: + - webHookURLSecretKeySelector + type: object + verbose: + type: boolean + required: + - level + - name + - verbose + type: object + type: array + restore: + description: 'Backup defines configuration of Jenkins backup restore + More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-backup-and-restore' + properties: + action: + description: Action defines action which performs restore backup + in restore container sidecar + properties: + exec: + description: Exec specifies the action to take. + properties: + command: + description: Command is the command line to execute inside + the container, the working directory for the command is + root ('/') in the container's filesystem. The command + is simply exec'd, it is not run inside a shell, so traditional + shell instructions ('|', etc) won't work. To use a shell, + you need to explicitly call out to that shell. Exit status + of 0 is treated as live/healthy and non-zero is unhealthy. + items: + type: string + type: array + type: object + type: object + containerName: + description: ContainerName is the container name responsible for + restore backup operation + type: string + recoveryOnce: + description: RecoveryOnce if want to restore specific backup set + this field and then Jenkins will be restarted and desired backup + will be restored + format: int64 + type: integer + required: + - action + - containerName + type: object + roles: + description: Roles defines list of extra RBAC roles for the Jenkins + Master pod service account + items: + description: RoleRef contains information that points to the role + being used + properties: + apiGroup: + description: APIGroup is the group for the resource being referenced + type: string + kind: + description: Kind is the type of resource being referenced + type: string + name: + description: Name is the name of resource being referenced + type: string + required: + - apiGroup + - kind + - name + type: object + type: array + seedJobs: + description: 'SeedJobs defines list of Jenkins Seed Job configurations + More info: https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines' + items: + description: 'SeedJob defines configuration for seed job More info: + https://github.com/jenkinsci/kubernetes-operator/blob/master/docs/getting-started.md#configure-seed-jobs-and-pipelines' + properties: + additionalClasspath: + description: AdditionalClasspath is setting for Job DSL API plugin + to set Additional Classpath + type: string + bitbucketPushTrigger: + description: BitbucketPushTrigger is used for Bitbucket web hooks + type: boolean + buildPeriodically: + description: BuildPeriodically is setting for scheduled trigger + type: string + credentialID: + description: CredentialID is the Kubernetes secret name which + stores repository access credentials + type: string + credentialType: + description: JenkinsCredentialType is the https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ + credential type + type: string + description: + description: Description is the description of the seed job + type: string + failOnMissingPlugin: + description: FailOnMissingPlugin is setting for Job DSL API plugin + that fails job if required plugin is missing + type: boolean + githubPushTrigger: + description: GitHubPushTrigger is used for GitHub web hooks + type: boolean + id: + description: ID is the unique seed job name + type: string + ignoreMissingFiles: + description: IgnoreMissingFiles is setting for Job DSL API plugin + to ignore files that miss + type: boolean + pollSCM: + description: PollSCM is setting for polling changes in SCM + type: string + repositoryBranch: + description: RepositoryBranch is the repository branch where are + seed job definitions + type: string + repositoryUrl: + description: RepositoryURL is the repository access URL. Can be + SSH or HTTPS. + type: string + targets: + description: Targets is the repository path where are seed job + definitions + type: string + unstableOnDeprecation: + description: UnstableOnDeprecation is setting for Job DSL API + plugin that sets build status as unstable if build using deprecated + features + type: boolean + type: object + type: array + service: + description: 'Service is Kubernetes service of Jenkins master HTTP pod + Defaults to : port: 8080 type: ClusterIP' + 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 + labels: + additionalProperties: + type: string + description: 'Route service traffic to pods with label keys and + values matching this selector. If empty or not present, the service + is assumed to have an external process managing its endpoints, + which Kubernetes will not modify. Only applies to types ClusterIP, + NodePort, and LoadBalancer. Ignored if type is ExternalName. More + info: https://kubernetes.io/docs/concepts/services-networking/service/' + type: object + loadBalancerIP: + description: 'Only applies to Service Type: LoadBalancer LoadBalancer + will get created with the IP specified in this field. This feature + depends on whether the underlying cloud-provider supports specifying + the loadBalancerIP when a load balancer is created. This field + will be ignored if the cloud-provider does not support the feature.' + type: string + loadBalancerSourceRanges: + description: 'If specified and supported by the platform, this will + restrict traffic through the cloud-provider load-balancer will + be restricted to the specified client IPs. This field will be + ignored if the cloud-provider does not support the feature." More + info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + items: + type: string + type: array + nodePort: + description: 'The port on each node on which this service is exposed + when type=NodePort or LoadBalancer. Usually assigned by the system. + If specified, it will be allocated to the service if unused or + else creation of the service will fail. Default is to auto-allocate + a port if the ServiceType of this Service requires one. More info: + https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + format: int32 + type: integer + port: + description: 'The port that are exposed by this service. More info: + https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' + format: int32 + type: integer + type: + description: 'Type determines how the Service is exposed. Defaults + to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, + and LoadBalancer. "ExternalName" maps to the specified externalName. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if that + is not specified, by manual construction of an Endpoints object. + If clusterIP is "None", no virtual IP is allocated and the endpoints + are published as a set of endpoints rather than a stable IP. "NodePort" + builds on ClusterIP and allocates a port on every node which routes + to the clusterIP. "LoadBalancer" builds on NodePort and creates + an external load-balancer (if supported in the current cloud) + which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' + type: string + type: object + serviceAccount: + description: ServiceAccount defines Jenkins master service account attributes + 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 + type: object + slaveService: + description: 'Service is Kubernetes service of Jenkins slave pods Defaults + to : port: 50000 type: ClusterIP' + 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 + labels: + additionalProperties: + type: string + description: 'Route service traffic to pods with label keys and + values matching this selector. If empty or not present, the service + is assumed to have an external process managing its endpoints, + which Kubernetes will not modify. Only applies to types ClusterIP, + NodePort, and LoadBalancer. Ignored if type is ExternalName. More + info: https://kubernetes.io/docs/concepts/services-networking/service/' + type: object + loadBalancerIP: + description: 'Only applies to Service Type: LoadBalancer LoadBalancer + will get created with the IP specified in this field. This feature + depends on whether the underlying cloud-provider supports specifying + the loadBalancerIP when a load balancer is created. This field + will be ignored if the cloud-provider does not support the feature.' + type: string + loadBalancerSourceRanges: + description: 'If specified and supported by the platform, this will + restrict traffic through the cloud-provider load-balancer will + be restricted to the specified client IPs. This field will be + ignored if the cloud-provider does not support the feature." More + info: https://kubernetes.io/docs/tasks/access-application-cluster/configure-cloud-provider-firewall/' + items: + type: string + type: array + nodePort: + description: 'The port on each node on which this service is exposed + when type=NodePort or LoadBalancer. Usually assigned by the system. + If specified, it will be allocated to the service if unused or + else creation of the service will fail. Default is to auto-allocate + a port if the ServiceType of this Service requires one. More info: + https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport' + format: int32 + type: integer + port: + description: 'The port that are exposed by this service. More info: + https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies' + format: int32 + type: integer + type: + description: 'Type determines how the Service is exposed. Defaults + to ClusterIP. Valid options are ExternalName, ClusterIP, NodePort, + and LoadBalancer. "ExternalName" maps to the specified externalName. + "ClusterIP" allocates a cluster-internal IP address for load-balancing + to endpoints. Endpoints are determined by the selector or if that + is not specified, by manual construction of an Endpoints object. + If clusterIP is "None", no virtual IP is allocated and the endpoints + are published as a set of endpoints rather than a stable IP. "NodePort" + builds on ClusterIP and allocates a port on every node which routes + to the clusterIP. "LoadBalancer" builds on NodePort and creates + an external load-balancer (if supported in the current cloud) + which routes to the clusterIP. More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types' + type: string + type: object + required: + - jenkinsAPISettings + - master + type: object + status: + description: Status defines the observed state of Jenkins + properties: + appliedGroovyScripts: + description: AppliedGroovyScripts is a list with all applied groovy + scripts in Jenkins by the operator + items: + description: AppliedGroovyScript is the applied groovy script in Jenkins + by the operator + properties: + configurationType: + description: ConfigurationType is the name of the configuration + type(base-groovy, user-groovy, user-casc) + type: string + hash: + description: Hash is the hash of the groovy script and secrets + which it uses + type: string + name: + description: Name is the name of the groovy script + type: string + source: + description: Source is the name of source where is located groovy + script + type: string + required: + - configurationType + - hash + - name + - source + type: object + type: array + backupDoneBeforePodDeletion: + description: BackupDoneBeforePodDeletion tells if backup before pod + deletion has been made + type: boolean + baseConfigurationCompletedTime: + description: BaseConfigurationCompletedTime is a time when Jenkins base + configuration phase has been completed + format: date-time + type: string + createdSeedJobs: + description: CreatedSeedJobs contains list of seed job id already created + in Jenkins + items: + type: string + type: array + lastBackup: + description: LastBackup is the latest backup number + format: int64 + type: integer + operatorVersion: + description: OperatorVersion is the operator version which manages this + CR + type: string + pendingBackup: + description: PendingBackup is the pending backup number + format: int64 + type: integer + provisionStartTime: + description: ProvisionStartTime is a time when Jenkins master pod has + been created + format: date-time + type: string + restoredBackup: + description: RestoredBackup is the restored backup number after Jenkins + master pod restart + format: int64 + type: integer + userAndPasswordHash: + description: UserAndPasswordHash is a SHA256 hash made from user and + password + type: string + userConfigurationCompletedTime: + description: UserConfigurationCompletedTime is a time when Jenkins user + configuration phase has been completed + format: date-time + type: string + type: object + type: object + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true diff --git a/deploy/crds/jenkins.io_jenkinsimages_crd.yaml b/deploy/crds/jenkins.io_jenkinsimages_crd.yaml new file mode 100644 index 00000000..acf4f081 --- /dev/null +++ b/deploy/crds/jenkins.io_jenkinsimages_crd.yaml @@ -0,0 +1,85 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: jenkinsimages.jenkins.io +spec: + group: jenkins.io + names: + kind: JenkinsImage + listKind: JenkinsImageList + plural: jenkinsimages + singular: jenkinsimage + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: JenkinsImageSpec defines the desired state of JenkinsImage + properties: + image: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + plugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + required: + - image + - plugins + type: object + status: + description: JenkinsImageStatus defines the observed state of JenkinsImage + properties: + image: + type: string + installedPlugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + md5sum: + type: string + type: object + type: object + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true diff --git a/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml b/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml new file mode 100644 index 00000000..582c8977 --- /dev/null +++ b/deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml @@ -0,0 +1,15 @@ +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + master: + containers: + - name: jenkins-master + image: jenkins/jenkins:2.263.2-lts-alpine + seedJobs: + - id: jenkins-operator + targets: "cicd/jobs/*.jenkins" + description: "Jenkins Operator repository" + repositoryBranch: master + repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git diff --git a/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml b/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml new file mode 100644 index 00000000..8206d650 --- /dev/null +++ b/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml @@ -0,0 +1,106 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: jenkins.jenkins.io +spec: + group: jenkins.io + names: + kind: Jenkins + listKind: JenkinsList + plural: jenkins + singular: jenkins + scope: Namespaced + versions: + - name : v1alpha2 + served: true + storage: true + - name : v1alpha1 + served: true + storage: false +--- +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: jenkinsimages.jenkins.io +spec: + group: jenkins.io + names: + kind: JenkinsImage + listKind: JenkinsImageList + plural: jenkinsimages + singular: jenkinsimage + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: JenkinsImageSpec defines the desired state of JenkinsImage + properties: + image: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + plugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + required: + - image + - plugins + type: object + status: + description: JenkinsImageStatus defines the observed state of JenkinsImage + properties: + image: + type: string + installedPlugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + md5sum: + type: string + type: object + type: object + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true + diff --git a/deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml b/deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml new file mode 100644 index 00000000..cda0a0fc --- /dev/null +++ b/deploy/crds/jenkins_v1alpha2_jenkinsimage_cr.yaml @@ -0,0 +1,24 @@ +apiVersion: jenkins.io/v1alpha2 +kind: JenkinsImage +metadata: + name: simple-jenkinsimage +spec: + image: + name: jenkins/jenkins + tag: 2.263.1-lts-alpine + plugins: + - name: kubernetes + version: "1.28.6" + - name: workflow-job + version: "2.40" + - name: workflow-aggregator + version: "2.6" + - name: git + version: "4.5.0" + - name: job-dsl + version: "1.77" + - name: configuration-as-code + version: "1.46" + - name: kubernetes-credentials-provider + version: "0.15" + diff --git a/deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml b/deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml new file mode 100644 index 00000000..acf4f081 --- /dev/null +++ b/deploy/crds/jenkins_v1alpha2_jenkinsimage_crd.yaml @@ -0,0 +1,85 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: jenkinsimages.jenkins.io +spec: + group: jenkins.io + names: + kind: JenkinsImage + listKind: JenkinsImageList + plural: jenkinsimages + singular: jenkinsimage + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + description: JenkinsImage is the Schema for the jenkinsimages 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/sig-architecture/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/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: JenkinsImageSpec defines the desired state of JenkinsImage + properties: + image: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + plugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + required: + - image + - plugins + type: object + status: + description: JenkinsImageStatus defines the observed state of JenkinsImage + properties: + image: + type: string + installedPlugins: + items: + description: Defines Jenkins Plugin structure + properties: + name: + type: string + version: + type: string + required: + - name + type: object + type: array + md5sum: + type: string + type: object + type: object + version: v1alpha2 + versions: + - name: v1alpha2 + served: true + storage: true diff --git a/deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml b/deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml new file mode 100644 index 00000000..9942813a --- /dev/null +++ b/deploy/crds/openshift_jenkins_v1alpha2_jenkins_cr.yaml @@ -0,0 +1,73 @@ +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + annotations: + jenkins.io/openshift-mode: 'true' + name: jenkins +spec: + master: + containers: + - name: jenkins-master + command: + - /usr/bin/go-init + - '-main' + - /usr/libexec/s2i/run + env: + - name: OPENSHIFT_ENABLE_OAUTH + value: 'true' + - name: OPENSHIFT_ENABLE_REDIRECT_PROMPT + value: 'true' + - name: DISABLE_ADMINISTRATIVE_MONITORS + value: 'false' + - name: KUBERNETES_MASTER + value: 'https://kubernetes.default:443' + - name: KUBERNETES_TRUST_CERTIFICATES + value: 'true' + - name: JENKINS_SERVICE_NAME + value: jenkins-operator-http-jenkins + - name: JNLP_SERVICE_NAME + value: jenkins-operator-slave-jenkins + - name: JENKINS_UC_INSECURE + value: 'false' + - name: JENKINS_HOME + value: /var/lib/jenkins + - name: JAVA_OPTS + value: >- + -XX:+UnlockExperimentalVMOptions -XX:+UnlockExperimentalVMOptions + -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 + -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true + image: 'quay.io/openshift/origin-jenkins:latest' + imagePullPolicy: Always + livenessProbe: + httpGet: + path: /login + port: 8080 + scheme: HTTP + initialDelaySeconds: 420 + periodSeconds: 360 + timeoutSeconds: 240 + readinessProbe: + httpGet: + path: /login + port: 8080 + scheme: HTTP + initialDelaySeconds: 3 + periodSeconds: 0 + timeoutSeconds: 240 + resources: + limits: + cpu: 600m + memory: 4Gi + requests: + cpu: 500m + memory: 3Gi + service: + port: 8080 + type: ClusterIP + slaveService: + port: 50000 + type: ClusterIP + serviceAccount: + annotations: + serviceaccounts.openshift.io/oauth-redirectreference.jenkins: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"jenkins-operator"}}' + diff --git a/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml b/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml new file mode 100644 index 00000000..de2d8c6d --- /dev/null +++ b/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins-operator.v0.2.2.clusterserviceversion.yaml @@ -0,0 +1,244 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: >- + [{"apiVersion":"jenkins.io/v1alpha2","kind":"Jenkins","metadata":{"name":"example"},"spec":{"master":{"containers":[{"name":"jenkins-master","image":"jenkins/jenkins:lts","imagePullPolicy":"Always","livenessProbe":{"failureThreshold":12,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":80,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":5},"readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":30,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{"cpu":"1500m","memory":"3Gi"},"requests":{"cpu":"1","memory":"500Mi"}}}]},"seedJobs":[{"id":"jenkins-operator","targets":"cicd/jobs/*.jenkins","description":"Jenkins + Operator + repository","repositoryBranch":"master","repositoryUrl":"https://github.com/jenkinsci/kubernetes-operator.git"}]}}] + categories: Integration & Delivery + certified: 'false' + description: >- + Kubernetes native operator which fully manages Jenkins on Kubernetes. + containerImage: 'virtuslab/jenkins-operator:v0.2.2' + support: 'VirtusLab' + capabilities: Basic Install + repository: 'https://github.com/jenkinsci/kubernetes-operator' + name: jenkins-operator.v0.2.2 + namespace: placeholder +spec: + apiservicedefinitions: {} + displayName: Jenkins Operator + description: >+ + ##What's the Jenkins Operator? + + Jenkins operator is a Kubernetes native operator which fully manages Jenkins on Kubernetes. It was built with immutability and declarative configuration as code in mind. + + + Out of the box it provides: + + + integration with Kubernetes + + pipelines as code + + extensibility via groovy scripts or configuration as code plugin + + security and hardening + + Problem statement and goals + + The main reason why we decided to implement the Jenkins Operator is the fact that we faced a lot of problems with standard Jenkins deployment. We want to make Jenkins more robust, suitable for dynamic and multi-tenant environments. + + + Some of the problems we want to solve: + + installing plugins with incompatible versions or security vulnerabilities + + better configuration as code + + lack of end to end tests + + handle graceful shutdown properly + + security and hardening out of the box + + orphaned jobs with no jnlp connection + + make errors more visible for end users + + backup and restore for jobs history + + + version: 0.2.2 + minKubeVersion: 1.11.0 + maturity: alpha + keywords: + - jenkins + - operator + - CI/CD + maintainers: + - name: Tomasz Sęk + email: tomasz.sek.88@gmail.com + - name: Jakub Al-Khalili + email: jal-khalili@virtuslab.com + provider: + name: VirtusLab + labels: {} + selector: + matchLabels: {} + links: + - name: GitHub + url: 'https://github.com/jenkinsci/kubernetes-operator' + - name: Website + url: 'https://jenkinsci.github.io/kubernetes-operator/' + icon: + - base64data: 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 + mediatype: image/png + customresourcedefinitions: + owned: + - name: jenkins.jenkins.io + displayName: Jenkins + kind: Jenkins + version: v1alpha2 + description: Jenkins + resources: + - version: v1 + kind: Deployment + - version: v1 + kind: Service + - version: v1 + kind: ReplicaSet + - version: v1 + kind: Pod + - version: v1 + kind: Secret + - version: v1 + kind: ConfigMap + specDescriptors: [] + statusDescriptors: [] + required: [] + install: + spec: + deployments: + - name: jenkins-operator + spec: + replicas: 1 + selector: + matchLabels: + name: jenkins-operator + strategy: {} + template: + metadata: + labels: + name: jenkins-operator + spec: + containers: + - command: + - jenkins-operator + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: jenkins-operator + image: virtuslab/jenkins-operator:v0.2.2 + imagePullPolicy: IfNotPresent + name: jenkins-operator + resources: {} + serviceAccountName: jenkins-operator + permissions: + - rules: + - apiGroups: + - "" + resources: + - services + - configmaps + - secrets + verbs: + - get + - create + - update + - list + - watch + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - create + - apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - create + - update + - apiGroups: + - "" + resources: + - pods/portforward + verbs: + - create + - apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - '*' + - apiGroups: + - "" + resources: + - events + verbs: + - watch + - list + - create + - patch + - apiGroups: + - apps + resourceNames: + - jenkins-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - jenkins.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - get + - list + - watch + serviceAccountName: jenkins-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: false + type: AllNamespaces diff --git a/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml b/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml new file mode 100644 index 00000000..b74dcc75 --- /dev/null +++ b/deploy/olm-catalog/jenkins-operator/0.2.2/jenkins_v1alpha2_jenkins_crd.yaml @@ -0,0 +1,20 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: jenkins.jenkins.io +spec: + group: jenkins.io + names: + kind: Jenkins + listKind: JenkinsList + plural: jenkins + singular: jenkins + scope: Namespaced + version: v1alpha2 + versions: + - name : v1alpha2 + served: true + storage: true + - name : v1alpha1 + served: true + storage: false diff --git a/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml b/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml new file mode 100644 index 00000000..4853d9c4 --- /dev/null +++ b/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins-operator.v0.3.0.clusterserviceversion.yaml @@ -0,0 +1,226 @@ +apiVersion: operators.coreos.com/v1alpha1 +kind: ClusterServiceVersion +metadata: + annotations: + alm-examples: '[{"apiVersion":"jenkins.io/v1alpha2","kind":"Jenkins","metadata":{"name":"example"},"spec":{"master":{"containers":[{"image":"jenkins/jenkins:lts","imagePullPolicy":"Always","livenessProbe":{"failureThreshold":12,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":80,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":5},"name":"jenkins-master","readinessProbe":{"failureThreshold":3,"httpGet":{"path":"/login","port":"http","scheme":"HTTP"},"initialDelaySeconds":30,"periodSeconds":10,"successThreshold":1,"timeoutSeconds":1},"resources":{"limits":{"cpu":"1500m","memory":"3Gi"},"requests":{"cpu":"1","memory":"500Mi"}}}]},"seedJobs":[{"description":"Jenkins + Operator repository","id":"jenkins-operator","repositoryBranch":"master","repositoryUrl":"https://github.com/jenkinsci/kubernetes-operator.git","targets":"cicd/jobs/*.jenkins"}]}},{"apiVersion":"jenkins.io/v1alpha2","kind":"Jenkins","metadata":{"name":"jenkins"},"spec":{"master":{"containers":[{"image":"quay.io/openshift/origin-jenkins:latest","name":"jenkins-master","resources":{"limits":{"cpu":"1500m","memory":"3Gi"},"requests":{"cpu":"1","memory":"500Mi"}}}]}}}]' + capabilities: Basic Install + categories: Integration & Delivery + certified: "false" + containerImage: virtuslab/jenkins-operator:v0.3.0 + description: Kubernetes native operator which fully manages Jenkins on Kubernetes. + repository: https://github.com/jenkinsci/kubernetes-operator + support: VirtusLab + name: jenkins-operator.v0.3.0 + namespace: placeholder +spec: + apiservicedefinitions: {} + customresourcedefinitions: + owned: + - description: Jenkins + displayName: Jenkins + kind: Jenkins + name: jenkins.jenkins.io + resources: + - kind: Deployment + name: "" + version: v1 + - kind: Service + name: "" + version: v1 + - kind: ReplicaSet + name: "" + version: v1 + - kind: Pod + name: "" + version: v1 + - kind: Secret + name: "" + version: v1 + - kind: ConfigMap + name: "" + version: v1 + version: v1alpha2 + description: |+ + ##What's the Jenkins Operator? + Jenkins operator is a Kubernetes native operator which fully manages Jenkins on Kubernetes. It was built with immutability and declarative configuration as code in mind. + + Out of the box it provides: + + integration with Kubernetes + pipelines as code + extensibility via groovy scripts or configuration as code plugin + security and hardening + Problem statement and goals + The main reason why we decided to implement the Jenkins Operator is the fact that we faced a lot of problems with standard Jenkins deployment. We want to make Jenkins more robust, suitable for dynamic and multi-tenant environments. + + Some of the problems we want to solve: + installing plugins with incompatible versions or security vulnerabilities + better configuration as code + lack of end to end tests + handle graceful shutdown properly + security and hardening out of the box + orphaned jobs with no jnlp connection + make errors more visible for end users + backup and restore for jobs history + + + displayName: Jenkins Operator + icon: + - base64data: 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 + mediatype: image/png + install: + spec: + deployments: + - name: jenkins-operator + spec: + replicas: 1 + selector: + matchLabels: + name: jenkins-operator + strategy: {} + template: + metadata: + labels: + name: jenkins-operator + spec: + containers: + - command: + - jenkins-operator + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.annotations['olm.targetNamespaces'] + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: jenkins-operator + image: virtuslab/jenkins-operator:v0.3.0 + imagePullPolicy: IfNotPresent + name: jenkins-operator + resources: {} + serviceAccountName: jenkins-operator + permissions: + - rules: + - apiGroups: + - "" + resources: + - services + - configmaps + - secrets + verbs: + - get + - create + - update + - list + - watch + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - create + - apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - create + - update + - apiGroups: + - "" + resources: + - pods/portforward + verbs: + - create + - apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - '*' + - apiGroups: + - "" + resources: + - events + verbs: + - watch + - list + - create + - patch + - apiGroups: + - apps + resourceNames: + - jenkins-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - jenkins.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - get + - list + - watch + serviceAccountName: jenkins-operator + strategy: deployment + installModes: + - supported: true + type: OwnNamespace + - supported: true + type: SingleNamespace + - supported: false + type: MultiNamespace + - supported: false + type: AllNamespaces + keywords: + - jenkins + - operator + - CI/CD + links: + - name: GitHub + url: https://github.com/jenkinsci/kubernetes-operator + - name: Website + url: https://jenkinsci.github.io/kubernetes-operator/ + maintainers: + - email: tomasz.sek.88@gmail.com + name: Tomasz Sęk + - email: jal-khalili@virtuslab.com + name: Jakub Al-Khalili + maturity: alpha + minKubeVersion: 1.11.0 + provider: + name: VirtusLab + replaces: jenkins-operator.v0.2.2 + selector: {} + version: 0.3.0 diff --git a/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml b/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml new file mode 100644 index 00000000..b74dcc75 --- /dev/null +++ b/deploy/olm-catalog/jenkins-operator/0.3.0/jenkins_v1alpha2_jenkins_crd.yaml @@ -0,0 +1,20 @@ +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: jenkins.jenkins.io +spec: + group: jenkins.io + names: + kind: Jenkins + listKind: JenkinsList + plural: jenkins + singular: jenkins + scope: Namespaced + version: v1alpha2 + versions: + - name : v1alpha2 + served: true + storage: true + - name : v1alpha1 + served: true + storage: false diff --git a/deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml b/deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml new file mode 100644 index 00000000..69971831 --- /dev/null +++ b/deploy/olm-catalog/jenkins-operator/jenkins-operator.package.yaml @@ -0,0 +1,5 @@ +channels: +- currentCSV: jenkins-operator.v0.3.0 + name: alpha +defaultChannel: alpha +packageName: jenkins-operator diff --git a/deploy/operator.yaml b/deploy/operator.yaml new file mode 100644 index 00000000..95ba9a28 --- /dev/null +++ b/deploy/operator.yaml @@ -0,0 +1,34 @@ +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: jenkins-operator +spec: + replicas: 1 + selector: + matchLabels: + name: jenkins-operator + template: + metadata: + labels: + name: jenkins-operator + spec: + serviceAccountName: jenkins-operator + containers: + - name: jenkins-operator + image: virtuslab/jenkins-operator:v0.5.0 + command: + - jenkins-operator + args: [] + imagePullPolicy: IfNotPresent + env: + - name: WATCH_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: OPERATOR_NAME + value: "jenkins-operator" diff --git a/deploy/role.yaml b/deploy/role.yaml new file mode 100644 index 00000000..a2653abc --- /dev/null +++ b/deploy/role.yaml @@ -0,0 +1,117 @@ +--- +kind: Role +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: jenkins-operator +rules: + - apiGroups: + - "" + resources: + - services + - configmaps + - secrets + - serviceaccounts + verbs: + - get + - create + - update + - list + - watch + - apiGroups: + - apps + resources: + - deployments + - daemonsets + - replicasets + - statefulsets + verbs: + - '*' + - apiGroups: + - rbac.authorization.k8s.io + resources: + - roles + - rolebindings + verbs: + - create + - update + - list + - watch + - apiGroups: + - "" + resources: + - pods/portforward + verbs: + - create + - apiGroups: + - "" + resources: + - pods/log + verbs: + - get + - list + - watch + - apiGroups: + - "" + resources: + - pods + - pods/exec + verbs: + - "*" + - apiGroups: + - "" + resources: + - events + verbs: + - watch + - list + - create + - patch + - apiGroups: + - apps + resourceNames: + - jenkins-operator + resources: + - deployments/finalizers + verbs: + - update + - apiGroups: + - jenkins.io + resources: + - '*' + verbs: + - '*' + - apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - get + - list + - watch + - apiGroups: + - "route.openshift.io" + resources: + - routes + verbs: + - get + - list + - watch + - create + - update + - apiGroups: + - "image.openshift.io" + resources: + - imagestreams + verbs: + - get + - list + - watch + - apiGroups: + - "build.openshift.io" + resources: + - builds + - buildconfigs + verbs: + - get + - list + - watch diff --git a/deploy/role_binding.yaml b/deploy/role_binding.yaml new file mode 100644 index 00000000..8224b7c1 --- /dev/null +++ b/deploy/role_binding.yaml @@ -0,0 +1,12 @@ +--- +kind: RoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: jenkins-operator +subjects: +- kind: ServiceAccount + name: jenkins-operator +roleRef: + kind: Role + name: jenkins-operator + apiGroup: rbac.authorization.k8s.io diff --git a/deploy/service_account.yaml b/deploy/service_account.yaml new file mode 100644 index 00000000..21b293ce --- /dev/null +++ b/deploy/service_account.yaml @@ -0,0 +1,5 @@ +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: jenkins-operator diff --git a/variables.mk b/variables.mk index 6ed1aef0..0c310b53 100644 --- a/variables.mk +++ b/variables.mk @@ -69,7 +69,8 @@ OPERATOR_ARGS ?= --jenkins-api-hostname=$(JENKINS_API_HOSTNAME) --jenkins-api-po .DEFAULT_GOAL := help -PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) +PLATFORM = $(shell echo $(UNAME_S) | tr A-Z a-z) +CPUS_NUMBER = 3 ##################### FROM OPERATOR SDK ######################## # Default bundle image tag diff --git a/website/content/en/docs/Developer Guide/Preview/_index.md b/website/content/en/docs/Developer Guide/Preview/_index.md new file mode 100644 index 00000000..595fe717 --- /dev/null +++ b/website/content/en/docs/Developer Guide/Preview/_index.md @@ -0,0 +1,301 @@ +--- +title: "Developer Guide" +linkTitle: "Developer Guide - Preview" +weight: 60 +date: 2020-02-09 +description: > + Jenkins Operator for developers +--- + +{{% pageinfo %}} +This document explains how to setup your development environment. +{{% /pageinfo %}} + +## Prerequisites + +- [operator_sdk][operator_sdk] version 1.3.0 +- [git][git_tool] +- [go][go_tool] version 1.15.6 +- [goimports, golint, checkmake and staticcheck][install_dev_tools] +- [minikube][minikube] version 1.17.1 (preferred Hypervisor - [virtualbox][virtualbox]) (automatically downloaded) +- [docker][docker_tool] version 17.03+ + +## Clone repository and download dependencies + +```bash +git clone git@github.com:jenkinsci/kubernetes-operator.git +cd kubernetes-operator +make go-dependencies +``` + +## Build and run with a minikube + +Start minikube instance configured for **Jenkins Operator**. Appropriate minikube version will be downloaded to bin folder. +```bash +make minikube-start +``` +Next run **Jenkins Operator** locally. +```bash +make run +``` +Console output indicating readiness of this phase: +```bash ++ build ++ run +kubectl config use-context minikube +Switched to context "minikube". +Watching 'default' namespace +bin/manager --jenkins-api-hostname=192.168.99.252 --jenkins-api-port=0 --jenkins-api-use-nodeport=true --cluster-domain=cluster.local +2021-02-08T14:14:45.263+0100 INFO cmd Version: v0.5.0 +2021-02-08T14:14:45.263+0100 INFO cmd Git commit: 305dbeda-dirty-dirty +2021-02-08T14:14:45.264+0100 INFO cmd Go Version: go1.15.6 +2021-02-08T14:14:45.264+0100 INFO cmd Go OS/Arch: darwin/amd64 +2021-02-08T14:14:45.264+0100 INFO cmd Watch namespace: default +2021-02-08T14:14:45.592+0100 INFO controller-runtime.metrics metrics server is starting to listen {"addr": "0.0.0.0:8383"} +2021-02-08T14:14:45.599+0100 INFO cmd starting manager +2021-02-08T14:14:45.599+0100 INFO controller-runtime.manager starting metrics server {"path": "/metrics"} +2021-02-08T14:14:45.599+0100 INFO controller-runtime.manager.controller.jenkins Starting EventSource {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "source": "kind source: jenkins.io/v1alpha2, Kind=Jenkins"} +2021-02-08T14:14:45.700+0100 INFO controller-runtime.manager.controller.jenkins Starting EventSource {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "source": "kind source: /, Kind="} +2021-02-08T14:14:45.800+0100 INFO controller-runtime.manager.controller.jenkins Starting EventSource {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "source": "kind source: /, Kind="} +2021-02-08T14:14:45.901+0100 INFO controller-runtime.manager.controller.jenkins Starting EventSource {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "source": "kind source: /, Kind="} +2021-02-08T14:14:46.003+0100 INFO controller-runtime.manager.controller.jenkins Starting EventSource {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "source": "kind source: core/v1, Kind=Secret"} +2021-02-08T14:14:46.004+0100 INFO controller-runtime.manager.controller.jenkins Starting EventSource {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "source": "kind source: core/v1, Kind=ConfigMap"} +2021-02-08T14:14:46.004+0100 INFO controller-runtime.manager.controller.jenkins Starting EventSource {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "source": "kind source: jenkins.io/v1alpha2, Kind=Jenkins"} +2021-02-08T14:14:46.004+0100 INFO controller-runtime.manager.controller.jenkins Starting Controller {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins"} +2021-02-08T14:14:46.004+0100 INFO controller-runtime.manager.controller.jenkins Starting workers {"reconciler group": "jenkins.io", "reconciler kind": "Jenkins", "worker count": 1} + +``` +Lastly apply Jenkins Custom Resource to minikube cluster: +```bash +kubectl apply -f config/samples/jenkins.io_v1alpha2_jenkins.yaml + +{"level":"info","ts":1612790690.875426,"logger":"controller-jenkins","msg":"Setting default Jenkins container command","cr":"jenkins-example"} +{"level":"info","ts":1612790690.8754492,"logger":"controller-jenkins","msg":"Setting default Jenkins container JAVA_OPTS environment variable","cr":"jenkins-example"} +{"level":"info","ts":1612790690.875456,"logger":"controller-jenkins","msg":"Setting default operator plugins","cr":"jenkins-example"} +{"level":"info","ts":1612790690.875463,"logger":"controller-jenkins","msg":"Setting default Jenkins master service","cr":"jenkins-example"} +{"level":"info","ts":1612790690.875467,"logger":"controller-jenkins","msg":"Setting default Jenkins slave service","cr":"jenkins-example"} +{"level":"info","ts":1612790690.881811,"logger":"controller-jenkins","msg":"*v1alpha2.Jenkins/jenkins-example has been updated","cr":"jenkins-example"} +{"level":"info","ts":1612790691.252834,"logger":"controller-jenkins","msg":"Creating a new Jenkins Master Pod default/jenkins-jenkins-example","cr":"jenkins-example"} +{"level":"info","ts":1612790691.322793,"logger":"controller-jenkins","msg":"Jenkins master pod restarted by operator:","cr":"jenkins-example"} +{"level":"info","ts":1612790691.322817,"logger":"controller-jenkins","msg":"Jenkins Operator version has changed, actual '' new 'v0.5.0'","cr":"jenkins-example"} +{"level":"info","ts":1612790691.3228202,"logger":"controller-jenkins","msg":"Jenkins CR has been replaced","cr":"jenkins-example"} +{"level":"info","ts":1612790695.8789551,"logger":"controller-jenkins","msg":"Creating a new Jenkins Master Pod default/jenkins-jenkins-example","cr":"jenkins-example"} +{"level":"warn","ts":1612790817.9423082,"logger":"controller-jenkins","msg":"Reconcile loop failed: couldn't init Jenkins API client: Get \"http://192.168.99.254:31998/api/json\": dial tcp 192.168.99.254:31998: connect: connection refused","cr":"jenkins-example"} +{"level":"warn","ts":1612790817.9998221,"logger":"controller-jenkins","msg":"Reconcile loop failed: couldn't init Jenkins API client: Get \"http://192.168.99.254:31998/api/json\": dial tcp 192.168.99.254:31998: connect: connection refused","cr":"jenkins-example"} +{"level":"info","ts":1612790818.581316,"logger":"controller-jenkins","msg":"base-groovy ConfigMap 'jenkins-operator-base-configuration-jenkins-example' name '1-basic-settings.groovy' running groovy script","cr":"jenkins-example"} +... +{"level":"info","ts":1612790820.9473379,"logger":"controller-jenkins","msg":"base-groovy ConfigMap 'jenkins-operator-base-configuration-jenkins-example' name '8-disable-job-dsl-script-approval.groovy' running groovy script","cr":"jenkins-example"} +{"level":"info","ts":1612790821.244055,"logger":"controller-jenkins","msg":"Base configuration phase is complete, took 2m6s","cr":"jenkins-example"} +{"level":"info","ts":1612790821.7953842,"logger":"controller-jenkins","msg":"Waiting for Seed Job Agent `seed-job-agent`...","cr":"jenkins-example"} +... + +{"level":"info","ts":1612790851.843638,"logger":"controller-jenkins","msg":"Waiting for Seed Job Agent `seed-job-agent`...","cr":"jenkins-example"} +{"level":"info","ts":1612790853.489524,"logger":"controller-jenkins","msg":"User configuration phase is complete, took 2m38s","cr":"jenkins-example"} + +Two log lines says that Jenkins Operator works correctly: + +* `Base configuration phase is complete` - ensures manifests, Jenkins pod, Jenkins configuration and Jenkins API token +* `User configuration phase is complete` - ensures Jenkins restore, backup and seed jobs along with user configuration + +> Details about base and user phase can be found [here](https://jenkinsci.github.io/kubernetes-operator/docs/how-it-works/architecture-and-design/). + + +```bash +kubectl get jenkins -o yaml + +apiVersion: v1 +items: +- apiVersion: jenkins.io/v1alpha2 + kind: Jenkins + metadata: + ... + spec: + backup: + action: {} + containerName: "" + interval: 0 + makeBackupBeforePodDeletion: false + configurationAsCode: + configurations: [] + secret: + name: "" + groovyScripts: + configurations: [] + secret: + name: "" + jenkinsAPISettings: + authorizationStrategy: createUser + master: + basePlugins: + ... + containers: + - command: + - bash + - -c + - /var/jenkins/scripts/init.sh && exec /sbin/tini -s -- /usr/local/bin/jenkins.sh + env: + - name: JAVA_OPTS + value: -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap + -XX:MaxRAMFraction=1 -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true + image: jenkins/jenkins:2.263.3-lts-alpine + imagePullPolicy: Always + livenessProbe: + ... + readinessProbe: + ... + resources: + limits: + cpu: 1500m + memory: 3Gi + requests: + cpu: "1" + memory: 500Mi + disableCSRFProtection: false + restore: + action: {} + containerName: "" + getLatestAction: {} + seedJobs: + - additionalClasspath: "" + bitbucketPushTrigger: false + buildPeriodically: "" + description: Jenkins Operator repository + failOnMissingPlugin: false + githubPushTrigger: false + id: jenkins-operator + ignoreMissingFiles: false + pollSCM: "" + repositoryBranch: master + repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git + targets: cicd/jobs/*.jenkins + unstableOnDeprecation: false + service: + port: 8080 + type: NodePort + serviceAccount: {} + slaveService: + port: 50000 + type: ClusterIP + status: + appliedGroovyScripts: + - configurationType: base-groovy + hash: 2ownqpRyBjQYmzTRttUx7axok3CKe2E45frI5iRwH0w= + name: 1-basic-settings.groovy + source: jenkins-operator-base-configuration-jenkins-example + ... + baseConfigurationCompletedTime: "2021-02-08T13:27:01Z" + createdSeedJobs: + - jenkins-operator + operatorVersion: v0.5.0 + provisionStartTime: "2021-02-08T13:24:55Z" + userAndPasswordHash: nnfZsWmFfAYlYyVYeKhWW2KB4L8mE61JUfetAsr9IMM= + userConfigurationCompletedTime: "2021-02-08T13:27:33Z" +kind: List +metadata: + resourceVersion: "" + selfLink: "" +``` + +```bash +kubectl get po + +NAME READY STATUS RESTARTS AGE +jenkins-jenkins-example 1/1 Running 0 23m +seed-job-agent-jenkins-example-758cc7cc5c-82hbl 1/1 Running 0 21m + +``` + +### Debug Jenkins Operator + +```bash +make run OPERATOR_EXTRA_ARGS="--debug" +``` + +## Build and run with a remote Kubernetes cluster + +You can also run the controller locally and make it listen to a remote Kubernetes server. + +```bash +make run NAMESPACE=default KUBECTL_CONTEXT=remote-k8s EXTRA_ARGS='--kubeconfig ~/.kube/config' +``` + +Once **Jenkins Operator** are up and running, apply Jenkins custom resource: + +```bash +kubectl --context remote-k8s --namespace default apply -f deploy/crds/jenkins_v1alpha2_jenkins_cr.yaml +kubectl --context remote-k8s --namespace default get jenkins -o yaml +kubectl --context remote-k8s --namespace default get po +``` + +## Testing + +Tests are written using [Ginkgo](https://onsi.github.io/ginkgo/) with [Gomega](https://onsi.github.io/gomega/). + +Run unit tests with go fmt, lint, statickcheck, vet: + +```bash +make verify +``` + +Run unit tests only: + +```bash +make test +``` + +### Running E2E tests + +Run e2e tests with minikube: + +```bash +make minikube-start +make e2e +``` + +Run the specific e2e test: + +```bash +make e2e E2E_TEST_SELECTOR='^TestConfiguration$' +``` + +### Building docker image on minikube + +To be able to work with the docker daemon on `minikube` machine run the following command before building an image: + +```bash +eval $(bin/minikube docker-env) +``` + +### When `api/v1alpha2/jenkins_types.go` has changed + +Run: + +```bash +make manifests +``` + +### Getting the Jenkins URL and basic credentials + +```bash +minikube service jenkins-operator-http- --url +kubectl get secret jenkins-operator-credentials- -o 'jsonpath={.data.user}' | base64 -d +kubectl get secret jenkins-operator-credentials- -o 'jsonpath={.data.password}' | base64 -d +``` + +[dep_tool]:https://golang.github.io/dep/docs/installation.html +[git_tool]:https://git-scm.com/downloads +[go_tool]:https://golang.org/dl/ +[operator_sdk]:https://github.com/operator-framework/operator-sdk +[fork_guide]:https://help.github.com/articles/fork-a-repo/ +[docker_tool]:https://docs.docker.com/install/ +[kubectl_tool]:https://kubernetes.io/docs/tasks/tools/install-kubectl/ +[minikube]:https://kubernetes.io/docs/tasks/tools/install-minikube/ +[virtualbox]:https://www.virtualbox.org/wiki/Downloads +[install_dev_tools]:https://jenkinsci.github.io/kubernetes-operator/docs/developer-guide/tools/ + +## Self-learning + +* [Tutorial: Deep Dive into the Operator Framework for... Melvin Hillsman, Michael Hrivnak, & Matt Dorn +](https://www.youtube.com/watch?v=8_DaCcRMp5I) + +* [Operator Framework Training By OpenShift](https://www.katacoda.com/openshift/courses/operatorframework) diff --git a/website/content/en/docs/Developer Guide/_index.md b/website/content/en/docs/Developer Guide/_index.md index 17a331ec..b7a594c5 100644 --- a/website/content/en/docs/Developer Guide/_index.md +++ b/website/content/en/docs/Developer Guide/_index.md @@ -152,6 +152,7 @@ items: restore: action: {} containerName: "" + getLatestAction: {} seedJobs: - additionalClasspath: "" bitbucketPushTrigger: false @@ -244,7 +245,6 @@ Run e2e tests with minikube: ```bash make minikube-start -eval $(minikube docker-env) make e2e ``` diff --git a/website/content/en/docs/Getting Started/Preview/_index.md b/website/content/en/docs/Getting Started/Preview/_index.md new file mode 100644 index 00000000..64cd1b66 --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/_index.md @@ -0,0 +1,18 @@ +--- +title: "Preview" +linkTitle: "Preview" +weight: 10 +date: 2021-01-18 +description: > + How to work with jenkins-operator to be released version +--- + +{{% pageinfo %}} +This document describes a getting started guide for **Jenkins Operator** currently in preview version and an additional configuration. +{{% /pageinfo %}} + +## First Steps + +Prepare your Kubernetes cluster and set up your `kubectl` access. + +Once you have running Kubernetes cluster you can focus on installing **Jenkins Operator** according to the [Installation](/kubernetes-operator/docs/installation/) guide. diff --git a/website/content/en/docs/Getting Started/Preview/aks.md b/website/content/en/docs/Getting Started/Preview/aks.md new file mode 100644 index 00000000..166643c9 --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/aks.md @@ -0,0 +1,24 @@ +--- +title: "AKS" +linkTitle: "AKS" +weight: 10 +date: 2021-01-18 +description: > + Additional configuration for Azure Kubernetes Service +--- + +Azure AKS managed Kubernetes service adds to every pod the following environment variables: + +```yaml +- name: KUBERNETES_PORT_443_TCP_ADDR + value: +- name: KUBERNETES_PORT + value: tcp:// +- name: KUBERNETES_PORT_443_TCP + value: tcp:// +- name: KUBERNETES_SERVICE_HOST + value: +``` + +The operator is aware of it and omits these environment variables when checking if a Jenkins pod environment has been changed. It prevents the +restart of a Jenkins pod over and over again. \ No newline at end of file diff --git a/website/content/en/docs/Getting Started/Preview/configuration.md b/website/content/en/docs/Getting Started/Preview/configuration.md new file mode 100644 index 00000000..19721edd --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/configuration.md @@ -0,0 +1,315 @@ +--- +title: "Configuration" +linkTitle: "Configuration" +weight: 2 +date: 2021-01-25 +description: > + How to configure Jenkins with Operator +--- + +## Configure Seed Jobs and Pipelines + +Jenkins operator uses [job-dsl][job-dsl] and [kubernetes-credentials-provider][kubernetes-credentials-provider] plugins for configuring jobs +and deploy keys. + +## Prepare job definitions and pipelines + +First you have to prepare pipelines and job definition in your GitHub repository using the following structure: + +``` +cicd/ +├── jobs +│   └── k8s.jenkins +└── pipelines + └── k8s.jenkins +``` + +**`cicd/jobs/k8s.jenkins`** is a job definition: + +``` +#!/usr/bin/env groovy + +pipelineJob('k8s-e2e') { + displayName('Kubernetes Plugin E2E Test') + + logRotator { + numToKeep(10) + daysToKeep(30) + } + + configure { project -> + project / 'properties' / 'org.jenkinsci.plugins.workflow.job.properties.DurabilityHintJobProperty' { + hint('PERFORMANCE_OPTIMIZED') + } + } + + definition { + cpsScm { + scm { + git { + remote { + url('https://github.com/jenkinsci/kubernetes-operator.git') + credentials('jenkins-operator') + } + branches('*/master') + } + } + scriptPath('cicd/pipelines/k8s.jenkins') + } + } +} +``` + +**`cicd/pipelines/k8s.jenkins`** is an actual Jenkins pipeline: + +``` +#!/usr/bin/env groovy + +def label = "k8s-${UUID.randomUUID().toString()}" +def home = "/home/jenkins" +def workspace = "${home}/workspace/build-jenkins-operator" +def workdir = "${workspace}/src/github.com/jenkinsci/kubernetes-operator/" + +podTemplate(label: label, + containers: [ + containerTemplate(name: 'alpine', image: 'alpine:3.11', ttyEnabled: true, command: 'cat'), + ], + ) { + node(label) { + stage('Run shell') { + container('alpine') { + sh 'echo "hello world"' + } + } + } +} +``` + +## Configure Seed Jobs + +Jenkins Seed Jobs are configured using `Jenkins.spec.seedJobs` section from your custom resource manifest: + +``` +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + seedJobs: + - id: jenkins-operator + targets: "cicd/jobs/*.jenkins" + description: "Jenkins Operator repository" + repositoryBranch: master + repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git +``` + +**Jenkins Operator** will automatically discover and configure all the seed jobs. + +You can verify if deploy keys were successfully configured in the Jenkins **Credentials** tab. + +![jenkins](/kubernetes-operator/img/jenkins-credentials.png) + +You can verify if your pipelines were successfully configured in the Jenkins Seed Job console output. + +![jenkins](/kubernetes-operator/img/jenkins-seed.png) + +If your GitHub repository is **private** you have to configure SSH or username/password authentication. + +### SSH authentication + +#### Generate SSH Keys + +There are two methods of SSH private key generation: + +```bash +$ openssl genrsa -out 2048 +``` + +or + +```bash +$ ssh-keygen -t rsa -b 2048 +$ ssh-keygen -p -f -m pem +``` + +Then copy content from generated file. + +#### Public key + +If you want to upload your public key to your Git server you need to extract it. + +If key was generated by `openssl` then you need to type this to extract public key: + +```bash +$ openssl rsa -in -pubout > .pub +``` + +If key was generated by `ssh-keygen` the public key content is located in .pub and there is no need to extract public key + +#### Configure SSH authentication + +Configure a seed job like this: + +``` +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + seedJobs: + - id: jenkins-operator-ssh + credentialType: basicSSHUserPrivateKey + credentialID: k8s-ssh + targets: "cicd/jobs/*.jenkins" + description: "Jenkins Operator repository" + repositoryBranch: master + repositoryUrl: ssh://git@github.com:jenkinsci/kubernetes-operator.git +``` + +and create a Kubernetes Secret (name of secret should be the same from `credentialID` field): + +``` +apiVersion: v1 +kind: Secret +metadata: + name: k8s-ssh + labels: + "jenkins.io/credentials-type": "basicSSHUserPrivateKey" + annotations: + "jenkins.io/credentials-description" : "ssh github.com:jenkinsci/kubernetes-operator" +stringData: + privateKey: | + -----BEGIN RSA PRIVATE KEY----- + MIIJKAIBAAKCAgEAxxDpleJjMCN5nusfW/AtBAZhx8UVVlhhhIKXvQ+dFODQIdzO + oDXybs1zVHWOj31zqbbJnsfsVZ9Uf3p9k6xpJ3WFY9b85WasqTDN1xmSd6swD4N8 + ... + username: github_user_name +``` + +### Username & password authentication + +Configure the seed job like: + +``` +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + seedJobs: + - id: jenkins-operator-user-pass + credentialType: usernamePassword + credentialID: k8s-user-pass + targets: "cicd/jobs/*.jenkins" + description: "Jenkins Operator repository" + repositoryBranch: master + repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git +``` + +and create a Kubernetes Secret (name of secret should be the same from `credentialID` field): + +``` +apiVersion: v1 +kind: Secret +metadata: + name: k8s-user-pass +stringData: + username: github_user_name + password: password_or_token +``` + +### External authentication +You can use `external` credential type if you want to configure authentication using Configuration As Code or Groovy Script. + +## HTTP Proxy for downloading plugins + +To use forwarding proxy with an operator to download plugins you need to add the following environment variable to Jenkins Custom Resource (CR), e.g.: + +```yaml +spec: + master: + containers: + - name: jenkins-master + env: + - name: CURL_OPTIONS + value: -L -x +``` + +In `CURL_OPTIONS` var you can set additional arguments to `curl` command. + +## Pulling Docker images from private repositories + +To pull a Docker Image from private repository you can use `imagePullSecrets`. + +Please follow the instructions on [creating a secret with a docker config](https://kubernetes.io/docs/concepts/containers/images/?origin_team=T42NTAGHM#creating-a-secret-with-a-docker-config). + +### Docker Hub Configuration +To use Docker Hub additional steps are required. + +Edit the previously created secret: +```bash +kubectl -n edit secret +``` + +The `.dockerconfigjson` key's value needs to be replaced with a modified version. + +After modifications, it needs to be encoded as a Base64 value before setting the `.dockerconfigjson` key. + +Example config file to modify and use: +``` +{ + "auths":{ + "https://index.docker.io/v1/":{ + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + }, + "auth.docker.io":{ + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + }, + "registry.docker.io":{ + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + }, + "docker.io":{ + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + }, + "https://registry-1.docker.io/v2/": { + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + }, + "registry-1.docker.io/v2/": { + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + }, + "registry-1.docker.io": { + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + }, + "https://registry-1.docker.io": { + "username":"user", + "password":"password", + "email":"yourdockeremail@gmail.com", + "auth":"base64 of string user:password" + } + } +} +``` + +[job-dsl]:https://github.com/jenkinsci/job-dsl-plugin +[kubernetes-credentials-provider]:https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ diff --git a/website/content/en/docs/Getting Started/Preview/configure-backup-and-restore.md b/website/content/en/docs/Getting Started/Preview/configure-backup-and-restore.md new file mode 100644 index 00000000..a5a4135c --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/configure-backup-and-restore.md @@ -0,0 +1,90 @@ +--- +title: "Configure backup and restore" +linkTitle: "Configure backup and restore" +weight: 10 +date: 2021-01-25 +description: > + Prevent loss of job history +--- + +Backup and restore is done by a container sidecar. + +### PVC + +#### Create PVC + +Save to the file named pvc.yaml: +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: + namespace: +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 500Gi +``` + +Run the following command: +```bash +$ kubectl -n create -f pvc.yaml +``` + +#### Configure Jenkins CR + +```yaml +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: + namespace: +spec: + master: + securityContext: + runAsUser: 1000 + fsGroup: 1000 + containers: + - name: jenkins-master + image: jenkins/jenkins:2.263.2-lts-alpine + - name: backup # container responsible for the backup and restore + env: + - name: BACKUP_DIR + value: /backup + - name: JENKINS_HOME + value: /jenkins-home + - name: BACKUP_COUNT + value: "3" # keep only the 2 most recent backups + image: virtuslab/jenkins-operator-backup-pvc:v0.1.0 # look at backup/pvc directory + imagePullPolicy: IfNotPresent + volumeMounts: + - mountPath: /jenkins-home # Jenkins home volume + name: jenkins-home + - mountPath: /backup # backup volume + name: backup + volumes: + - name: backup # PVC volume where backups will be stored + persistentVolumeClaim: + claimName: + backup: + containerName: backup # container name is responsible for backup + action: + exec: + command: + - /home/user/bin/backup.sh # this command is invoked on "backup" container to make backup, for example /home/user/bin/backup.sh , is passed by operator + getLatestAction: + exec: + command: + - /home/user/bin/get-latest.sh # this command is invoked on "backup" container to get last backup number before pod deletion. If you don't omit it in CR, you can lose data + interval: 30 # how often make backup in seconds + makeBackupBeforePodDeletion: true # make a backup before pod deletion + restore: + containerName: backup # container name is responsible for restore backup + action: + exec: + command: + - /home/user/bin/restore.sh # this command is invoked on "backup" container to make restore backup, for example /home/user/bin/restore.sh , is passed by operator + #recoveryOnce: # if want to restore specific backup configure this field and then Jenkins will be restarted and desired backup will be restored +``` diff --git a/website/content/en/docs/Getting Started/Preview/custom-backup-and-restore.md b/website/content/en/docs/Getting Started/Preview/custom-backup-and-restore.md new file mode 100644 index 00000000..f632953a --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/custom-backup-and-restore.md @@ -0,0 +1,184 @@ +--- +title: "Custom Backup and Restore Providers" +linkTitle: "Custom Backup and Restore Providers" +weight: 10 +date: 2021-01-18 +description: > + Custom backup and restore provider +--- + +With enough effort one can create a custom backup and restore provider +for the Jenkins Operator. + +## Requirements + +Two commands (e.g. scripts) are required: + +- a backup command, e.g. `backup.sh` that takes one argument, a **backup number** +- a restore command, e.g. `backup.sh` that takes one argument, a **backup number** + +Both scripts need to return an exit code of `0` on success and `1` or greater for failure. + +One of those scripts (or the entry point of the container) needs to be responsible +for backup cleanup or rotation if required, or an external system. + +## How it works + +The mechanism relies on basic Kubernetes and UNIX functionalities. + +The backup (and restore) container runs as a sidecar in the same +Kubernetes pod as the Jenkins master. + +Name of the backup and restore containers can be set as necessary using +`spec.backup.containerName` and `spec.restore.containerName`. +In most cases it will be the same container, but we allow for less common use cases. + +The operator will call a backup or restore commands inside a sidecar container when necessary: + +- backup command (defined in `spec.backup.action.exec.command`) + will be called every `N` seconds configurable in: `spec.backup.interval` + and on pod shutdown (if enabled in `spec.backup.makeBackupBeforePodDeletion`) + with an integer representing the current backup number as first and only argument +- restore command (defined in `spec.restore.action.exec.command`) + will be called at Jenkins startup + with an integer representing the backup number to restore as first and only argument + (can be overridden using `spec.restore.recoveryOnce`) + +## Example AWS S3 backup using the CLI + +This example shows abbreviated version of a simple AWS S3 backup implementation +using: `aws-cli`, `bash` and `kube2iam`. + +In addition to your normal `Jenkins` `CustomResource` some additional settings +for backup and restore are required, e.g.: + +```yaml +kind: Jenkins +apiVersion: jenkins.io/v1alpha1 +metadata: + name: example + namespace: jenkins +spec: + master: + masterAnnotations: + iam.amazonaws.com/role: "my-example-backup-role" # tell kube2iam where the AWS IAM role is + containers: + - name: jenkins-master + ... + - name: backup # container responsible for backup and restore + image: quay.io/virtuslab/aws-cli:1.16.263-2 + workingDir: /home/user/bin/ + command: # our container entry point + - sleep + - infinity + env: + - name: BACKUP_BUCKET + value: my-example-bucket # the S3 bucket name to use + - name: BACKUP_PATH + value: my-backup-path # the S3 bucket path prefix to use + - name: JENKINS_HOME + value: /jenkins-home # the path to mount jenkins home dir in the backup container + volumeMounts: + - mountPath: /jenkins-home # Jenkins home volume + name: jenkins-home + - mountPath: /home/user/bin/backup.sh + name: backup-scripts + subPath: backup.sh + readOnly: true + - mountPath: /home/user/bin/restore.sh + name: backup-scripts + subPath: restore.sh + readOnly: true + volumes: + - name: backup-scripts + configMap: + defaultMode: 0754 + name: jenkins-operator-backup-s3 + securityContext: # make sure both containers use the same UID and GUID + runAsUser: 1000 + fsGroup: 1000 + ... + backup: + containerName: backup # container name responsible for backup + interval: 3600 # how often make a backup in seconds + makeBackupBeforePodDeletion: true # trigger backup just before deleting the pod + action: + exec: + command: + # this command is invoked on "backup" container to create a backup, + # is passed by operator, + # for example /home/user/bin/backup.sh + - /home/user/bin/backup.sh + restore: + containerName: backup # container name is responsible for restore backup + action: + exec: + command: + # this command is invoked on "backup" container to restore a backup, + # is passed by operator + # for example /home/user/bin/restore.sh + - /home/user/bin/restore.sh +# recoveryOnce: # if want to restore specific backup configure this field and then Jenkins will be restarted and desired backup will be restored +``` + +The actual backup and restore scripts will be provided in a `ConfigMap`: + +```yaml +kind: ConfigMap +apiVersion: v1 +metadata: + name: jenkins-operator-backup-s3 + namespace: jenkins + labels: + app: jenkins-operator +data: + backup.sh: |- + #!/bin/bash -xeu + [[ ! $# -eq 1 ]] && echo "Usage: $0 backup_number" && exit 1; + [[ -z "${BACKUP_BUCKET}" ]] && echo "Required 'BACKUP_BUCKET' env not set" && exit 1; + [[ -z "${BACKUP_PATH}" ]] && echo "Required 'BACKUP_PATH' env not set" && exit 1; + [[ -z "${JENKINS_HOME}" ]] && echo "Required 'JENKINS_HOME' env not set" && exit 1; + + backup_number=$1 + echo "Running backup #${backup_number}" + + BACKUP_TMP_DIR=$(mktemp -d) + tar -C ${JENKINS_HOME} -czf "${BACKUP_TMP_DIR}/${backup_number}.tar.gz" --exclude jobs/*/workspace* -c jobs && \ + + aws s3 cp ${BACKUP_TMP_DIR}/${backup_number}.tar.gz s3://${BACKUP_BUCKET}/${BACKUP_PATH}/${backup_number}.tar.gz + echo Done + + restore.sh: |- + #!/bin/bash -xeu + [[ ! $# -eq 1 ]] && echo "Usage: $0 backup_number" && exit 1 + [[ -z "${BACKUP_BUCKET}" ]] && echo "Required 'BACKUP_BUCKET' env not set" && exit 1; + [[ -z "${BACKUP_PATH}" ]] && echo "Required 'BACKUP_PATH' env not set" && exit 1; + [[ -z "${JENKINS_HOME}" ]] && echo "Required 'JENKINS_HOME' env not set" && exit 1; + + backup_number=$1 + echo "Running restore #${backup_number}" + + BACKUP_TMP_DIR=$(mktemp -d) + aws s3 cp s3://${BACKUP_BUCKET}/${BACKUP_PATH}/${backup_number}.tar.gz ${BACKUP_TMP_DIR}/${backup_number}.tar.gz + + tar -C ${JENKINS_HOME} -zxf "${BACKUP_TMP_DIR}/${backup_number}.tar.gz" + echo Done +``` + +In our example we will use S3 bucket lifecycle policy to keep +the number of backups under control, e.g. Cloud Formation fragment: +```yaml + Type: AWS::S3::Bucket + Properties: + BucketName: my-example-bucket + ... + LifecycleConfiguration: + Rules: + - Id: BackupCleanup + Status: Enabled + Prefix: my-backup-path + ExpirationInDays: 7 + NoncurrentVersionExpirationInDays: 14 + AbortIncompleteMultipartUpload: + DaysAfterInitiation: 3 +``` diff --git a/website/content/en/docs/Getting Started/Preview/customization.md b/website/content/en/docs/Getting Started/Preview/customization.md new file mode 100644 index 00000000..76b12e10 --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/customization.md @@ -0,0 +1,202 @@ +--- +title: "Customization" +linkTitle: "Customization" +weight: 3 +date: 2021-01-25 +description: > + How to customize Jenkins +--- + +## How to customize Jenkins +Jenkins can be customized with plugins. +Plugin's configuration is applied as groovy scripts or the [configuration as code plugin](https://github.com/jenkinsci/configuration-as-code-plugin). +Any plugin working for Jenkins can be installed by the Jenkins Operator. + +Pre-installed plugins: +* configuration-as-code v1.47 +* git v4.5.0 +* job-dsl v1.77 +* kubernetes-credentials-provider v0.15 +* kubernetes v1.29.0 +* workflow-aggregator v2.6 +* workflow-job v2.40 + +Rest of the plugins can be found in [plugins repository](https://plugins.jenkins.io/). + + +#### Install plugins + +Edit Custom Resource under `spec.master.plugins`: + +```yaml +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + master: + plugins: + - name: simple-theme-plugin + version: "0.6" +``` + +Under `spec.master.basePlugins` you can find plugins for a valid **Jenkins Operator**: + +```yaml +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + master: + basePlugins: + - name: kubernetes + version: "1.28.6" + - name: workflow-job + version: "2.40" + - name: workflow-aggregator + version: "2.6" + - name: git + version: "4.5.0" + - name: job-dsl + version: "1.77" + - name: configuration-as-code + version: "1.46" + - name: kubernetes-credentials-provider + version: "0.15" +``` + +You can change their versions. + +The **Jenkins Operator** will then automatically install plugins after the Jenkins master pod restart. + +#### Apply plugin's config + +By using a [ConfigMap](https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/) you can create your own **Jenkins** customized configuration. +Then you must reference the **`ConfigMap`** in the **Jenkins** pod customization file in `spec.groovyScripts` or `spec.configurationAsCode` + +Create a **`ConfigMap`** with specific name (eg. `jenkins-operator-user-configuration`). Then, modify the **Jenkins** manifest: + +```yaml +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + configurationAsCode: + configurations: + - name: jenkins-operator-user-configuration + groovyScripts: + configurations: + - name: jenkins-operator-user-configuration +``` + +Here is an example of `jenkins-operator-user-configuration`: +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: jenkins-operator-user-configuration +data: + 1-configure-theme.groovy: | + import jenkins.* + import jenkins.model.* + import hudson.* + import hudson.model.* + import org.jenkinsci.plugins.simpletheme.ThemeElement + import org.jenkinsci.plugins.simpletheme.CssTextThemeElement + import org.jenkinsci.plugins.simpletheme.CssUrlThemeElement + + Jenkins jenkins = Jenkins.getInstance() + + def decorator = Jenkins.instance.getDescriptorByType(org.codefirst.SimpleThemeDecorator.class) + + List configElements = new ArrayList<>(); + configElements.add(new CssTextThemeElement("DEFAULT")); + configElements.add(new CssUrlThemeElement("https://cdn.rawgit.com/afonsof/jenkins-material-theme/gh-pages/dist/material-light-green.css")); + decorator.setElements(configElements); + decorator.save(); + + jenkins.save() + 1-system-message.yaml: | + jenkins: + systemMessage: "Configuration as Code integration works!!!" +``` + +* `*.groovy` is Groovy script configuration +* `*.yaml is` configuration as code + +If you want to correct your configuration you can edit it while the **Jenkins Operator** is running. +Jenkins will reconcile and apply the new configuration. + +## How to use secrets from a Groovy scripts + +If you configured `spec.groovyScripts.secret.name`, then this secret is available to use from map Groovy scripts. +The secrets are loaded to `secrets` map. + +Create a [secret](https://kubernetes.io/docs/concepts/configuration/secret/) with for example the name `jenkins-conf-secrets`. + +```yaml +kind: Secret +apiVersion: v1 +type: Opaque +metadata: + name: jenkins-conf-secrets + namespace: default +data: + SYSTEM_MESSAGE: SGVsbG8gd29ybGQ= +``` + +Then modify the **Jenkins** pod manifest by changing `spec.groovyScripts.secret.name` to `jenkins-conf-secrets`. + +```yaml +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + configurationAsCode: + configurations: + - name: jenkins-operator-user-configuration + secret: + name: jenkins-conf-secrets + groovyScripts: + configurations: + - name: jenkins-operator-user-configuration + secret: + name: jenkins-conf-secrets +``` + +Now you can test that the secret is mounted by applying this `ConfigMap` for Groovy script: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: jenkins-operator-user-configuration +data: + 1-system-message.groovy: | + import jenkins.* + import jenkins.model.* + import hudson.* + import hudson.model.* + Jenkins jenkins = Jenkins.getInstance() + + jenkins.setSystemMessage(secrets["SYSTEM_MESSAGE"]) + jenkins.save() +``` + +Or by applying this configuration as code: +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: jenkins-operator-user-configuration +data: + 1-system-message.yaml: | + jenkins: + systemMessage: ${SYSTEM_MESSAGE} +``` + + +After this, you should see the `Hello world` system message from the **Jenkins** homepage. \ No newline at end of file diff --git a/website/content/en/docs/Getting Started/Preview/deploy-jenkins.md b/website/content/en/docs/Getting Started/Preview/deploy-jenkins.md new file mode 100644 index 00000000..29e892f6 --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/deploy-jenkins.md @@ -0,0 +1,90 @@ +--- +title: "Deploy Jenkins" +linkTitle: "Deploy Jenkins" +weight: 1 +date: 2021-01-25 +description: > + Deploy production ready Jenkins Operator manifest +--- + +Once Jenkins Operator is up and running let's deploy actual Jenkins instance. +Create manifest e.g. **`jenkins_instance.yaml`** with following data and save it on drive. + +```yaml +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + name: example +spec: + master: + containers: + - name: jenkins-master + image: jenkins/jenkins:2.263.2-lts-alpine + imagePullPolicy: Always + livenessProbe: + failureThreshold: 12 + httpGet: + path: /login + port: http + scheme: HTTP + initialDelaySeconds: 80 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 5 + readinessProbe: + failureThreshold: 3 + httpGet: + path: /login + port: http + scheme: HTTP + initialDelaySeconds: 30 + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + resources: + limits: + cpu: 1500m + memory: 3Gi + requests: + cpu: "1" + memory: 500Mi + seedJobs: + - id: jenkins-operator + targets: "cicd/jobs/*.jenkins" + description: "Jenkins Operator repository" + repositoryBranch: master + repositoryUrl: https://github.com/jenkinsci/kubernetes-operator.git +``` + +Deploy a Jenkins to Kubernetes: + +```bash +kubectl create -f jenkins_instance.yaml +``` +Watch the Jenkins instance being created: + +```bash +kubectl get pods -w +``` + +Get the Jenkins credentials: + +```bash +kubectl get secret jenkins-operator-credentials- -o 'jsonpath={.data.user}' | base64 -d +kubectl get secret jenkins-operator-credentials- -o 'jsonpath={.data.password}' | base64 -d +``` + +Connect to the Jenkins instance (minikube): + +```bash +minikube service jenkins-operator-http- --url +``` + +Connect to the Jenkins instance (actual Kubernetes cluster): + +```bash +kubectl port-forward jenkins- 8080:8080 +``` +Then open browser with address `http://localhost:8080`. + +![jenkins](/kubernetes-operator/img/jenkins.png) diff --git a/website/content/en/docs/Getting Started/Preview/diagnostics.md b/website/content/en/docs/Getting Started/Preview/diagnostics.md new file mode 100644 index 00000000..18ecf679 --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/diagnostics.md @@ -0,0 +1,42 @@ +--- +title: "Diagnostics" +linkTitle: "Diagnostics" +weight: 40 +date: 2021-01-18 +description: > + How to deal with Jenkins Operator problems +--- + + +Turn on debug in **Jenkins Operator** deployment: + +```bash +sed -i 's|\(args:\).*|\1\ ["--debug"\]|' deploy/operator.yaml +kubectl apply -f deploy/operator.yaml +``` + +Watch Kubernetes events: + +```bash +kubectl get events --sort-by='{.lastTimestamp}' +``` + +Verify Jenkins master logs: + +```bash +kubectl logs -f jenkins- +``` + +Verify the `jenkins-operator` logs: + +```bash +kubectl logs deployment/jenkins-operator +``` + +## Troubleshooting + +Delete the Jenkins master pod and wait for the new one to come up: + +```bash +kubectl delete pod jenkins- +``` diff --git a/website/content/en/docs/Getting Started/Preview/notifications.md b/website/content/en/docs/Getting Started/Preview/notifications.md new file mode 100644 index 00000000..06845e29 --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/notifications.md @@ -0,0 +1,114 @@ +--- +title: "Notifications" +linkTitle: "Notifications" +weight: 10 +date: 2021-01-18 +description: > + How to setup operator notifications. +--- + +## Slack + +Please follow [this](https://api.slack.com/incoming-webhooks) instructions to get web hook URL. + +Create web hook secret with name `jenkins-operator-notification-data`. Contains key `url` with provided web hook URL. + +```bash +$ kubectl create secret generic jenkins-operator-notification-data --from-literal=url= +``` + +Example configuration for Slack: + +```yaml +kind: Jenkins +spec: + master: + notifications: + - level: info + verbose: true + name: + slack: + webHookURLSecretKeySelector: + secret: + name: + key: +``` + +## Microsoft Teams + +Please follow [this](https://docs.microsoft.com/en-gb/outlook/actionable-messages/send-via-connectors) instructions to get web hook URL. + +Example configuration for Microsoft Teams: + +```yaml +kind: Jenkins +spec: + master: + notifications: + - level: info + verbose: true + name: + teams: + webHookURLSecretKeySelector: + secret: + name: + key: +``` + +## Mailgun + +Example configuration for Mailgun: + +```yaml +kind: Jenkins +spec: + master: + notifications: + - level: info + verbose: true + name: + mailgun: + domain: + apiKeySecretKeySelector: + secret: + name: + key: + recipient: + from: +``` + +## Debug options + +As you see there is two debugging options: + +* `level` (warning/info) - Set level of messages to send. + +* `verbose` - Print stacktrace and additional error messages + +## Multiple providers + +You can use multiple providers to send notification to another communication channels at the same time. +For example you will send notifications to Slack and Teams. + +```yaml +kind: Jenkins +spec: + master: + notifications: + - level: info + verbose: true + name: nslack + slack: + webHookURLSecretKeySelector: + secret: + name: + key: + - level: info + verbose: true + name: nteams + teams: + webHookURLSecretKeySelector: + secret: + name: + key: +``` diff --git a/website/content/en/docs/Getting Started/Preview/openshift.md b/website/content/en/docs/Getting Started/Preview/openshift.md new file mode 100644 index 00000000..7af146ac --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/openshift.md @@ -0,0 +1,104 @@ +--- +title: "OpenShift" +linkTitle: "OpenShift" +weight: 20 +date: 2020-04-29 +description: > + Additional configuration for OpenShift +--- + +## SecurityContext + +OpenShift enforces Security Constraints Context (scc) when deploying an image. +By default, container images run in restricted scc which prevents from setting +a fixed user id to run with. You need to have ensure that you do not provide a +securityContext with a runAsUser and that your image does not use a hardcoded user. + +```yaml +securityContext: {} +``` + +## OpenShift Jenkins image + +OpenShift provides a pre-configured Jenkins image containing 3 openshift plugins for +jenkins (openshift-login-plugin, openshift-sync-plugin and openshift-client-plugin) +which allows better jenkins integration with kubernetes and OpenShift. + +The OpenShift Jenkins image requires additional configuration to be fully enabled. + +### Sample OpenShift CR +The following Custom Resource can be used to create a Jenkins instance using the +OpenShift Jenkins image and sets values for: +- `image: 'quay.io/openshift/origin-jenkins:latest' : This is the OpenShift Jenkins image. + +- serviceAccount: to allow oauth authentication to work, the service account needs +a specific annotation pointing to the route exposing the jenkins service. Here, +the route is named `jenkins-route` + +- `OPENSHIFT_ENABLE_OAUTH` environment variable for the master container is set to true. + +Here is a complete Jenkins CR allowing the deployment of the Jenkins OpenShift image. +```yaml +apiVersion: jenkins.io/v1alpha2 +kind: Jenkins +metadata: + annotations: + jenkins.io/openshift-mode: 'true' + name: jenkins +spec: + serviceAccount: + annotations: + serviceaccounts.openshift.io/oauth-redirectreference.jenkins: '{"kind":"OAuthRedirectReference","apiVersion":"v1","reference":{"kind":"Route","name":"jenkins-route"}}' + master: + containers: + - name: jenkins-master + image: 'quay.io/openshift/origin-jenkins:latest' + command: + - /usr/bin/go-init + - '-main' + - /usr/libexec/s2i/run + env: + - name: OPENSHIFT_ENABLE_OAUTH + value: 'true' + - name: OPENSHIFT_ENABLE_REDIRECT_PROMPT + value: 'true' + - name: DISABLE_ADMINISTRATIVE_MONITORS + value: 'false' + - name: KUBERNETES_MASTER + value: 'https://kubernetes.default:443' + - name: KUBERNETES_TRUST_CERTIFICATES + value: 'true' + - name: JENKINS_SERVICE_NAME + value: jenkins-operator-http-jenkins + - name: JNLP_SERVICE_NAME + value: jenkins-operator-slave-jenkins + - name: JENKINS_UC_INSECURE + value: 'false' + - name: JENKINS_HOME + value: /var/lib/jenkins + - name: JAVA_OPTS + value: >- + -XX:+UnlockExperimentalVMOptions -XX:+UnlockExperimentalVMOptions + -XX:+UseCGroupMemoryLimitForHeap -XX:MaxRAMFraction=1 + -Djenkins.install.runSetupWizard=false -Djava.awt.headless=true + imagePullPolicy: Always + service: + port: 8080 + type: ClusterIP + slaveService: + port: 50000 + type: ClusterIP +``` + +### OpenShift OAuth integration +The creation of a Route is required for the integraiton of Jenkins with +OpenShift oauth authentication. By default, the jenkins http service is named +`jenkins-operator-http-${jenkins-cr-name}` + +```bash +oc create route edge jenkins-route --service=jenkins-operator-http-jenkins +``` +Note: the route name (jenkins-route) must match the pointed route on the serviceaccount annotation. + + +After the creation of the Route. It can be used to navigate to the Jenkins Login Page and login with your Openshift Credentials. diff --git a/website/content/en/docs/Getting Started/Preview/schema.md b/website/content/en/docs/Getting Started/Preview/schema.md new file mode 100644 index 00000000..2a1444c7 --- /dev/null +++ b/website/content/en/docs/Getting Started/Preview/schema.md @@ -0,0 +1,2642 @@ +--- +title: "Schema" +linkTitle: "Schema" +weight: 40 +date: 2021-01-18 +description: > + API Schema definitions for Jenkins CRD +--- + +{{% pageinfo %}} +This document contains API scheme for `jenkins-operator` Custom Resource Definition manifest +{{% /pageinfo %}} + +

Packages:

+ +

jenkins.io

+

+

Package v1alpha2 contains API Schema definitions for the jenkins.io v1alpha2 API group

+

+Resource Types: + +

Jenkins +

+

+

Jenkins is the Schema for the jenkins API

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+apiVersion
+string
+ +jenkins.io/v1alpha2 + +
+kind
+string +
Jenkins
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +JenkinsSpec + + +
+

Spec defines the desired state of the Jenkins

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+master
+ + +JenkinsMaster + + +
+

Master represents Jenkins master pod properties and Jenkins plugins. +Every single change here requires a pod restart.

+
+seedJobs
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.SeedJob + + +
+(Optional) +

SeedJobs defines list of Jenkins Seed Job configurations +More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration#configure-seed-jobs-and-pipelines

+
+notifications
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Notification + + +
+(Optional) +

Notifications defines list of a services which are used to inform about Jenkins status +Can be used to integrate chat services like Slack, Microsoft Teams or Mailgun

+
+service
+ + +Service + + +
+(Optional) +

Service is Kubernetes service of Jenkins master HTTP pod +Defaults to : +port: 8080 +type: ClusterIP

+
+slaveService
+ + +Service + + +
+(Optional) +

Service is Kubernetes service of Jenkins slave pods +Defaults to : +port: 50000 +type: ClusterIP

+
+backup
+ + +Backup + + +
+(Optional) +

Backup defines configuration of Jenkins backup +More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/

+
+restore
+ + +Restore + + +
+(Optional) +

Backup defines configuration of Jenkins backup restore +More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/

+
+groovyScripts
+ + +GroovyScripts + + +
+(Optional) +

GroovyScripts defines configuration of Jenkins customization via groovy scripts

+
+configurationAsCode
+ + +ConfigurationAsCode + + +
+(Optional) +

ConfigurationAsCode defines configuration of Jenkins customization via Configuration as Code Jenkins plugin

+
+roles
+ + +[]Kubernetes rbac/v1.RoleRef + + +
+(Optional) +

Roles defines list of extra RBAC roles for the Jenkins Master pod service account

+
+serviceAccount
+ + +ServiceAccount + + +
+(Optional) +

ServiceAccount defines Jenkins master service account attributes

+
+jenkinsAPISettings
+ + +JenkinsAPISettings + + +
+

JenkinsAPISettings defines configuration used by the operator to gain admin access to the Jenkins API

+
+
+status
+ + +JenkinsStatus + + +
+

Status defines the observed state of Jenkins

+
+

AppliedGroovyScript +

+

+(Appears on: +JenkinsStatus) +

+

+

AppliedGroovyScript is the applied groovy script in Jenkins by the operator.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+configurationType
+ +string + +
+

ConfigurationType is the name of the configuration type(base-groovy, user-groovy, user-casc)

+
+source
+ +string + +
+

Source is the name of source where is located groovy script

+
+name
+ +string + +
+

Name is the name of the groovy script

+
+hash
+ +string + +
+

Hash is the hash of the groovy script and secrets which it uses

+
+

AuthorizationStrategy +(string alias)

+

+(Appears on: +JenkinsAPISettings) +

+

+

AuthorizationStrategy defines authorization strategy of the operator for the Jenkins API

+

+

Backup +

+

+(Appears on: +JenkinsSpec) +

+

+

Backup defines configuration of Jenkins backup.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+containerName
+ +string + +
+

ContainerName is the container name responsible for backup operation

+
+action
+ + +Handler + + +
+

Action defines action which performs backup in backup container sidecar

+
+interval
+ +uint64 + +
+

Interval tells how often make backup in seconds +Defaults to 30.

+
+makeBackupBeforePodDeletion
+ +bool + +
+

MakeBackupBeforePodDeletion tells operator to make backup before Jenkins master pod deletion

+
+

ConfigMapRef +

+

+(Appears on: +Customization) +

+

+

ConfigMapRef is reference to Kubernetes ConfigMap.

+

+ + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+

ConfigurationAsCode +

+

+(Appears on: +JenkinsSpec) +

+

+

ConfigurationAsCode defines configuration of Jenkins customization via Configuration as Code Jenkins plugin.

+

+ + + + + + + + + + + + + +
FieldDescription
+Customization
+ + +Customization + + +
+

+(Members of Customization are embedded into this type.) +

+
+

Container +

+

+(Appears on: +JenkinsMaster) +

+

+

Container defines Kubernetes container attributes.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name of the container specified as a DNS_LABEL. +Each container in a pod must have a unique name (DNS_LABEL).

+
+image
+ +string + +
+

Docker image name. +More info: https://kubernetes.io/docs/concepts/containers/images

+
+imagePullPolicy
+ + +Kubernetes core/v1.PullPolicy + + +
+

Image pull policy. +One of Always, Never, IfNotPresent. +Defaults to Always.

+
+resources
+ + +Kubernetes core/v1.ResourceRequirements + + +
+

Compute Resources required by this container. +More info: https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/

+
+command
+ +[]string + +
+(Optional) +

Entrypoint array. Not executed within a shell. +The docker image’s ENTRYPOINT is used if this is not provided. +Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable +cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax +can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, +regardless of whether the variable exists or not. +More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

+
+args
+ +[]string + +
+(Optional) +

Arguments to the entrypoint. +The docker image’s CMD is used if this is not provided. +Variable references $(VAR_NAME) are expanded using the container’s environment. If a variable +cannot be resolved, the reference in the input string will be unchanged. The $(VAR_NAME) syntax +can be escaped with a double $$, ie: $$(VAR_NAME). Escaped references will never be expanded, +regardless of whether the variable exists or not. +More info: https://kubernetes.io/docs/tasks/inject-data-application/define-command-argument-container/#running-a-command-in-a-shell

+
+workingDir
+ +string + +
+(Optional) +

Container’s working directory. +If not specified, the container runtime’s default will be used, which +might be configured in the container image.

+
+ports
+ + +[]Kubernetes core/v1.ContainerPort + + +
+(Optional) +

List of ports to expose from the container. Exposing a port here gives +the system additional information about the network connections a +container uses, but is primarily informational. Not specifying a port here +DOES NOT prevent that port from being exposed. Any port which is +listening on the default “0.0.0.0” address inside a container will be +accessible from the network.

+
+envFrom
+ + +[]Kubernetes core/v1.EnvFromSource + + +
+(Optional) +

List of sources to populate environment variables in the container. +The keys defined within a source must be a C_IDENTIFIER. All invalid keys +will be reported as an event when the container is starting. When a key exists in multiple +sources, the value associated with the last source will take precedence. +Values defined by an Env with a duplicate key will take precedence.

+
+env
+ + +[]Kubernetes core/v1.EnvVar + + +
+(Optional) +

List of environment variables to set in the container.

+
+volumeMounts
+ + +[]Kubernetes core/v1.VolumeMount + + +
+(Optional) +

Pod volumes to mount into the container’s filesystem.

+
+livenessProbe
+ + +Kubernetes core/v1.Probe + + +
+(Optional) +

Periodic probe of container liveness. +Container will be restarted if the probe fails.

+
+readinessProbe
+ + +Kubernetes core/v1.Probe + + +
+(Optional) +

Periodic probe of container service readiness. +Container will be removed from service endpoints if the probe fails.

+
+lifecycle
+ + +Kubernetes core/v1.Lifecycle + + +
+(Optional) +

Actions that the management system should take in response to container lifecycle events.

+
+securityContext
+ + +Kubernetes core/v1.SecurityContext + + +
+(Optional) +

Security options the pod should run with. +More info: https://kubernetes.io/docs/concepts/policy/security-context/ +More info: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

+
+

Customization +

+

+(Appears on: +ConfigurationAsCode, +GroovyScripts) +

+

+

Customization defines configuration of Jenkins customization.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+secret
+ + +SecretRef + + +
+
+configurations
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.ConfigMapRef + + +
+
+

GroovyScripts +

+

+(Appears on: +JenkinsSpec) +

+

+

GroovyScripts defines configuration of Jenkins customization via groovy scripts.

+

+ + + + + + + + + + + + + +
FieldDescription
+Customization
+ + +Customization + + +
+

+(Members of Customization are embedded into this type.) +

+
+

Handler +

+

+(Appears on: +Backup, +Restore) +

+

+

Handler defines a specific action that should be taken.

+

+ + + + + + + + + + + + + +
FieldDescription
+exec
+ + +Kubernetes core/v1.ExecAction + + +
+

Exec specifies the action to take.

+
+

Image +

+

+(Appears on: +JenkinsImageSpec) +

+

+

Defines Jenkins Plugin structure

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+version
+ +string + +
+
+

JenkinsAPISettings +

+

+(Appears on: +JenkinsSpec) +

+

+

JenkinsAPISettings defines configuration used by the operator to gain admin access to the Jenkins API

+

+ + + + + + + + + + + + + +
FieldDescription
+authorizationStrategy
+ + +AuthorizationStrategy + + +
+
+

JenkinsCredentialType +(string alias)

+

+(Appears on: +SeedJob) +

+

+

JenkinsCredentialType defines type of Jenkins credential used to seed job mechanism.

+

+

JenkinsImage +

+

+

JenkinsImage is the Schema for the jenkinsimages API

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+metadata
+ + +Kubernetes meta/v1.ObjectMeta + + +
+Refer to the Kubernetes API documentation for the fields of the +metadata field. +
+spec
+ + +JenkinsImageSpec + + +
+
+
+ + + + + + + + + +
+image
+ + +Image + + +
+
+plugins
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsPlugin + + +
+
+
+status
+ + +JenkinsImageStatus + + +
+
+

JenkinsImageSpec +

+

+(Appears on: +JenkinsImage) +

+

+

JenkinsImageSpec defines the desired state of JenkinsImage

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+image
+ + +Image + + +
+
+plugins
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsPlugin + + +
+
+

JenkinsImageStatus +

+

+(Appears on: +JenkinsImage) +

+

+

JenkinsImageStatus defines the observed state of JenkinsImage

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+image
+ +string + +
+
+md5sum
+ +string + +
+
+installedPlugins
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.JenkinsPlugin + + +
+
+

JenkinsMaster +

+

+(Appears on: +JenkinsSpec) +

+

+

JenkinsMaster defines the Jenkins master pod attributes and plugins, +every single change requires a Jenkins master pod restart.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+annotations
+ +map[string]string + +
+(Optional) +

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

+
+masterAnnotations
+ +map[string]string + +
+(Optional) +

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 +Deprecated: will be removed in the future, please use Annotations(annotations)

+
+labels
+ +map[string]string + +
+(Optional) +

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

+
+nodeSelector
+ +map[string]string + +
+(Optional) +

NodeSelector is a selector which must be true for the pod to fit on a node. +Selector which must match a node’s labels for the pod to be scheduled on that node. +More info: https://kubernetes.io/docs/concepts/configuration/assign-pod-node/

+
+securityContext
+ + +Kubernetes core/v1.PodSecurityContext + + +
+(Optional) +

SecurityContext that applies to all the containers of the Jenkins +Master. As per kubernetes specification, it can be overridden +for each container individually. +Defaults to: +runAsUser: 1000 +fsGroup: 1000

+
+containers
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Container + + +
+(Optional) +

List of containers belonging to the pod. +Containers cannot currently be added or removed. +There must be at least one container in a Pod. +Defaults to: +- image: jenkins/jenkins:lts +imagePullPolicy: Always +livenessProbe: +failureThreshold: 12 +httpGet: +path: /login +port: http +scheme: HTTP +initialDelaySeconds: 80 +periodSeconds: 10 +successThreshold: 1 +timeoutSeconds: 5 +name: jenkins-master +readinessProbe: +failureThreshold: 3 +httpGet: +path: /login +port: http +scheme: HTTP +initialDelaySeconds: 30 +periodSeconds: 10 +successThreshold: 1 +timeoutSeconds: 1 +resources: +limits: +cpu: 1500m +memory: 3Gi +requests: +cpu: “1” +memory: 600Mi

+
+imagePullSecrets
+ + +[]Kubernetes core/v1.LocalObjectReference + + +
+(Optional) +

ImagePullSecrets is an optional list of references to secrets in the same namespace to use for pulling any of the images used by this PodSpec. +If specified, these secrets will be passed to individual puller implementations for them to use. For example, +in the case of docker, only DockerConfig type secrets are honored. +More info: https://kubernetes.io/docs/concepts/containers/images#specifying-imagepullsecrets-on-a-pod

+
+volumes
+ + +[]Kubernetes core/v1.Volume + + +
+(Optional) +

List of volumes that can be mounted by containers belonging to the pod. +More info: https://kubernetes.io/docs/concepts/storage/volumes

+
+tolerations
+ + +[]Kubernetes core/v1.Toleration + + +
+(Optional) +

If specified, the pod’s tolerations.

+
+basePlugins
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Plugin + + +
+(Optional) +

BasePlugins contains plugins required by operator +Defaults to : +- name: kubernetes +version: “1.28.6” +- name: workflow-job +version: “2.40” +- name: workflow-aggregator +version: “2.6” +- name: git +version: “4.5.0” +- name: job-dsl +version: “1.77” +- name: configuration-as-code +version: “1.46” +- name: kubernetes-credentials-provider +version: “0.15”

+
+plugins
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Plugin + + +
+(Optional) +

Plugins contains plugins required by user

+
+disableCSRFProtection
+ +bool + +
+

DisableCSRFProtection allows you to toggle CSRF Protection on Jenkins

+
+priorityClassName
+ +string + +
+(Optional) +

PriorityClassName for Jenkins master pod

+
+

JenkinsPlugin +

+

+(Appears on: +JenkinsImageSpec, +JenkinsImageStatus) +

+

+

Defines Jenkins Plugin structure

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+version
+ +string + +
+
+

JenkinsSpec +

+

+(Appears on: +Jenkins) +

+

+

JenkinsSpec defines the desired state of the Jenkins.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+master
+ + +JenkinsMaster + + +
+

Master represents Jenkins master pod properties and Jenkins plugins. +Every single change here requires a pod restart.

+
+seedJobs
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.SeedJob + + +
+(Optional) +

SeedJobs defines list of Jenkins Seed Job configurations +More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration#configure-seed-jobs-and-pipelines

+
+notifications
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Notification + + +
+(Optional) +

Notifications defines list of a services which are used to inform about Jenkins status +Can be used to integrate chat services like Slack, Microsoft Teams or Mailgun

+
+service
+ + +Service + + +
+(Optional) +

Service is Kubernetes service of Jenkins master HTTP pod +Defaults to : +port: 8080 +type: ClusterIP

+
+slaveService
+ + +Service + + +
+(Optional) +

Service is Kubernetes service of Jenkins slave pods +Defaults to : +port: 50000 +type: ClusterIP

+
+backup
+ + +Backup + + +
+(Optional) +

Backup defines configuration of Jenkins backup +More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/

+
+restore
+ + +Restore + + +
+(Optional) +

Backup defines configuration of Jenkins backup restore +More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configure-backup-and-restore/

+
+groovyScripts
+ + +GroovyScripts + + +
+(Optional) +

GroovyScripts defines configuration of Jenkins customization via groovy scripts

+
+configurationAsCode
+ + +ConfigurationAsCode + + +
+(Optional) +

ConfigurationAsCode defines configuration of Jenkins customization via Configuration as Code Jenkins plugin

+
+roles
+ + +[]Kubernetes rbac/v1.RoleRef + + +
+(Optional) +

Roles defines list of extra RBAC roles for the Jenkins Master pod service account

+
+serviceAccount
+ + +ServiceAccount + + +
+(Optional) +

ServiceAccount defines Jenkins master service account attributes

+
+jenkinsAPISettings
+ + +JenkinsAPISettings + + +
+

JenkinsAPISettings defines configuration used by the operator to gain admin access to the Jenkins API

+
+

JenkinsStatus +

+

+(Appears on: +Jenkins) +

+

+

JenkinsStatus defines the observed state of Jenkins

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+operatorVersion
+ +string + +
+(Optional) +

OperatorVersion is the operator version which manages this CR

+
+provisionStartTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

ProvisionStartTime is a time when Jenkins master pod has been created

+
+baseConfigurationCompletedTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

BaseConfigurationCompletedTime is a time when Jenkins base configuration phase has been completed

+
+userConfigurationCompletedTime
+ + +Kubernetes meta/v1.Time + + +
+(Optional) +

UserConfigurationCompletedTime is a time when Jenkins user configuration phase has been completed

+
+restoredBackup
+ +uint64 + +
+(Optional) +

RestoredBackup is the restored backup number after Jenkins master pod restart

+
+lastBackup
+ +uint64 + +
+(Optional) +

LastBackup is the latest backup number

+
+pendingBackup
+ +uint64 + +
+(Optional) +

PendingBackup is the pending backup number

+
+backupDoneBeforePodDeletion
+ +bool + +
+(Optional) +

BackupDoneBeforePodDeletion tells if backup before pod deletion has been made

+
+userAndPasswordHash
+ +string + +
+(Optional) +

UserAndPasswordHash is a SHA256 hash made from user and password

+
+createdSeedJobs
+ +[]string + +
+(Optional) +

CreatedSeedJobs contains list of seed job id already created in Jenkins

+
+appliedGroovyScripts
+ + +[][]github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.AppliedGroovyScript + + +
+(Optional) +

AppliedGroovyScripts is a list with all applied groovy scripts in Jenkins by the operator

+
+

Mailgun +

+

+(Appears on: +Notification) +

+

+

Mailgun is handler for Mailgun email service notification channel.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+domain
+ +string + +
+
+apiKeySecretKeySelector
+ + +SecretKeySelector + + +
+
+recipient
+ +string + +
+
+from
+ +string + +
+
+

MicrosoftTeams +

+

+(Appears on: +Notification) +

+

+

MicrosoftTeams is handler for Microsoft MicrosoftTeams notification channel.

+

+ + + + + + + + + + + + + +
FieldDescription
+webHookURLSecretKeySelector
+ + +SecretKeySelector + + +
+

The web hook URL to MicrosoftTeams App

+
+

Notification +

+

+(Appears on: +JenkinsSpec) +

+

+

Notification is a service configuration used to send notifications about Jenkins status.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+level
+ + +NotificationLevel + + +
+
+verbose
+ +bool + +
+
+name
+ +string + +
+
+slack
+ + +github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Slack + + +
+
+teams
+ + +github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.MicrosoftTeams + + +
+
+mailgun
+ + +github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Mailgun + + +
+
+smtp
+ + +github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.SMTP + + +
+
+

NotificationLevel +(string alias)

+

+(Appears on: +Notification) +

+

+

NotificationLevel defines the level of a Notification.

+

+

Plugin +

+

+(Appears on: +JenkinsMaster) +

+

+

Plugin defines Jenkins plugin.

+

+ + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+

Name is the name of Jenkins plugin

+
+version
+ +string + +
+

Version is the version of Jenkins plugin

+
+downloadURL
+ +string + +
+

DownloadURL is the custom url from where plugin has to be downloaded.

+
+

Restore +

+

+(Appears on: +JenkinsSpec) +

+

+

Restore defines configuration of Jenkins backup restore operation.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+containerName
+ +string + +
+

ContainerName is the container name responsible for restore backup operation

+
+action
+ + +Handler + + +
+

Action defines action which performs restore backup in restore container sidecar

+
+getLatestAction
+ + +Handler + + +
+(Optional) +

GetLatestAction defines action which returns the latest backup number. If there is no backup “-1” should be +returned.

+
+recoveryOnce
+ +uint64 + +
+(Optional) +

RecoveryOnce if want to restore specific backup set this field and then Jenkins will be restarted and desired backup will be restored

+
+

SMTP +

+

+(Appears on: +Notification) +

+

+

SMTP is handler for sending emails via this protocol.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+usernameSecretKeySelector
+ + +SecretKeySelector + + +
+
+passwordSecretKeySelector
+ + +SecretKeySelector + + +
+
+port
+ +int + +
+
+server
+ +string + +
+
+tlsInsecureSkipVerify
+ +bool + +
+
+from
+ +string + +
+
+to
+ +string + +
+
+

SecretKeySelector +

+

+(Appears on: +Mailgun, +MicrosoftTeams, +SMTP, +Slack) +

+

+

SecretKeySelector selects a key of a Secret.

+

+ + + + + + + + + + + + + + + + + +
FieldDescription
+secret
+ + +Kubernetes core/v1.LocalObjectReference + + +
+

The name of the secret in the pod’s namespace to select from.

+
+key
+ +string + +
+

The key of the secret to select from. Must be a valid secret key.

+
+

SecretRef +

+

+(Appears on: +Customization) +

+

+

SecretRef is reference to Kubernetes secret.

+

+ + + + + + + + + + + + + +
FieldDescription
+name
+ +string + +
+
+

SeedJob +

+

+(Appears on: +JenkinsSpec) +

+

+

SeedJob defines configuration for seed job +More info: https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/configuration/#configure-seed-jobs-and-pipelines.

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+id
+ +string + +
+

ID is the unique seed job name

+
+credentialID
+ +string + +
+

CredentialID is the Kubernetes secret name which stores repository access credentials

+
+description
+ +string + +
+(Optional) +

Description is the description of the seed job

+
+targets
+ +string + +
+

Targets is the repository path where are seed job definitions

+
+repositoryBranch
+ +string + +
+

RepositoryBranch is the repository branch where are seed job definitions

+
+repositoryUrl
+ +string + +
+

RepositoryURL is the repository access URL. Can be SSH or HTTPS.

+
+credentialType
+ + +JenkinsCredentialType + + +
+(Optional) +

JenkinsCredentialType is the https://jenkinsci.github.io/kubernetes-credentials-provider-plugin/ credential type

+
+bitbucketPushTrigger
+ +bool + +
+(Optional) +

BitbucketPushTrigger is used for Bitbucket web hooks

+
+githubPushTrigger
+ +bool + +
+(Optional) +

GitHubPushTrigger is used for GitHub web hooks

+
+buildPeriodically
+ +string + +
+(Optional) +

BuildPeriodically is setting for scheduled trigger

+
+pollSCM
+ +string + +
+(Optional) +

PollSCM is setting for polling changes in SCM

+
+ignoreMissingFiles
+ +bool + +
+(Optional) +

IgnoreMissingFiles is setting for Job DSL API plugin to ignore files that miss

+
+additionalClasspath
+ +string + +
+(Optional) +

AdditionalClasspath is setting for Job DSL API plugin to set Additional Classpath

+
+failOnMissingPlugin
+ +bool + +
+(Optional) +

FailOnMissingPlugin is setting for Job DSL API plugin that fails job if required plugin is missing

+
+unstableOnDeprecation
+ +bool + +
+(Optional) +

UnstableOnDeprecation is setting for Job DSL API plugin that sets build status as unstable if build using deprecated features

+
+

Service +

+

+(Appears on: +JenkinsSpec) +

+

+

Service defines Kubernetes service attributes

+

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescription
+annotations
+ +map[string]string + +
+(Optional) +

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

+
+labels
+ +map[string]string + +
+(Optional) +

Route service traffic to pods with label keys and values matching this +selector. If empty or not present, the service is assumed to have an +external process managing its endpoints, which Kubernetes will not +modify. Only applies to types ClusterIP, NodePort, and LoadBalancer. +Ignored if type is ExternalName. +More info: https://kubernetes.io/docs/concepts/services-networking/service/

+
+type
+ + +Kubernetes core/v1.ServiceType + + +
+(Optional) +

Type determines how the Service is exposed. Defaults to ClusterIP. Valid +options are ExternalName, ClusterIP, NodePort, and LoadBalancer. +“ExternalName” maps to the specified externalName. +“ClusterIP” allocates a cluster-internal IP address for load-balancing to +endpoints. Endpoints are determined by the selector or if that is not +specified, by manual construction of an Endpoints object. If clusterIP is +“None”, no virtual IP is allocated and the endpoints are published as a +set of endpoints rather than a stable IP. +“NodePort” builds on ClusterIP and allocates a port on every node which +routes to the clusterIP. +“LoadBalancer” builds on NodePort and creates an +external load-balancer (if supported in the current cloud) which routes +to the clusterIP. +More info: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services—service-types

+
+port
+ +int32 + +
+

The port that are exposed by this service. +More info: https://kubernetes.io/docs/concepts/services-networking/service/#virtual-ips-and-service-proxies

+
+nodePort
+ +int32 + +
+(Optional) +

The port on each node on which this service is exposed when type=NodePort or LoadBalancer. +Usually assigned by the system. If specified, it will be allocated to the service +if unused or else creation of the service will fail. +Default is to auto-allocate a port if the ServiceType of this Service requires one. +More info: https://kubernetes.io/docs/concepts/services-networking/service/#type-nodeport

+
+loadBalancerSourceRanges
+ +[]string + +
+(Optional) +

If specified and supported by the platform, this will restrict traffic through the cloud-provider +load-balancer will be restricted to the specified client IPs. This field will be ignored if the +cloud-provider does not support the feature.” +More info: https://kubernetes.io/docs/tasks/administer-cluster/securing-a-cluster/#restricting-cloud-metadata-api-access

+
+loadBalancerIP
+ +string + +
+(Optional) +

Only applies to Service Type: LoadBalancer +LoadBalancer will get created with the IP specified in this field. +This feature depends on whether the underlying cloud-provider supports specifying +the loadBalancerIP when a load balancer is created. +This field will be ignored if the cloud-provider does not support the feature.

+
+

ServiceAccount +

+

+(Appears on: +JenkinsSpec) +

+

+

ServiceAccount defines Kubernetes service account attributes

+

+ + + + + + + + + + + + + +
FieldDescription
+annotations
+ +map[string]string + +
+(Optional) +

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

+
+

Slack +

+

+(Appears on: +Notification) +

+

+

Slack is handler for Slack notification channel.

+

+ + + + + + + + + + + + + +
FieldDescription
+webHookURLSecretKeySelector
+ + +SecretKeySelector + + +
+

The web hook URL to Slack App

+
+
+

+Generated with gen-crd-api-reference-docs +on git commit fe81e5a. +

diff --git a/website/content/en/docs/Getting Started/latest/configuration.md b/website/content/en/docs/Getting Started/latest/configuration.md index 9fbf0bca..56b0e14d 100644 --- a/website/content/en/docs/Getting Started/latest/configuration.md +++ b/website/content/en/docs/Getting Started/latest/configuration.md @@ -106,7 +106,7 @@ podTemplate(label: label, Jenkins Seed Jobs are configured using `Jenkins.spec.seedJobs` section from your custom resource manifest: -``` +```yaml apiVersion: jenkins.io/v1alpha2 kind: Jenkins metadata: @@ -167,7 +167,7 @@ If key was generated by `ssh-keygen` the public key content is located in Jenkins default image details --- -**Jenkins Operator** is fully compatible with **`jenkins:lts`** Docker image and does not introduce any hidden changes to the upstream Jenkins. +**Jenkins Operator** is fully compatible with **`jenkins:lts`** Docker image and does not introduce any hidden changes +to the upstream Jenkins. However due to problems with plugins and images version compatibility we are using specific tags +in the exemplary Custom Resource, so you know a working configuration. If needed, the Docker image can be easily changed in custom resource manifest as long as it supports standard Jenkins file system structure. diff --git a/website/content/en/docs/Installation/Preview/_index.md b/website/content/en/docs/Installation/Preview/_index.md new file mode 100644 index 00000000..60f42ee0 --- /dev/null +++ b/website/content/en/docs/Installation/Preview/_index.md @@ -0,0 +1,881 @@ +--- +title: "Installation - Preview" +linkTitle: "Installation - Preview" +weight: 1 +date: 2020-10-05 +description: > + How to install Jenkins Operator +--- + +{{% pageinfo %}} +This document describes installation procedure for **Jenkins Operator**. +All container images can be found at [virtuslab/jenkins-operator](https://hub.docker.com/r/virtuslab/jenkins-operator) +{{% /pageinfo %}} + +## Requirements + +To run **Jenkins Operator**, you will need: +- access to a Kubernetes cluster version `1.17+` +- `kubectl` version `1.17+` + +## Configure Custom Resource Definition + +Install Jenkins Custom Resource Definition: + +```bash +kubectl apply -f https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/deploy/crds/jenkins_v1alpha2_jenkins_crd.yaml +``` + +## Deploy Jenkins Operator + +There are two ways to deploy the Jenkins Operator. + +### Using YAML's + +Apply Service Account and RBAC roles: + +```bash +kubectl apply -f https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/config/all-in-one-v1alpha2.yaml +``` + +Watch **Jenkins Operator** instance being created: + +```bash +kubectl get pods -w +``` + +Now **Jenkins Operator** should be up and running in the `default` namespace. + +### Using Helm Chart + +There is an option to use Helm to install the operator. It requires the Helm 3+ for deployment. + +Create a namespace for the operator: + +```bash +$ kubectl create namespace +``` + +To install, you need only to type these commands: + +```bash +$ helm repo add jenkins https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart +$ helm install jenkins/jenkins-operator -n +``` + +In case you want to use released Chart **v0.4.1**, before installing/upgrading please install additional CRD into the cluster: + +```bash +$ kubectl apply -f https://raw.githubusercontent.com/jenkinsci/kubernetes-operator/master/chart/jenkins-operator/crds/jenkinsimage-crd.yaml +``` + +To add custom labels and annotations, you can use `values.yaml` file or pass them into `helm install` command, e.g.: + +```bash +$ helm install jenkins/jenkins-operator -n --set jenkins.labels.LabelKey=LabelValue,jenkins.annotations.AnnotationKey=AnnotationValue +``` +You can further customize Jenkins using `values.yaml`: +

Jenkins instance configuration +

+ + + + + + + + + + + + + + + + + +
FieldDefault valueDescription
+jenkins + +

operator is section for configuring operator deployment

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+enabled + +true + +Enabled can enable or disable the Jenkins instance. +Set to false if you have configured CR already and/or you want to deploy an operator only. +
+apiVersion +jenkins.io/v1alpha2 +Version of the CR manifest. The recommended and default value is jenkins.io/v1alpha2. +More info +
+name + +jenkins + +Name of resource. The pod name will be jenkins-<name> (name will be set as suffix). +
+namespace + +default + +Namespace the resources will be deployed to. It's not recommended to use default namespace. +Create new namespace for jenkins (e.g. kubectl create -n jenkins) +
+labels + +{} + +Labels are injected into metadata labels field. +
+annotations + +{} + +Annotations are injected into metadata annotations field. +
+image + +jenkins/jenkins:lts + +Image is the name (and tag) of the Jenkins instance. +It's recommended to use LTS (tag: "lts") version. +
+env + +[] + +Env contains jenkins container environment variables. +
+imagePullPolicy + +Always + +Defines policy for pulling images +
+priorityClassName + +"" + +PriorityClassName indicates the importance of a Pod relative to other Pods. +More info +
+disableCSRFProtection + +false + +disableCSRFProtection can enable or disable operator built-in CSRF protection. +Set it to true if you are using OpenShift Jenkins Plugin. +More info +
+imagePullSecrets + +[] + +Used if you want to pull images from private repository +More info +
+notifications + +[] + +Notifications is feature that notify user about Jenkins reconcilation status +More info +
+basePlugins + +
+- name: kubernetes
+  version: "1.25.2"
+- name: workflow-job
+  version: "2.39"
+- name: workflow-aggregator
+  version: "2.6"
+- name: git
+  version: "4.2.2"
+- name: job-dsl
+  version: "1.77"
+- name: configuration-as-code
+  version: "1.38"
+- name: kubernetes-credentials
+        -provider
+  version: "0.13"
+
+
+Plugins installed and required by the operator +shouldn't contain plugins defined by user +You can change their versions here +More info +
+plugins + +[] + +Plugins required by the user. You can define plugins here. +More info +Example: +
+plugins:
+ - name: simple-theme-plugin
+   version: 0.5.1
+
+
+seedJobs + +[] + +Placeholder for jenkins seed jobs +For seed job creation tutorial, check:
Prepare seed jobs +
Configure seed jobs +
Example: + +
+seedJobs:
+- id: jenkins-operator
+  targets: "cicd/jobs/*.jenkins"
+  description: "Jenkins Operator repository"
+  repositoryBranch: master
+  repositoryUrl: 
+  - https://github.com/jenkinsci/kubernetes-operator.git
+
+
+
+resources + +
+limits:
+  cpu: 1500m
+  memory: 3Gi
+requests:
+  cpu: 1
+  memory: 500M
+
+
+Resource limit/request for Jenkins +More info +
+volumes + +
+- name: backup
+  persistentVolumeClaim:
+    claimName: jenkins-backup
+
+
+Volumes used by Jenkins +By default, we are only using PVC volume for storing backups. +
+volumeMounts + +[] + +volumeMounts are mounts for Jenkins pod. +
+securityContext + +runAsUser: 1000 +fsGroup: 1000 + +SecurityContext for pod. +
servicenot implementedHttp Jenkins service. See https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/schema/#github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Service for details.
slaveServicenot implementedSlave Jenkins service. See https://jenkinsci.github.io/kubernetes-operator/docs/getting-started/latest/schema/#github.com/jenkinsci/kubernetes-operator/pkg/apis/jenkins/v1alpha2.Service for details.
+livenessProbe + +
+livenessProbe:
+  failureThreshold: 12
+  httpGet:
+    path: /login
+    port: http
+    scheme: HTTP
+  initialDelaySeconds: 80
+  periodSeconds: 10
+  successThreshold: 1
+  timeoutSeconds: 5
+
+
+livenessProbe for Pod +
+readinessProbe + +
+readinessProbe:
+  failureThreshold: 3
+  httpGet:
+    path: /login
+    port: http
+    scheme: HTTP
+  initialDelaySeconds: 30
+  periodSeconds: 10
+  successThreshold: 1
+  timeoutSeconds: 1
+
+
+readinessProbe for Pod +
+ +backup + +

+ + +Backup + + +

+
+ +Backup is section for configuring operator's backup feature +By default backup feature is enabled and pre-configured +This section simplifies the configuration described here: Configure backup and restore +For customization tips see Custom backup and restore +
+configuration +

+ + +Configuration + + +

+
+Section where we can configure Jenkins instance. +See Customization for details +
+
+ +### Configuring operator deployment + + + + + + + + + + + + + + + + + +
FieldDefault valueDescription
+ operator + +

operator is section for configuring operator deployment

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ replicaCount
+
+ 1 + + Number of Replicas. +
+ image + + virtuslab/jenkins-operator:v0.4.0 + + Name (and tag) of the Jenkins Operator image. +
+ imagePullPolicy + + IfNotPresent + + Defines policy for pulling images. +
+ imagePullSecrets + + [] + + Used if you want to pull images from private repository. +
+ nameOverride + + "" + + nameOverride overrides the app name. +
+ fullnameOverride + + "" + + fullnameOverride overrides the deployment name +
+ resources + + {} + +
+ nodeSelector + + {} + +
+ tolerations + + {} + +
+ affinity + + {} + +
+
+ + + +

Backup +

+

+(Appears on: +JenkinsConfiguration) +

+

+Backup defines configuration of Jenkins backup. +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDefault valueDescription
+ enabled + + true + + Enabled is enable/disable switch for backup feature. +
+ image + + virtuslab/jenkins-operator-backup-pvc:v0.0.8 + + Image used by backup feature. +
+ containerName + + backup + + Backup container name. +
+ interval + + 30 + + Defines how often make backup in seconds. +
+ makeBackupBeforePodDeletion + + true + + When enabled will make backup before pod deletion. +
+ backupCommand + + /home/user/bin/backup.sh + + Backup container command. +
+ restoreCommand + + /home/user/bin/restore.sh + + Backup restore command. +
+ pvc + +

Persistent Volume Claim Kubernetes resource

+
+ + + + + + + + + + + + + + + + + + + + + + + +
+ enabled + + true + + Enable/disable switch for PVC +
+ enabled + + true + + Enable/disable switch for PVC +
+ size + + 5Gi + + Size of PVC +
+ className + + "" + + StorageClassName for PVC + More info +
+
+ env + +
+- name: BACKUP_DIR
+  value: /backup
+- name: JENKINS_HOME
+  value: /jenkins-home
+- name: BACKUP_COUNT
+  value: "3"
+
+
+ Contains container environment variables. + PVC backup provider handles these variables:
+ BACKUP_DIR - path for storing backup files (default: "/backup")
+ JENKINS_HOME - path to jenkins home (default: "/jenkins-home")
+ BACKUP_COUNT - define how much recent backups will be kept
+
+ volumeMounts + +
+- name: jenkins-home
+  mountPath: /jenkins-home
+- mountPath: /backup
+  name: backup
+
+
+ Holds the mount points for volumes. +
+ +

Configuration +

+

+ (Appears on: + Jenkins instance configuration) +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDefault valueDescription
+ configurationAsCode + + {} + + ConfigurationAsCode defines configuration of Jenkins customization via Configuration as Code Jenkins plugin. +Example:
+
+- configMapName: jenkins-casc
+  content: {}
+
+
+ groovyScripts + + {} + + GroovyScripts defines configuration of Jenkins customization via groovy scripts. + Example:
+
+- configMapName: jenkins-gs
+  content: {}
+
+
+ secretRefName + + "" + + secretRefName of existing secret (previously created). +
+ secretData + + {} + + If secretRefName is empty, secretData creates new secret and fills with data provided in secretData. +
+ From f1ea9d8ce9e30be11e03768d894ca3057758e64f Mon Sep 17 00:00:00 2001 From: Sylwia Brant Date: Thu, 11 Feb 2021 16:10:37 +0100 Subject: [PATCH 15/15] Fix images in CRs --- config/all_in_one_v1alpha2.yaml | 4 ++-- config/manager/manager.yaml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config/all_in_one_v1alpha2.yaml b/config/all_in_one_v1alpha2.yaml index 04971a11..a8f2ef4e 100644 --- a/config/all_in_one_v1alpha2.yaml +++ b/config/all_in_one_v1alpha2.yaml @@ -195,9 +195,9 @@ spec: - /manager args: - --leader-elect - image: jenkins-operator:305dbeda-dirty-dirty + image: virtuslab/jenkins-operator:v0.5.0 name: jenkins-operator - imagePullPolicy: Never + imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false livenessProbe: diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index c615e4de..e0c31927 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -23,9 +23,9 @@ spec: - /manager args: - --leader-elect - image: jenkins-operator:305dbeda-dirty-dirty + image: virtuslab/jenkins-operator:v0.5.0 name: jenkins-operator - imagePullPolicy: Never + imagePullPolicy: IfNotPresent securityContext: allowPrivilegeEscalation: false livenessProbe: