From 61920648d8156fc55a6dc7509b40b2d013be9069 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 18 Nov 2021 16:27:48 +0900 Subject: [PATCH 01/89] Generate scaffolds Signed-off-by: zoetrope --- .dockerignore | 4 + .gitignore | 29 +- Dockerfile | 27 + Makefile | 147 +++- PROJECT | 6 + config/default/kustomization.yaml | 74 ++ config/default/manager_auth_proxy_patch.yaml | 27 + config/default/manager_config_patch.yaml | 20 + config/manager/controller_manager_config.yaml | 11 + config/manager/kustomization.yaml | 10 + config/manager/manager.yaml | 60 ++ config/prometheus/kustomization.yaml | 2 + config/prometheus/monitor.yaml | 20 + .../rbac/auth_proxy_client_clusterrole.yaml | 9 + config/rbac/auth_proxy_role.yaml | 17 + config/rbac/auth_proxy_role_binding.yaml | 12 + config/rbac/auth_proxy_service.yaml | 15 + config/rbac/kustomization.yaml | 18 + config/rbac/leader_election_role.yaml | 37 + config/rbac/leader_election_role_binding.yaml | 12 + config/rbac/role_binding.yaml | 12 + config/rbac/service_account.yaml | 5 + go.mod | 9 + go.sum | 781 ++++++++++++++++++ hack/boilerplate.go.txt | 15 + main.go | 93 +++ 26 files changed, 1429 insertions(+), 43 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 PROJECT 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/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_binding.yaml create mode 100644 config/rbac/service_account.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 hack/boilerplate.go.txt create mode 100644 main.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..0f04682 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file +# Ignore build and test binaries. +bin/ +testbin/ diff --git a/.gitignore b/.gitignore index 5ccc268..c0a7a54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,25 @@ + +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin +testbin/* + # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 -.glide/ +# Kubernetes Generated files - skip generated files, except for vendored files -# Editors -*~ -.*.swp -.#* -\#*# -/.vscode -/.idea +!vendor/**/zz_generated.* -# Generated files -/bin -/docs/book +# editor and IDE paraphernalia +.idea +*.swp +*.swo +*~ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4152680 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# Build the manager binary +FROM golang:1.16 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 main.go main.go +COPY api/ api/ +COPY controllers/ controllers/ + +# Build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 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 07ce5a8..5475a57 100644 --- a/Makefile +++ b/Makefile @@ -1,45 +1,130 @@ -BIN_DIR := $(shell pwd)/bin -# Tool versions -MDBOOK_VERSION = 0.4.10 -MDBOOK := $(BIN_DIR)/mdbook +# Image URL to use all building/pushing image targets +IMG ?= controller:latest +# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. +ENVTEST_K8S_VERSION = 1.22 -# Test tools -STATICCHECK = $(BIN_DIR)/staticcheck -NILERR = $(BIN_DIR)/nilerr +# 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 + +# Setting SHELL to bash allows bash commands to be executed by recipes. +# This is a requirement for 'setup-envtest.sh' in the test target. +# Options are set to exit when a recipe line exits non-zero or a piped command fails. +SHELL = /usr/bin/env bash -o pipefail +.SHELLFLAGS = -ec .PHONY: all -all: test +all: build -.PHONY: book -book: $(MDBOOK) - rm -rf docs/book - cd docs; $(MDBOOK) build +##@ General +# The help target prints out all targets with their descriptions organized +# beneath their categories. The categories are represented by '##@' and the +# target descriptions by '##'. The awk commands is responsible for reading the +# entire set of makefiles included in this invocation, looking for lines of the +# file as xyz: ## something, and then pretty-format the target and help. Then, +# if there's a line with ##@ something, that gets pretty-printed as a category. +# More info on the usage of ANSI control characters for terminal formatting: +# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters +# More info on the awk command: +# http://linuxcommand.org/lc3_adv_awk.php -.PHONY: test -test: test-tools - test -z "$$(gofmt -s -l . | tee /dev/stderr)" - $(STATICCHECK) ./... - test -z "$$($(NILERR) ./... 2>&1 | tee /dev/stderr)" - go install ./... - go test -race -v ./... +.PHONY: help +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + +##@ Development + +.PHONY: manifests +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + +.PHONY: generate +generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + +.PHONY: fmt +fmt: ## Run go fmt against code. + go fmt ./... + +.PHONY: vet +vet: ## Run go vet against code. go vet ./... +.PHONY: test +test: manifests generate fmt vet envtest ## Run tests. + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out + +##@ Build + +.PHONY: build +build: generate fmt vet ## Build manager binary. + go build -o bin/manager main.go + +.PHONY: run +run: manifests generate fmt vet ## Run a controller from your host. + go run ./main.go + +.PHONY: docker-build +docker-build: test ## Build docker image with the manager. + docker build -t ${IMG} . + +.PHONY: docker-push +docker-push: ## Push docker image with the manager. + docker push ${IMG} + +##@ Deployment + +ifndef ignore-not-found + ignore-not-found = false +endif + +.PHONY: install +install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. + $(KUSTOMIZE) build config/crd | kubectl apply -f - + +.PHONY: uninstall +uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - + +.PHONY: deploy +deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} + $(KUSTOMIZE) build config/default | kubectl apply -f - -##@ Tools +.PHONY: undeploy +undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. + $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - -$(MDBOOK): - mkdir -p bin - curl -fsL https://github.com/rust-lang/mdBook/releases/download/v$(MDBOOK_VERSION)/mdbook-v$(MDBOOK_VERSION)-x86_64-unknown-linux-gnu.tar.gz | tar -C bin -xzf - +CONTROLLER_GEN = $(shell pwd)/bin/controller-gen +.PHONY: controller-gen +controller-gen: ## Download controller-gen locally if necessary. + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0) -.PHONY: test-tools -test-tools: $(STATICCHECK) $(NILERR) +KUSTOMIZE = $(shell pwd)/bin/kustomize +.PHONY: kustomize +kustomize: ## Download kustomize locally if necessary. + $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) -$(STATICCHECK): - mkdir -p $(BIN_DIR) - GOBIN=$(BIN_DIR) go install honnef.co/go/tools/cmd/staticcheck@latest +ENVTEST = $(shell pwd)/bin/setup-envtest +.PHONY: envtest +envtest: ## Download envtest-setup locally if necessary. + $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) -$(NILERR): - mkdir -p $(BIN_DIR) - GOBIN=$(BIN_DIR) go install github.com/gostaticanalysis/nilerr/cmd/nilerr@latest +# 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 diff --git a/PROJECT b/PROJECT new file mode 100644 index 0000000..993ad38 --- /dev/null +++ b/PROJECT @@ -0,0 +1,6 @@ +domain: cybozu.com +layout: +- go.kubebuilder.io/v3 +projectName: neco-tenant-controller +repo: github.com/cybozu-go/neco-tenant-controller +version: "3" diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml new file mode 100644 index 0000000..b159725 --- /dev/null +++ b/config/default/kustomization.yaml @@ -0,0 +1,74 @@ +# Adds namespace to all resources. +namespace: neco-tenant-controller-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: neco-tenant-controller- + +# 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 0000000..4e2232f --- /dev/null +++ b/config/default/manager_auth_proxy_patch.yaml @@ -0,0 +1,27 @@ +# 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.8.0 + args: + - "--secure-listen-address=0.0.0.0:8443" + - "--upstream=http://127.0.0.1:8080/" + - "--logtostderr=true" + - "--v=10" + ports: + - containerPort: 8443 + protocol: TCP + 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 0000000..6c40015 --- /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 0000000..17ae949 --- /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: 309b12c3.cybozu.com diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml new file mode 100644 index 0000000..2bcd3ee --- /dev/null +++ b/config/manager/kustomization.yaml @@ -0,0 +1,10 @@ +resources: +- manager.yaml + +generatorOptions: + disableNameSuffixHash: true + +configMapGenerator: +- name: manager-config + files: + - controller_manager_config.yaml diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml new file mode 100644 index 0000000..cf11cec --- /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: + annotations: + kubectl.kubernetes.io/default-container: manager + labels: + control-plane: controller-manager + spec: + securityContext: + runAsNonRoot: true + 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 + # TODO(user): Configure the resources accordingly based on the project requirements. + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + serviceAccountName: controller-manager + terminationGracePeriodSeconds: 10 diff --git a/config/prometheus/kustomization.yaml b/config/prometheus/kustomization.yaml new file mode 100644 index 0000000..ed13716 --- /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 0000000..d19136a --- /dev/null +++ b/config/prometheus/monitor.yaml @@ -0,0 +1,20 @@ + +# 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 + scheme: https + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token + tlsConfig: + insecureSkipVerify: true + 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 0000000..51a75db --- /dev/null +++ b/config/rbac/auth_proxy_client_clusterrole.yaml @@ -0,0 +1,9 @@ +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 0000000..80e1857 --- /dev/null +++ b/config/rbac/auth_proxy_role.yaml @@ -0,0 +1,17 @@ +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 0000000..ec7acc0 --- /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: controller-manager + namespace: system diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml new file mode 100644 index 0000000..71f1797 --- /dev/null +++ b/config/rbac/auth_proxy_service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + labels: + control-plane: controller-manager + name: controller-manager-metrics-service + namespace: system +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + control-plane: controller-manager diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml new file mode 100644 index 0000000..731832a --- /dev/null +++ b/config/rbac/kustomization.yaml @@ -0,0 +1,18 @@ +resources: +# All RBAC will be applied under this service account in +# the deployment namespace. You may comment out this resource +# if your manager will use a service account that exists at +# runtime. Be sure to update RoleBinding and ClusterRoleBinding +# subjects if changing service account names. +- service_account.yaml +- 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 0000000..4190ec8 --- /dev/null +++ b/config/rbac/leader_election_role.yaml @@ -0,0 +1,37 @@ +# permissions to do leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: leader-election-role +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - 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 0000000..1d1321e --- /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: controller-manager + namespace: system diff --git a/config/rbac/role_binding.yaml b/config/rbac/role_binding.yaml new file mode 100644 index 0000000..2070ede --- /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: controller-manager + namespace: system diff --git a/config/rbac/service_account.yaml b/config/rbac/service_account.yaml new file mode 100644 index 0000000..7cd6025 --- /dev/null +++ b/config/rbac/service_account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: controller-manager + namespace: system diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1c05c9c --- /dev/null +++ b/go.mod @@ -0,0 +1,9 @@ +module github.com/cybozu-go/neco-tenant-controller + +go 1.16 + +require ( + k8s.io/apimachinery v0.22.1 + k8s.io/client-go v0.22.1 + sigs.k8s.io/controller-runtime v0.10.0 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..80d4bda --- /dev/null +++ b/go.sum @@ -0,0 +1,781 @@ +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.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.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/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +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.11.18 h1:90Y4srNYrwOtAgVo3ndrQkTYn6kf1Eg/AjTFJ8Is2aM= +github.com/Azure/go-autorest/autorest v0.11.18/go.mod h1:dSiJPy22c3u0OtOKDNttNgqpNFY/GeWa7GH/Pz56QRA= +github.com/Azure/go-autorest/autorest/adal v0.9.13 h1:Mp5hbtOePIzM8pJVRa3YLrWWmZtoxRXqUEzCfJt3+/Q= +github.com/Azure/go-autorest/autorest/adal v0.9.13/go.mod h1:W/MM4U6nLxnIskrw4UwWzlHfGjwUS50aOsc/I3yuU8M= +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.4.1 h1:K0laFcLE6VLTOwNgSxaGbUcLPuGXlNkbVvq4cW4nIHk= +github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= +github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+ZtXWSmf4Tg= +github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= +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/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +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/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +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/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= +github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= +github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= +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.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= +github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= +github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= +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/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +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/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= +github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= +github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= +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/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +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-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-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +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-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.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= +github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= +github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= +github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/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/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +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/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/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +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/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +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.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +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/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.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= +github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= +github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +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.3.0/go.mod h1:z0ButlSOZa5vEBq9m2m2hlwIgKw+rp3sdCBRoJY+30Y= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +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 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-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.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/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +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/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.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/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/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +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/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +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.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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +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/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= +github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= +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/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +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/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +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.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= +github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +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.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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/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-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +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.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +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/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +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/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +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/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +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.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +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/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/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= +go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= +go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= +go.opentelemetry.io/otel v0.20.0/go.mod h1:Y3ugLH2oa81t5QO+Lty+zXf8zC9L26ax4Nzoxm/dooo= +go.opentelemetry.io/otel/exporters/otlp v0.20.0/go.mod h1:YIieizyaN77rtLJra0buKiNBOm9XQfkPEKBeuhoMwAM= +go.opentelemetry.io/otel/metric v0.20.0/go.mod h1:598I5tYlH1vzBjn+BTuhzTCSb/9debfNp6R3s7Pr1eU= +go.opentelemetry.io/otel/oteltest v0.20.0/go.mod h1:L7bgKf9ZB7qCwT9Up7i9/pn0PWIa9FqQ2IQ8LoxiGnw= +go.opentelemetry.io/otel/sdk v0.20.0/go.mod h1:g/IcepuwNsoiX5Byy2nNV0ySUF1em498m7hBWC279Yc= +go.opentelemetry.io/otel/sdk/export/metric v0.20.0/go.mod h1:h7RBNMsDJ5pmI1zExLi+bJK+Dr8NQCh0qGhm1KDnNlE= +go.opentelemetry.io/otel/sdk/metric v0.20.0/go.mod h1:knxiS8Xd4E/N+ZqKmUPf3gTTZ4/0TjTXukfxjzSTpHE= +go.opentelemetry.io/otel/trace v0.20.0/go.mod h1:6GjCW8zgDjwGHGa6GkyeB8+/5vjT16gUEi0Nf1iBdgw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +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.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= +go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +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/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/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +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/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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.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.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20181023162649-9b4f9f5ad519/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-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-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/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-20190724013045-ca1201d0de80/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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +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-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= +golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +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/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +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-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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/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= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/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-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-20191204072324-ce4227a45e2e/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-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-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= +golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= +golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +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.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac h1:7zkz7BUtwNFFqcowJ+RIgu2MaV/MapERkDIy+mwPyjs= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/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-20180917221912-90fa682c2a6e/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-20190114222345-bf090417da8b/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-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-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-20190506145303-2d16b83fe98c/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-20190621195816-6e04913cbbac/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-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-20191012152004-8de300cfc20a/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-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-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-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-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= +gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +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.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +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.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/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-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-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +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-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-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +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.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +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/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +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/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +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/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +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.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= +gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= +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.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY= +k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= +k8s.io/apiextensions-apiserver v0.22.1 h1:YSJYzlFNFSfUle+yeEXX0lSQyLEoxoPJySRupepb0gE= +k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c= +k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM= +k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400= +k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw= +k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= +k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= +k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= +k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= +k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= +k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= +k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= +k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= +k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= +k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +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.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= +sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= +sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= +sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= +sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= +sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt new file mode 100644 index 0000000..45dbbbb --- /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 0000000..c2150d3 --- /dev/null +++ b/main.go @@ -0,0 +1,93 @@ +/* +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" + "os" + + // 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" + + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + //+kubebuilder:scaffold:imports +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("setup") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + + //+kubebuilder:scaffold:scheme +} + +func main() { + var metricsAddr string + var enableLeaderElection bool + var probeAddr string + 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", false, + "Enable leader election for controller manager. "+ + "Enabling this will ensure there is only one active controller manager.") + opts := zap.Options{ + Development: true, + } + opts.BindFlags(flag.CommandLine) + flag.Parse() + + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: metricsAddr, + Port: 9443, + HealthProbeBindAddress: probeAddr, + LeaderElection: enableLeaderElection, + LeaderElectionID: "309b12c3.cybozu.com", + }) + if err != nil { + setupLog.Error(err, "unable to start manager") + os.Exit(1) + } + + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem running manager") + os.Exit(1) + } +} From 5624d2ea9c07bc687a595102401cc28ddf6efc36 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 18 Nov 2021 16:32:30 +0900 Subject: [PATCH 02/89] Generate api Signed-off-by: zoetrope --- PROJECT | 10 ++ api/v1beta1/groupversion_info.go | 36 ++++++ api/v1beta1/tenant_types.go | 64 ++++++++++ api/v1beta1/zz_generated.deepcopy.go | 114 ++++++++++++++++++ .../multi-tenancy.cybozu.com_tenants.yaml | 57 +++++++++ config/crd/kustomization.yaml | 21 ++++ config/crd/kustomizeconfig.yaml | 19 +++ .../crd/patches/cainjection_in_tenants.yaml | 7 ++ config/crd/patches/webhook_in_tenants.yaml | 16 +++ config/rbac/role.yaml | 34 ++++++ config/rbac/tenant_editor_role.yaml | 24 ++++ config/rbac/tenant_viewer_role.yaml | 20 +++ .../samples/multi-tenancy_v1beta1_tenant.yaml | 6 + controllers/suite_test.go | 80 ++++++++++++ controllers/tenant_controller.go | 62 ++++++++++ go.mod | 2 + main.go | 11 ++ 17 files changed, 583 insertions(+) create mode 100644 api/v1beta1/groupversion_info.go create mode 100644 api/v1beta1/tenant_types.go create mode 100644 api/v1beta1/zz_generated.deepcopy.go create mode 100644 config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml create mode 100644 config/crd/kustomization.yaml create mode 100644 config/crd/kustomizeconfig.yaml create mode 100644 config/crd/patches/cainjection_in_tenants.yaml create mode 100644 config/crd/patches/webhook_in_tenants.yaml create mode 100644 config/rbac/role.yaml create mode 100644 config/rbac/tenant_editor_role.yaml create mode 100644 config/rbac/tenant_viewer_role.yaml create mode 100644 config/samples/multi-tenancy_v1beta1_tenant.yaml create mode 100644 controllers/suite_test.go create mode 100644 controllers/tenant_controller.go diff --git a/PROJECT b/PROJECT index 993ad38..fa94325 100644 --- a/PROJECT +++ b/PROJECT @@ -3,4 +3,14 @@ layout: - go.kubebuilder.io/v3 projectName: neco-tenant-controller repo: github.com/cybozu-go/neco-tenant-controller +resources: +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: cybozu.com + group: multi-tenancy + kind: Tenant + path: github.com/cybozu-go/neco-tenant-controller/api/v1beta1 + version: v1beta1 version: "3" diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go new file mode 100644 index 0000000..436f3c2 --- /dev/null +++ b/api/v1beta1/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 v1beta1 contains API Schema definitions for the multi-tenancy v1beta1 API group +//+kubebuilder:object:generate=true +//+groupName=multi-tenancy.cybozu.com +package v1beta1 + +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: "multi-tenancy.cybozu.com", Version: "v1beta1"} + + // 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/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go new file mode 100644 index 0000000..6f69add --- /dev/null +++ b/api/v1beta1/tenant_types.go @@ -0,0 +1,64 @@ +/* +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 v1beta1 + +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. + +// TenantSpec defines the desired state of Tenant +type TenantSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Foo is an example field of Tenant. Edit tenant_types.go to remove/update + Foo string `json:"foo,omitempty"` +} + +// TenantStatus defines the observed state of Tenant +type TenantStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// Tenant is the Schema for the tenants API +type Tenant struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec TenantSpec `json:"spec,omitempty"` + Status TenantStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// TenantList contains a list of Tenant +type TenantList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Tenant `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Tenant{}, &TenantList{}) +} diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 0000000..dcdd50a --- /dev/null +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,114 @@ +// +build !ignore_autogenerated + +/* +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 v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Tenant) DeepCopyInto(out *Tenant) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + out.Status = in.Status +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tenant. +func (in *Tenant) DeepCopy() *Tenant { + if in == nil { + return nil + } + out := new(Tenant) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Tenant) 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 *TenantList) DeepCopyInto(out *TenantList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Tenant, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantList. +func (in *TenantList) DeepCopy() *TenantList { + if in == nil { + return nil + } + out := new(TenantList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *TenantList) 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 *TenantSpec) DeepCopyInto(out *TenantSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec. +func (in *TenantSpec) DeepCopy() *TenantSpec { + if in == nil { + return nil + } + out := new(TenantSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantStatus) DeepCopyInto(out *TenantStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus. +func (in *TenantStatus) DeepCopy() *TenantStatus { + if in == nil { + return nil + } + out := new(TenantStatus) + in.DeepCopyInto(out) + return out +} diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml new file mode 100644 index 0000000..6791d22 --- /dev/null +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -0,0 +1,57 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: tenants.multi-tenancy.cybozu.com +spec: + group: multi-tenancy.cybozu.com + names: + kind: Tenant + listKind: TenantList + plural: tenants + singular: tenant + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants 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: TenantSpec defines the desired state of Tenant + properties: + foo: + description: Foo is an example field of Tenant. Edit tenant_types.go + to remove/update + type: string + type: object + status: + description: TenantStatus defines the observed state of Tenant + 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 0000000..432fc33 --- /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/multi-tenancy.cybozu.com_tenants.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_tenants.yaml +#+kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +#- patches/cainjection_in_tenants.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 0000000..ec5c150 --- /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_tenants.yaml b/config/crd/patches/cainjection_in_tenants.yaml new file mode 100644 index 0000000..8ab5ede --- /dev/null +++ b/config/crd/patches/cainjection_in_tenants.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: tenants.multi-tenancy.cybozu.com diff --git a/config/crd/patches/webhook_in_tenants.yaml b/config/crd/patches/webhook_in_tenants.yaml new file mode 100644 index 0000000..fd8f900 --- /dev/null +++ b/config/crd/patches/webhook_in_tenants.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tenants.multi-tenancy.cybozu.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml new file mode 100644 index 0000000..cc0d736 --- /dev/null +++ b/config/rbac/role.yaml @@ -0,0 +1,34 @@ + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + name: manager-role +rules: +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants/finalizers + verbs: + - update +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants/status + verbs: + - get + - patch + - update diff --git a/config/rbac/tenant_editor_role.yaml b/config/rbac/tenant_editor_role.yaml new file mode 100644 index 0000000..d94921c --- /dev/null +++ b/config/rbac/tenant_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit tenants. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tenant-editor-role +rules: +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants/status + verbs: + - get diff --git a/config/rbac/tenant_viewer_role.yaml b/config/rbac/tenant_viewer_role.yaml new file mode 100644 index 0000000..e561876 --- /dev/null +++ b/config/rbac/tenant_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view tenants. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: tenant-viewer-role +rules: +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants + verbs: + - get + - list + - watch +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants/status + verbs: + - get diff --git a/config/samples/multi-tenancy_v1beta1_tenant.yaml b/config/samples/multi-tenancy_v1beta1_tenant.yaml new file mode 100644 index 0000000..8450a65 --- /dev/null +++ b/config/samples/multi-tenancy_v1beta1_tenant.yaml @@ -0,0 +1,6 @@ +apiVersion: multi-tenancy.cybozu.com/v1beta1 +kind: Tenant +metadata: + name: tenant-sample +spec: + # TODO(user): Add fields here diff --git a/controllers/suite_test.go b/controllers/suite_test.go new file mode 100644 index 0000000..f747677 --- /dev/null +++ b/controllers/suite_test.go @@ -0,0 +1,80 @@ +/* +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" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "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" + + multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + //+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 +var k8sClient client.Client +var testEnv *envtest.Environment + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Controller Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: true, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + err = multitenancyv1beta1.AddToScheme(scheme.Scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + +}, 60) + +var _ = AfterSuite(func() { + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go new file mode 100644 index 0000000..521b4b4 --- /dev/null +++ b/controllers/tenant_controller.go @@ -0,0 +1,62 @@ +/* +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" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" +) + +// TenantReconciler reconciles a Tenant object +type TenantReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the Tenant object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile +func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&multitenancyv1beta1.Tenant{}). + Complete(r) +} diff --git a/go.mod b/go.mod index 1c05c9c..8d90097 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,8 @@ module github.com/cybozu-go/neco-tenant-controller go 1.16 require ( + github.com/onsi/ginkgo v1.16.4 + github.com/onsi/gomega v1.15.0 k8s.io/apimachinery v0.22.1 k8s.io/client-go v0.22.1 sigs.k8s.io/controller-runtime v0.10.0 diff --git a/main.go b/main.go index c2150d3..b621ed4 100644 --- a/main.go +++ b/main.go @@ -30,6 +30,9 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + + multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/controllers" //+kubebuilder:scaffold:imports ) @@ -41,6 +44,7 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(multitenancyv1beta1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -74,6 +78,13 @@ func main() { os.Exit(1) } + if err = (&controllers.TenantReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "Tenant") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { From 9f710d21417938cc0c4e181fdfdc0595888752a8 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 18 Nov 2021 16:37:35 +0900 Subject: [PATCH 03/89] Generate webhook for Tenant Signed-off-by: zoetrope --- PROJECT | 4 + api/v1beta1/tenant_webhook.go | 75 +++++++++++ api/v1beta1/webhook_suite_test.go | 133 +++++++++++++++++++ api/v1beta1/zz_generated.deepcopy.go | 2 +- config/certmanager/certificate.yaml | 25 ++++ config/certmanager/kustomization.yaml | 5 + config/certmanager/kustomizeconfig.yaml | 16 +++ config/default/manager_webhook_patch.yaml | 23 ++++ config/default/webhookcainjection_patch.yaml | 15 +++ config/webhook/kustomization.yaml | 6 + config/webhook/kustomizeconfig.yaml | 25 ++++ config/webhook/manifests.yaml | 56 ++++++++ config/webhook/service.yaml | 13 ++ go.mod | 1 + main.go | 4 + 15 files changed, 402 insertions(+), 1 deletion(-) create mode 100644 api/v1beta1/tenant_webhook.go create mode 100644 api/v1beta1/webhook_suite_test.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/default/manager_webhook_patch.yaml create mode 100644 config/default/webhookcainjection_patch.yaml create mode 100644 config/webhook/kustomization.yaml create mode 100644 config/webhook/kustomizeconfig.yaml create mode 100644 config/webhook/manifests.yaml create mode 100644 config/webhook/service.yaml diff --git a/PROJECT b/PROJECT index fa94325..fdcecd2 100644 --- a/PROJECT +++ b/PROJECT @@ -13,4 +13,8 @@ resources: kind: Tenant path: github.com/cybozu-go/neco-tenant-controller/api/v1beta1 version: v1beta1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 version: "3" diff --git a/api/v1beta1/tenant_webhook.go b/api/v1beta1/tenant_webhook.go new file mode 100644 index 0000000..5ec78fc --- /dev/null +++ b/api/v1beta1/tenant_webhook.go @@ -0,0 +1,75 @@ +/* +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 v1beta1 + +import ( + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" +) + +// log is for logging in this package. +var tenantlog = logf.Log.WithName("tenant-resource") + +func (r *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! + +//+kubebuilder:webhook:path=/mutate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=mtenant.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &Tenant{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *Tenant) Default() { + tenantlog.Info("default", "name", r.Name) + + // TODO(user): fill in your defaulting logic. +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//+kubebuilder:webhook:path=/validate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &Tenant{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *Tenant) ValidateCreate() error { + tenantlog.Info("validate create", "name", r.Name) + + // TODO(user): fill in your validation logic upon object creation. + return nil +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *Tenant) ValidateUpdate(old runtime.Object) error { + tenantlog.Info("validate update", "name", r.Name) + + // TODO(user): fill in your validation logic upon object update. + return nil +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *Tenant) ValidateDelete() error { + tenantlog.Info("validate delete", "name", r.Name) + + // TODO(user): fill in your validation logic upon object deletion. + return nil +} diff --git a/api/v1beta1/webhook_suite_test.go b/api/v1beta1/webhook_suite_test.go new file mode 100644 index 0000000..a508891 --- /dev/null +++ b/api/v1beta1/webhook_suite_test.go @@ -0,0 +1,133 @@ +/* +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 v1beta1 + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "path/filepath" + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + admissionv1beta1 "k8s.io/api/admission/v1beta1" + //+kubebuilder:scaffold:imports + "k8s.io/apimachinery/pkg/runtime" + "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" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +// 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 +var k8sClient client.Client +var testEnv *envtest.Environment +var ctx context.Context +var cancel context.CancelFunc + +func TestAPIs(t *testing.T) { + RegisterFailHandler(Fail) + + RunSpecsWithDefaultAndCustomReporters(t, + "Webhook Suite", + []Reporter{printer.NewlineReporter{}}) +} + +var _ = BeforeSuite(func() { + logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) + + ctx, cancel = context.WithCancel(context.TODO()) + + By("bootstrapping test environment") + testEnv = &envtest.Environment{ + CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + ErrorIfCRDPathMissing: false, + WebhookInstallOptions: envtest.WebhookInstallOptions{ + Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + }, + } + + cfg, err := testEnv.Start() + Expect(err).NotTo(HaveOccurred()) + Expect(cfg).NotTo(BeNil()) + + scheme := runtime.NewScheme() + err = AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = admissionv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:scheme + + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient).NotTo(BeNil()) + + // start webhook server using Manager + webhookInstallOptions := &testEnv.WebhookInstallOptions + mgr, err := ctrl.NewManager(cfg, ctrl.Options{ + Scheme: scheme, + Host: webhookInstallOptions.LocalServingHost, + Port: webhookInstallOptions.LocalServingPort, + CertDir: webhookInstallOptions.LocalServingCertDir, + LeaderElection: false, + MetricsBindAddress: "0", + }) + Expect(err).NotTo(HaveOccurred()) + + err = (&Tenant{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + + //+kubebuilder:scaffold:webhook + + go func() { + defer GinkgoRecover() + err = mgr.Start(ctx) + Expect(err).NotTo(HaveOccurred()) + }() + + // wait for the webhook server to get ready + dialer := &net.Dialer{Timeout: time.Second} + addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) + Eventually(func() error { + conn, err := tls.DialWithDialer(dialer, "tcp", addrPort, &tls.Config{InsecureSkipVerify: true}) + if err != nil { + return err + } + conn.Close() + return nil + }).Should(Succeed()) + +}, 60) + +var _ = AfterSuite(func() { + cancel() + By("tearing down the test environment") + err := testEnv.Stop() + Expect(err).NotTo(HaveOccurred()) +}) diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index dcdd50a..3fef378 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ limitations under the License. package v1beta1 import ( - runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. diff --git a/config/certmanager/certificate.yaml b/config/certmanager/certificate.yaml new file mode 100644 index 0000000..52d8661 --- /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 0000000..bebea5a --- /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 0000000..90d7c31 --- /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/default/manager_webhook_patch.yaml b/config/default/manager_webhook_patch.yaml new file mode 100644 index 0000000..738de35 --- /dev/null +++ b/config/default/manager_webhook_patch.yaml @@ -0,0 +1,23 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + containers: + - name: manager + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert diff --git a/config/default/webhookcainjection_patch.yaml b/config/default/webhookcainjection_patch.yaml new file mode 100644 index 0000000..02ab515 --- /dev/null +++ b/config/default/webhookcainjection_patch.yaml @@ -0,0 +1,15 @@ +# This patch add annotation to admission webhook config and +# the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) diff --git a/config/webhook/kustomization.yaml b/config/webhook/kustomization.yaml new file mode 100644 index 0000000..9cf2613 --- /dev/null +++ b/config/webhook/kustomization.yaml @@ -0,0 +1,6 @@ +resources: +- manifests.yaml +- service.yaml + +configurations: +- kustomizeconfig.yaml diff --git a/config/webhook/kustomizeconfig.yaml b/config/webhook/kustomizeconfig.yaml new file mode 100644 index 0000000..25e21e3 --- /dev/null +++ b/config/webhook/kustomizeconfig.yaml @@ -0,0 +1,25 @@ +# the following config is for teaching kustomize where to look at when substituting vars. +# It requires kustomize v2.1.0 or newer to work properly. +nameReference: +- kind: Service + version: v1 + fieldSpecs: + - kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + - kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/name + +namespace: +- kind: MutatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true +- kind: ValidatingWebhookConfiguration + group: admissionregistration.k8s.io + path: webhooks/clientConfig/service/namespace + create: true + +varReference: +- path: metadata/annotations diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml new file mode 100644 index 0000000..49f529b --- /dev/null +++ b/config/webhook/manifests.yaml @@ -0,0 +1,56 @@ + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + creationTimestamp: null + name: mutating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-multi-tenancy-cybozu-com-v1beta1-tenant + failurePolicy: Fail + name: mtenant.kb.io + rules: + - apiGroups: + - multi-tenancy.cybozu.com + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - tenants + sideEffects: None + +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + creationTimestamp: null + name: validating-webhook-configuration +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-multi-tenancy-cybozu-com-v1beta1-tenant + failurePolicy: Fail + name: vtenant.kb.io + rules: + - apiGroups: + - multi-tenancy.cybozu.com + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - tenants + sideEffects: None diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml new file mode 100644 index 0000000..3f638bd --- /dev/null +++ b/config/webhook/service.yaml @@ -0,0 +1,13 @@ + +apiVersion: v1 +kind: Service +metadata: + name: webhook-service + namespace: system +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + control-plane: controller-manager diff --git a/go.mod b/go.mod index 8d90097..1f41d5e 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.16 require ( github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.15.0 + k8s.io/api v0.22.1 k8s.io/apimachinery v0.22.1 k8s.io/client-go v0.22.1 sigs.k8s.io/controller-runtime v0.10.0 diff --git a/main.go b/main.go index b621ed4..ba4ce76 100644 --- a/main.go +++ b/main.go @@ -85,6 +85,10 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "Tenant") os.Exit(1) } + if err = (&multitenancyv1beta1.Tenant{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "Tenant") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { From 88c884bd1469becc6d10d21d78a6c6e09cbdabf4 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 18 Nov 2021 17:37:55 +0900 Subject: [PATCH 04/89] Setup project Signed-off-by: zoetrope --- .dockerignore | 11 +- .github/workflows/ci.yaml | 36 ++++- .github/workflows/mdbook.yaml | 48 ------- .github/workflows/release.yaml | 23 +++ .gitignore | 30 ++-- Dockerfile | 31 ++-- Makefile | 142 +++++++++---------- api/v1beta1/zz_generated.deepcopy.go | 16 --- cmd/neco-tenant-controller/main.go | 9 ++ cmd/neco-tenant-controller/sub/root.go | 76 ++++++++++ cmd/neco-tenant-controller/sub/run.go | 85 +++++++++++ config/multi-tenancy.cybozu.com_tenants.yaml | 57 ++++++++ e2e/Makefile | 56 ++++++++ e2e/e2e_test.go | 10 ++ e2e/env_test.go | 8 ++ e2e/kind-config.yaml | 6 + e2e/run_test.go | 31 ++++ e2e/suite_test.go | 19 +++ go.mod | 11 +- go.sum | 37 ++--- hack/boilerplate.go.txt | 15 -- main.go | 108 -------------- pkg/config/types.go | 22 +++ version.go | 4 + 24 files changed, 565 insertions(+), 326 deletions(-) delete mode 100644 .github/workflows/mdbook.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 cmd/neco-tenant-controller/main.go create mode 100644 cmd/neco-tenant-controller/sub/root.go create mode 100644 cmd/neco-tenant-controller/sub/run.go create mode 100644 config/multi-tenancy.cybozu.com_tenants.yaml create mode 100644 e2e/Makefile create mode 100644 e2e/e2e_test.go create mode 100644 e2e/env_test.go create mode 100644 e2e/kind-config.yaml create mode 100644 e2e/run_test.go create mode 100644 e2e/suite_test.go delete mode 100644 main.go create mode 100644 pkg/config/types.go create mode 100644 version.go diff --git a/.dockerignore b/.dockerignore index 0f04682..573b6c3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,7 @@ -# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file -# Ignore build and test binaries. -bin/ -testbin/ +/bin +/config +/docs +/e2e +/hack +/.git +/dist diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 08b3b06..bcfc58e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -5,9 +5,18 @@ on: branches: - 'main' env: - go-version: 1.16 + go-version: 1.17 cache-version: 1 jobs: + build: + name: Build binaries + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: ${{ env.go-version }} + - run: make build test: name: Small tests runs-on: ubuntu-20.04 @@ -17,3 +26,28 @@ jobs: with: go-version: ${{ env.go-version }} - run: make test + - run: make check-generate + - run: make envtest + e2e: + name: End-to-End Tests + strategy: + matrix: + k8s-version: ["1.22.2"] + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: ${{ env.go-version }} + - run: make start KUBERNETES_VERSION=${{ matrix.k8s-version }} + working-directory: e2e + - run: make test + working-directory: e2e + - run: make logs + working-directory: e2e + if: always() + - uses: actions/upload-artifact@v2 + if: always() + with: + name: logs-${{ matrix.k8s-version }}.tar.gz + path: e2e/logs.tar.gz diff --git a/.github/workflows/mdbook.yaml b/.github/workflows/mdbook.yaml deleted file mode 100644 index 1ad6bcf..0000000 --- a/.github/workflows/mdbook.yaml +++ /dev/null @@ -1,48 +0,0 @@ -name: Book -on: - pull_request: - push: - branches: - - 'main' -jobs: - build: - name: Build book - runs-on: ubuntu-20.04 - steps: - - uses: actions/checkout@v2 - - run: make book - - uses: actions/upload-artifact@v2 - with: - name: book - path: docs/book - retention-days: 1 - publish: - name: Publish book on GitHub Pages - runs-on: ubuntu-20.04 - needs: build - steps: - - uses: actions/checkout@v2 - with: - ref: gh-pages - - run: rm -rf * - - uses: actions/download-artifact@v2 - with: - name: book - - run: git add . - - name: Check diff - run: | - diffs=$(git status -s) - if [ "$diffs" = "" ]; then - echo "NO_DIFF=1" >> $GITHUB_ENV - else - printf "%s\n" "$diffs" - fi - - name: Commit changes - if: env.NO_DIFF != '1' - run: | - git config --global user.name 'Cybozu Neco' - git config --global user.email 'cybozu-neco@users.noreply.github.com' - git commit -m 'update' - - name: Push to gh-pages - if: github.ref == 'refs/heads/main' && env.NO_DIFF != '1' - run: git push origin gh-pages diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..b1af93d --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,23 @@ +name: Release +on: + push: + tags: + - 'v*' +env: + go-version: 1.17 +jobs: + image: + name: Push Container Image + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Build images + run: | + docker build -t neco-tenant-controller:dev . + - name: Login to ghcr.io + run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin + - name: Push images + run: | + TAG=${GITHUB_REF#refs/tags/v} + docker tag neco-tenant-controller:dev ghcr.io/cybozu-go/neco-tenant-controller:$TAG + docker push ghcr.io/cybozu-go/neco-tenant-controller:$TAG diff --git a/.gitignore b/.gitignore index c0a7a54..3058053 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,21 @@ - -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib -bin -testbin/* - # Test binary, build with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Kubernetes Generated files - skip generated files, except for vendored files +# Editors +*~ +.*.swp +.#* +\#*# +/.vscode +/.idea -!vendor/**/zz_generated.* +# Binaries +/bin +/build +/dist -# editor and IDE paraphernalia -.idea -*.swp -*.swo -*~ +# go mod +/vendor diff --git a/Dockerfile b/Dockerfile index 4152680..e1b7645 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,14 @@ # Build the manager binary -FROM golang:1.16 as builder +FROM quay.io/cybozu/golang:1.17-focal 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 ./ . +RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o neco-tenant-controller ./cmd/neco-tenant-controller -# Copy the go source -COPY main.go main.go -COPY api/ api/ -COPY controllers/ controllers/ +# the controller image +FROM scratch +LABEL org.opencontainers.image.source https://github.com/cybozu-go/neco-tenant -# Build -RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go +COPY --from=builder /work/neco-tenant-controller ./ +USER 10000:10000 -# 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"] +ENTRYPOINT ["/neco-tenant-controller"] diff --git a/Makefile b/Makefile index 5475a57..b213fd0 100644 --- a/Makefile +++ b/Makefile @@ -1,21 +1,24 @@ +# Tool versions +CTRL_TOOLS_VERSION=0.7.0 +CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) +KUSTOMIZE_VERSION = 4.4.1 -# Image URL to use all building/pushing image targets -IMG ?= controller:latest -# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.22 +# Test tools +BIN_DIR := $(shell pwd)/bin +STATICCHECK := $(BIN_DIR)/staticcheck +NILERR := $(BIN_DIR)/nilerr +SUDO = sudo -# 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 +# Set the shell used to bash for better error handling. +SHELL = /bin/bash +.SHELLFLAGS = -e -o pipefail -c -# Setting SHELL to bash allows bash commands to be executed by recipes. -# This is a requirement for 'setup-envtest.sh' in the test target. -# Options are set to exit when a recipe line exits non-zero or a piped command fails. -SHELL = /usr/bin/env bash -o pipefail -.SHELLFLAGS = -ec +CRD_OPTIONS = "crd:crdVersions=v1,maxDescLen=220" + +# for Go +GOOS = $(shell go env GOOS) +GOARCH = $(shell go env GOARCH) +SUFFIX = .PHONY: all all: build @@ -40,91 +43,82 @@ help: ## Display this help. ##@ Development .PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases +manifests: kustomize controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." -.PHONY: fmt -fmt: ## Run go fmt against code. - go fmt ./... +.PHONY: check-generate +check-generate: + $(MAKE) manifests generate apidoc + git diff --exit-code --name-only -.PHONY: vet -vet: ## Run go vet against code. - go vet ./... +.PHONY: envtest +envtest: setup-envtest + source <($(SETUP_ENVTEST) use -p env); \ + go test -v -count 1 -race ./controllers -ginkgo.progress -ginkgo.v -ginkgo.failFast .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -coverprofile cover.out +test: test-tools + go test -v -count 1 -race ./pkg/... + go install ./... + go vet ./... + test -z $$(gofmt -s -l . | tee /dev/stderr) + $(STATICCHECK) ./... + $(NILERR) ./... ##@ Build .PHONY: build -build: generate fmt vet ## Build manager binary. - go build -o bin/manager main.go - -.PHONY: run -run: manifests generate fmt vet ## Run a controller from your host. - go run ./main.go +build: + mkdir -p bin + GOBIN=$(shell pwd)/bin go install ./cmd/... .PHONY: docker-build -docker-build: test ## Build docker image with the manager. - docker build -t ${IMG} . - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - docker push ${IMG} - -##@ Deployment - -ifndef ignore-not-found - ignore-not-found = false -endif +docker-build: + docker build -t neco-tenant-controller:latest . -.PHONY: install -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | kubectl apply -f - +##@ Tools -.PHONY: uninstall -uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/crd | kubectl delete --ignore-not-found=$(ignore-not-found) -f - - -.PHONY: deploy -deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default | kubectl apply -f - - -.PHONY: undeploy -undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. - $(KUSTOMIZE) build config/default | kubectl delete --ignore-not-found=$(ignore-not-found) -f - - -CONTROLLER_GEN = $(shell pwd)/bin/controller-gen -.PHONY: controller-gen +CONTROLLER_GEN := $(shell pwd)/bin/controller-gen controller-gen: ## Download controller-gen locally if necessary. - $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.7.0) + $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v$(CTRL_TOOLS_VERSION)) + +SETUP_ENVTEST := $(shell pwd)/bin/setup-envtest +.PHONY: setup-envtest +setup-envtest: $(SETUP_ENVTEST) ## Download setup-envtest locally if necessary +$(SETUP_ENVTEST): + # see https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest + GOBIN=$(shell pwd)/bin go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest -KUSTOMIZE = $(shell pwd)/bin/kustomize +KUSTOMIZE := $(shell pwd)/bin/kustomize .PHONY: kustomize -kustomize: ## Download kustomize locally if necessary. - $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. -ENVTEST = $(shell pwd)/bin/setup-envtest -.PHONY: envtest -envtest: ## Download envtest-setup locally if necessary. - $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) +$(KUSTOMIZE): + mkdir -p bin + curl -fsL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv$(KUSTOMIZE_VERSION)/kustomize_v$(KUSTOMIZE_VERSION)_linux_amd64.tar.gz | \ + tar -C bin -xzf - # 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 ;\ +GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ } endef + +.PHONY: test-tools +test-tools: $(STATICCHECK) $(NILERR) + +$(STATICCHECK): + mkdir -p $(BIN_DIR) + GOBIN=$(BIN_DIR) go install honnef.co/go/tools/cmd/staticcheck@latest + +$(NILERR): + mkdir -p $(BIN_DIR) + GOBIN=$(BIN_DIR) go install github.com/gostaticanalysis/nilerr/cmd/nilerr@latest diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 3fef378..c24af61 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,21 +1,5 @@ // +build !ignore_autogenerated -/* -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 v1beta1 diff --git a/cmd/neco-tenant-controller/main.go b/cmd/neco-tenant-controller/main.go new file mode 100644 index 0000000..e93354a --- /dev/null +++ b/cmd/neco-tenant-controller/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/cybozu-go/neco-tenant-controller/cmd/neco-tenant-controller/sub" +) + +func main() { + sub.Execute() +} diff --git a/cmd/neco-tenant-controller/sub/root.go b/cmd/neco-tenant-controller/sub/root.go new file mode 100644 index 0000000..4b122fd --- /dev/null +++ b/cmd/neco-tenant-controller/sub/root.go @@ -0,0 +1,76 @@ +package sub + +import ( + "errors" + "flag" + "fmt" + "net" + "os" + "strconv" + + tenant "github.com/cybozu-go/neco-tenant-controller" + "github.com/spf13/cobra" + "k8s.io/klog" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +const defaultConfigPath = "/etc/neco-tenant-controller/config.yaml" + +var options struct { + configFile string + metricsAddr string + probeAddr string + leaderElectionID string + webhookAddr string + certDir string + zapOpts zap.Options +} + +var rootCmd = &cobra.Command{ + Use: "neco-tenant-controller", + Version: tenant.Version, + Short: "neco-tenant controller", + Long: `neco-tenant controller`, + + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + h, p, err := net.SplitHostPort(options.webhookAddr) + if err != nil { + return fmt.Errorf("invalid webhook address: %s, %v", options.webhookAddr, err) + } + numPort, err := strconv.Atoi(p) + if err != nil { + return fmt.Errorf("invalid webhook address: %s, %v", options.webhookAddr, err) + } + ns := os.Getenv("POD_NAMESPACE") + if ns == "" { + return errors.New("no environment variable POD_NAMESPACE") + } + return subMain(ns, h, numPort) + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + fs := rootCmd.Flags() + fs.StringVar(&options.configFile, "config-file", defaultConfigPath, "Configuration file path") + fs.StringVar(&options.metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to") + fs.StringVar(&options.probeAddr, "health-probe-addr", ":8081", "Listen address for health probes") + fs.StringVar(&options.leaderElectionID, "leader-election-id", "neco-tenant-controller", "ID for leader election by controller-runtime") + fs.StringVar(&options.webhookAddr, "webhook-addr", ":9443", "Listen address for the webhook endpoint") + fs.StringVar(&options.certDir, "cert-dir", "", "webhook certificate directory") + + goflags := flag.NewFlagSet("klog", flag.ExitOnError) + klog.InitFlags(goflags) + options.zapOpts.BindFlags(goflags) + + fs.AddGoFlagSet(goflags) +} diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go new file mode 100644 index 0000000..e5a8320 --- /dev/null +++ b/cmd/neco-tenant-controller/sub/run.go @@ -0,0 +1,85 @@ +package sub + +import ( + "fmt" + "os" + + // 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" + + tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/controllers" + "github.com/cybozu-go/neco-tenant-controller/pkg/config" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" +) + +func subMain(ns, addr string, port int) error { + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&options.zapOpts))) + logger := ctrl.Log.WithName("setup") + + scheme := runtime.NewScheme() + if err := clientgoscheme.AddToScheme(scheme); err != nil { + return fmt.Errorf("unable to add client-go objects: %w", err) + } + if err := tenantv1beta1.AddToScheme(scheme); err != nil { + return fmt.Errorf("unable to add neco-tenant-controller objects: %w", err) + } + + cfgData, err := os.ReadFile(options.configFile) + if err != nil { + return fmt.Errorf("failed to read %s: %w", options.configFile, err) + } + cfg := &config.Config{} + if err := cfg.Load(cfgData); err != nil { + return fmt.Errorf("unable to load the configuration file: %w", err) + } + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + Scheme: scheme, + MetricsBindAddress: options.metricsAddr, + HealthProbeBindAddress: options.probeAddr, + LeaderElection: true, + LeaderElectionID: options.leaderElectionID, + LeaderElectionNamespace: ns, + Host: addr, + Port: port, + CertDir: options.certDir, + }) + if err != nil { + return fmt.Errorf("unable to start manager: %w", err) + } + + if err := cfg.Validate(mgr.GetRESTMapper()); err != nil { + return fmt.Errorf("invalid configurations: %w", err) + } + + if err := (&controllers.TenantReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create Namespace controller: %w", err) + } + + if err = (&tenantv1beta1.Tenant{}).SetupWebhookWithManager(mgr); err != nil { + return fmt.Errorf("unable to create Tenant webhook: %w", err) + } + //+kubebuilder:scaffold:builder + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + return fmt.Errorf("unable to set up health check: %w", err) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + return fmt.Errorf("unable to set up ready check: %w", err) + } + + logger.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + return fmt.Errorf("problem running manager: %s", err) + } + return nil +} diff --git a/config/multi-tenancy.cybozu.com_tenants.yaml b/config/multi-tenancy.cybozu.com_tenants.yaml new file mode 100644 index 0000000..f688586 --- /dev/null +++ b/config/multi-tenancy.cybozu.com_tenants.yaml @@ -0,0 +1,57 @@ + +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + creationTimestamp: null + name: tenants.multi-tenancy.cybozu.com +spec: + group: multi-tenancy.cybozu.com + names: + kind: Tenant + listKind: TenantList + plural: tenants + singular: tenant + scope: Namespaced + versions: + - name: v1beta1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants 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.' + 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.' + type: string + metadata: + type: object + spec: + description: TenantSpec defines the desired state of Tenant + properties: + foo: + description: Foo is an example field of Tenant. Edit tenant_types.go + to remove/update + type: string + type: object + status: + description: TenantStatus defines the observed state of Tenant + type: object + type: object + served: true + storage: true + subresources: + status: {} +status: + acceptedNames: + kind: "" + plural: "" + conditions: [] + storedVersions: [] diff --git a/e2e/Makefile b/e2e/Makefile new file mode 100644 index 0000000..9d3a62e --- /dev/null +++ b/e2e/Makefile @@ -0,0 +1,56 @@ +KIND_VERSION = 0.11.1 +KUBERNETES_VERSION = 1.22.2 + +KIND := $(dir $(shell pwd))/bin/kind +KUBECTL := $(dir $(shell pwd))/bin/kubectl +KUBECONFIG := $(shell pwd)/.kubeconfig +KIND_CONFIG = kind-config.yaml +export KUBECTL KUBECONFIG + + +.PHONY: help +help: + @echo "Choose one of the following target" + @echo + @echo "start Start kind cluster and install accurate" + @echo "test Run e2e tests" + @echo "logs Save logs as logs.tar.gz" + @echo "stop Stop the kind cluster" + +.PHONY: start +start: $(KIND) $(KUBECTL) + $(KIND) create cluster --name=neco-tenant-controller --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m + cd ..; docker build --no-cache -t neco-tenant-controller:dev . + $(KIND) load docker-image neco-tenant-controller:dev --name=neco-tenant-controller + $(KUBECTL) apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml + $(KUBECTL) -n cert-manager wait --for=condition=available --timeout=180s --all deployments + $(KUBECTL) apply -k ../config/default + $(KUBECTL) -n tenant-system wait --for=condition=available --timeout=180s --all deployments + +.PHONY: test +test: + env PATH=$$(pwd)/../bin:$$PATH RUN_E2E=1 \ + go test -v -race . -ginkgo.progress -ginkgo.v -ginkgo.failFast + +.PHONY: logs +logs: + rm -rf logs.tar.gz logs + $(KIND) export logs --name=neco-tenant-controller ./logs + tar czf logs.tar.gz logs + rm -rf logs + +.PHONY: stop +stop: $(KIND) + $(KIND) delete cluster --name=neco-tenant-controller + -docker image rm neco-tenant-controller:dev + -docker image prune -f + +$(KIND): + mkdir -p ../bin + curl -sfL -o $@ https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)/kind-linux-amd64 + chmod a+x $@ + +$(KUBECTL): + mkdir -p ../bin + curl -sfL -o $@ https://dl.k8s.io/release/v$(KUBERNETES_VERSION)/bin/linux/amd64/kubectl + chmod a+x $@ diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go new file mode 100644 index 0000000..44c219f --- /dev/null +++ b/e2e/e2e_test.go @@ -0,0 +1,10 @@ +package e2e + +import ( + . "github.com/onsi/ginkgo" +) + +var _ = Describe("neco-tenant-controller", func() { + It("should work", func() { + }) +}) diff --git a/e2e/env_test.go b/e2e/env_test.go new file mode 100644 index 0000000..7e4b02f --- /dev/null +++ b/e2e/env_test.go @@ -0,0 +1,8 @@ +package e2e + +import "os" + +var ( + runE2E = os.Getenv("RUN_E2E") != "" + kubectlCmd = os.Getenv("KUBECTL") +) diff --git a/e2e/kind-config.yaml b/e2e/kind-config.yaml new file mode 100644 index 0000000..f7871d9 --- /dev/null +++ b/e2e/kind-config.yaml @@ -0,0 +1,6 @@ +apiVersion: kind.x-k8s.io/v1alpha4 +kind: Cluster +nodes: +- role: control-plane +- role: worker +- role: worker diff --git a/e2e/run_test.go b/e2e/run_test.go new file mode 100644 index 0000000..d71fb58 --- /dev/null +++ b/e2e/run_test.go @@ -0,0 +1,31 @@ +package e2e + +import ( + "bytes" + "fmt" + "os/exec" + + . "github.com/onsi/gomega" +) + +func kubectl(input []byte, args ...string) ([]byte, error) { + stdout := new(bytes.Buffer) + stderr := new(bytes.Buffer) + cmd := exec.Command(kubectlCmd, args...) + cmd.Stdout = stdout + cmd.Stderr = stderr + if input != nil { + cmd.Stdin = bytes.NewReader(input) + } + err := cmd.Run() + if err == nil { + return stdout.Bytes(), nil + } + return nil, fmt.Errorf("kubectl failed with %s: stderr=%s", err, stderr) +} + +func kubectlSafe(input []byte, args ...string) []byte { + out, err := kubectl(input, args...) + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + return out +} diff --git a/e2e/suite_test.go b/e2e/suite_test.go new file mode 100644 index 0000000..0dc1bf8 --- /dev/null +++ b/e2e/suite_test.go @@ -0,0 +1,19 @@ +package e2e + +import ( + "testing" + "time" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestE2e(t *testing.T) { + if !runE2E { + t.Skip("no RUN_E2E environment variable") + } + RegisterFailHandler(Fail) + SetDefaultEventuallyTimeout(30 * time.Second) + SetDefaultEventuallyPollingInterval(100 * time.Millisecond) + RunSpecs(t, "E2e Suite") +} diff --git a/go.mod b/go.mod index 1f41d5e..23587dd 100644 --- a/go.mod +++ b/go.mod @@ -5,8 +5,11 @@ go 1.16 require ( github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.15.0 - k8s.io/api v0.22.1 - k8s.io/apimachinery v0.22.1 - k8s.io/client-go v0.22.1 - sigs.k8s.io/controller-runtime v0.10.0 + github.com/spf13/cobra v1.1.3 + k8s.io/api v0.22.2 + k8s.io/apimachinery v0.22.2 + k8s.io/client-go v0.22.2 + k8s.io/klog v1.0.0 + sigs.k8s.io/controller-runtime v0.10.3 + sigs.k8s.io/yaml v1.2.0 ) diff --git a/go.sum b/go.sum index 80d4bda..8bab914 100644 --- a/go.sum +++ b/go.sum @@ -241,6 +241,7 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= @@ -373,6 +374,7 @@ github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasO github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -745,35 +747,36 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.22.1 h1:ISu3tD/jRhYfSW8jI/Q1e+lRxkR7w9UwQEZ7FgslrwY= -k8s.io/api v0.22.1/go.mod h1:bh13rkTp3F1XEaLGykbyRD2QaTTzPm0e/BMd8ptFONY= -k8s.io/apiextensions-apiserver v0.22.1 h1:YSJYzlFNFSfUle+yeEXX0lSQyLEoxoPJySRupepb0gE= -k8s.io/apiextensions-apiserver v0.22.1/go.mod h1:HeGmorjtRmRLE+Q8dJu6AYRoZccvCMsghwS8XTUYb2c= -k8s.io/apimachinery v0.22.1 h1:DTARnyzmdHMz7bFWFDDm22AM4pLWTQECMpRTFu2d2OM= -k8s.io/apimachinery v0.22.1/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apiserver v0.22.1/go.mod h1:2mcM6dzSt+XndzVQJX21Gx0/Klo7Aen7i0Ai6tIa400= -k8s.io/client-go v0.22.1 h1:jW0ZSHi8wW260FvcXHkIa0NLxFBQszTlhiAVsU5mopw= -k8s.io/client-go v0.22.1/go.mod h1:BquC5A4UOo4qVDUtoc04/+Nxp1MeHcVc1HJm1KmG8kk= -k8s.io/code-generator v0.22.1/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.22.1 h1:SFqIXsEN3v3Kkr1bS6rstrs1wd45StJqbtgbQ4nRQdo= -k8s.io/component-base v0.22.1/go.mod h1:0D+Bl8rrnsPN9v0dyYvkqFfBeAd4u7n77ze+p8CMiPo= +k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= +k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= +k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4= +k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= +k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= +k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= +k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= +k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= +k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= +k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= +k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M= +k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +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/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20210707171843-4b05e18ac7d9/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176 h1:Mx0aa+SUAcNRQbs5jUzV8lkDlGFU8laZsY9jrcVX5SY= -k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= +k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 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.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.10.0 h1:HgyZmMpjUOrtkaFtCnfxsR1bGRuFoAczSNbn2MoKj5U= -sigs.k8s.io/controller-runtime v0.10.0/go.mod h1:GCdh6kqV6IY4LK0JLwX0Zm6g233RtVGdb/f0+KSfprg= +sigs.k8s.io/controller-runtime v0.10.3 h1:s5Ttmw/B4AuIbwrXD3sfBkXwnPMMWrqpVj4WRt1dano= +sigs.k8s.io/controller-runtime v0.10.3/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= diff --git a/hack/boilerplate.go.txt b/hack/boilerplate.go.txt index 45dbbbb..e69de29 100644 --- a/hack/boilerplate.go.txt +++ b/hack/boilerplate.go.txt @@ -1,15 +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. -*/ \ No newline at end of file diff --git a/main.go b/main.go deleted file mode 100644 index ba4ce76..0000000 --- a/main.go +++ /dev/null @@ -1,108 +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 main - -import ( - "flag" - "os" - - // 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" - - "k8s.io/apimachinery/pkg/runtime" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/controllers" - //+kubebuilder:scaffold:imports -) - -var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("setup") -) - -func init() { - utilruntime.Must(clientgoscheme.AddToScheme(scheme)) - - utilruntime.Must(multitenancyv1beta1.AddToScheme(scheme)) - //+kubebuilder:scaffold:scheme -} - -func main() { - var metricsAddr string - var enableLeaderElection bool - var probeAddr string - 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", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") - opts := zap.Options{ - Development: true, - } - opts.BindFlags(flag.CommandLine) - flag.Parse() - - ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - - mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - MetricsBindAddress: metricsAddr, - Port: 9443, - HealthProbeBindAddress: probeAddr, - LeaderElection: enableLeaderElection, - LeaderElectionID: "309b12c3.cybozu.com", - }) - if err != nil { - setupLog.Error(err, "unable to start manager") - os.Exit(1) - } - - if err = (&controllers.TenantReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "Tenant") - os.Exit(1) - } - if err = (&multitenancyv1beta1.Tenant{}).SetupWebhookWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create webhook", "webhook", "Tenant") - os.Exit(1) - } - //+kubebuilder:scaffold:builder - - if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up health check") - os.Exit(1) - } - if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { - setupLog.Error(err, "unable to set up ready check") - os.Exit(1) - } - - setupLog.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { - setupLog.Error(err, "problem running manager") - os.Exit(1) - } -} diff --git a/pkg/config/types.go b/pkg/config/types.go new file mode 100644 index 0000000..f168a35 --- /dev/null +++ b/pkg/config/types.go @@ -0,0 +1,22 @@ +package config + +import ( + "k8s.io/apimachinery/pkg/api/meta" + "sigs.k8s.io/yaml" +) + +// Config represents the configuration file of neco-tenant-controller. +type Config struct { + ArgoCDNamespace string `json:"argocdNamespace,omitempty"` + TeleportNamespace string `json:"teleportNamespace,omitempty"` +} + +// Validate validates the configurations. +func (c *Config) Validate(mapper meta.RESTMapper) error { + return nil +} + +// Load loads configurations. +func (c *Config) Load(data []byte) error { + return yaml.Unmarshal(data, c, yaml.DisallowUnknownFields) +} diff --git a/version.go b/version.go new file mode 100644 index 0000000..2a6bfaf --- /dev/null +++ b/version.go @@ -0,0 +1,4 @@ +package tenant + +// Version represents the version of neco-tenant-controller. +const Version = "0.1.0" From fb14c7853387b60ab33db11bcdf43ef02aedc618 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 19 Nov 2021 18:40:41 +0900 Subject: [PATCH 05/89] Define CRD and Configuration Signed-off-by: zoetrope --- Makefile | 10 +++ api/v1beta1/tenant_types.go | 99 +++++++++++++++++++++++++++-- docs/config.md | 29 +++++++++ docs/crd_tenant.md | 122 ++++++++++++++++++++++++++++++++++++ pkg/config/types.go | 30 ++++++++- 5 files changed, 284 insertions(+), 6 deletions(-) create mode 100644 docs/config.md create mode 100644 docs/crd_tenant.md diff --git a/Makefile b/Makefile index b213fd0..8b65e5c 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ CTRL_TOOLS_VERSION=0.7.0 CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) KUSTOMIZE_VERSION = 4.4.1 +CRD_TO_MARKDOWN_VERSION = 0.0.3 # Test tools BIN_DIR := $(shell pwd)/bin @@ -50,6 +51,10 @@ manifests: kustomize controller-gen ## Generate WebhookConfiguration, ClusterRol generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." +.PHONY: apidoc +apidoc: crd-to-markdown $(wildcard api/*/*_types.go) + $(CRD_TO_MARKDOWN) --links docs/links.csv -f api/v1beta1/tenant_types.go -n Tenant > docs/crd_tenant.md + .PHONY: check-generate check-generate: $(MAKE) manifests generate apidoc @@ -102,6 +107,11 @@ $(KUSTOMIZE): curl -fsL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv$(KUSTOMIZE_VERSION)/kustomize_v$(KUSTOMIZE_VERSION)_linux_amd64.tar.gz | \ tar -C bin -xzf - +CRD_TO_MARKDOWN := $(shell pwd)/bin/crd-to-markdown +.PHONY: crd-to-markdown +crd-to-markdown: ## Download crd-to-markdown locally if necessary. + $(call go-get-tool,$(CRD_TO_MARKDOWN),github.com/clamoriniere/crd-to-markdown@v$(CRD_TO_MARKDOWN_VERSION)) + # 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 diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 6f69add..0547da1 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -25,11 +25,102 @@ import ( // TenantSpec defines the desired state of Tenant type TenantSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file + Namespaces []NamespaceSpec `json:"namespaces,omitempty"` + ArgoCD *ArgoCDSpec `json:"argocd,omitempty"` + Teleport *TeleportSpec `json:"teleport,omitempty"` +} + +// NamespaceSpec defines the desired state of Namespace +type NamespaceSpec struct { + // Name is the name of namespace to be generated + // +kubebuilder:validation:Required + Name string `json:"name"` + + // Labels are the labels to add to the namespace + // +optional + Labels map[string]string `json:"labels,omitempty"` + + // Annotations are the annotations to add to the namespace + // +optional + Annotations map[string]string `json:"annotations,omitempty"` + + // ExtraAdmins are the names of the team to add to the namespace administrator. + // Specify this if you want other tenant teams to be able to use your namespace. + // +optional + ExtraAdmins []string `json:"extraAdmins,omitempty"` +} + +// ArgoCDSpec defines the desired state of the settings for Argo CD +type ArgoCDSpec struct { + // Applications are the list of Application resources managed by the tenant team. + // +optional + Applications []ArgoCDApplicationSpec `json:"applications,omitempty"` + + // Repositories are the list of repositories used by the tenant team. + // +optional + Repositories []string `json:"repositories,omitempty"` + + // ExtraAdmins are the names of the team to add to the AppProject user. + // Specify this if you want other tenant teams to be able to use your AppProject. + // +optional + ExtraAdmins []string `json:"extraAdmins,omitempty"` +} + +// ArgoCDApplicationSpec defines the desired state of Application +type ArgoCDApplicationSpec struct { + // Name is the name of Application resource. + // +kubebuilder:validation:Required + Name string `json:"name"` + + // Path is a directory path within the Git repository, and is only valid for applications sourced from Git. + // +kubebuilder:validation:Required + Path string `json:"path"` + + // RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. + // +kubebuilder:validation:Required + RepoURL string `json:"repoURL"` + + // TargetRevision defines the revision of the source to sync the application to. + // In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. + // In case of Helm, this is a semver tag for the Chart's version. + // +kubebuilder:validation:Required + TargetRevision string `json:"targetRevision"` +} + +// TeleportSpec defines the desired state of the settings for Teleport +type TeleportSpec struct { + // Node is the settings of Teleport Node for the tenant team. + // +optional + Node *TeleportNodeSpec `json:"node,omitempty"` + + // Applications are the list of applications to be used by the tenant team. + // +optional + Applications []TeleportApplicationSpec `json:"applications,omitempty"` +} + +type TeleportNodeSpec struct { + // Replicas is the number of Teleport Node Pods. + // +kubebuilder:validation:Required + Replicas int `json:"replicas"` + + // ExtraArgs are the list of additional arguments to be specified for Teleport Node Pod. + // +optional + ExtraArgs []string `json:"extraArgs,omitempty"` +} + +// TeleportApplicationSpec defines the desired state of Teleport Application. +type TeleportApplicationSpec struct { + // Name is the name of the application to proxy. + // +kubebuilder:validation:Required + Name string `json:"name"` + + // URL is the internal address of the application to proxy. + // +kubebuilder:validation:Required + URL string `json:"url"` - // Foo is an example field of Tenant. Edit tenant_types.go to remove/update - Foo string `json:"foo,omitempty"` + // ExtraArgs are the list of additional arguments to be specified for Teleport Application Pod. + // +optional + ExtraArgs []string `json:"extraArgs,omitempty"` } // TenantStatus defines the observed state of Tenant diff --git a/docs/config.md b/docs/config.md new file mode 100644 index 0000000..6572cf7 --- /dev/null +++ b/docs/config.md @@ -0,0 +1,29 @@ +# Configurations + +## Configuration file + +`neco-tenant-controller` reads its configurations from a configuration file. + +The repository includes an example as follows: + +```yaml +namespaces: + # Labels to add to all namespaces to be deployed by neco-tenant-controller + commonLabels: + - accurate.cybozu.com/template: init-template + +argocd: + # The name of namespace where Argo CD is deployed + namespace: argocd + # The mode of validation for Application resources. + # If true is set, this does not deny Application resources but issues a warning. + permissiveValidation: true + +teleport: + # The name of namespace where Teleport Nodes are deployed + namespace: teleport + # The name of Teleport container image + image: quay.io/cybozu/teleport-node:latest + # The name of secret resource contains a license key for Teleport Enterprise + licenseSecretName: teleport-general-secret-20210310 +``` diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md new file mode 100644 index 0000000..546a20a --- /dev/null +++ b/docs/crd_tenant.md @@ -0,0 +1,122 @@ + +### Custom Resources + +* [Tenant](#tenant) + +### Sub Resources + +* [ArgoCDApplicationSpec](#argocdapplicationspec) +* [ArgoCDSpec](#argocdspec) +* [NamespaceSpec](#namespacespec) +* [TeleportApplicationSpec](#teleportapplicationspec) +* [TeleportNodeSpec](#teleportnodespec) +* [TeleportSpec](#teleportspec) +* [TenantList](#tenantlist) +* [TenantSpec](#tenantspec) + +#### ArgoCDApplicationSpec + +ArgoCDApplicationSpec defines the desired state of Application + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| name | Name is the name of Application resource. | string | true | +| path | Path is a directory path within the Git repository, and is only valid for applications sourced from Git. | string | true | +| repoURL | RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. | string | true | +| targetRevision | TargetRevision defines the revision of the source to sync the application to. In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. In case of Helm, this is a semver tag for the Chart's version. | string | true | + +[Back to Custom Resources](#custom-resources) + +#### ArgoCDSpec + +ArgoCDSpec defines the desired state of the settings for Argo CD + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| applications | Applications are the list of Application resources managed by the tenant team. | [][ArgoCDApplicationSpec](#argocdapplicationspec) | false | +| repositories | Repositories are the list of repositories used by the tenant team. | []string | false | +| extraAdmins | ExtraAdmins are the names of the team to add to the AppProject user. Specify this if you want other tenant teams to be able to use your AppProject. | []string | false | + +[Back to Custom Resources](#custom-resources) + +#### NamespaceSpec + +NamespaceSpec defines the desired state of Namespace + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| name | Name is the name of namespace to be generated | string | true | +| labels | Labels are the labels to add to the namespace | map[string]string | false | +| annotations | Annotations are the annotations to add to the namespace | map[string]string | false | +| extraAdmins | ExtraAdmins are the names of the team to add to the namespace administrator. Specify this if you want other tenant teams to be able to use your namespace. | []string | false | + +[Back to Custom Resources](#custom-resources) + +#### TeleportApplicationSpec + +TeleportApplicationSpec defines the desired state of Teleport Application. + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| name | Name is the name of the application to proxy. | string | true | +| url | URL is the internal address of the application to proxy. | string | true | +| extraArgs | ExtraArgs are the list of additional arguments to be specified for Teleport Application Pod. | []string | false | + +[Back to Custom Resources](#custom-resources) + +#### TeleportNodeSpec + + + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| replicas | Replicas is the number of Teleport Node Pods. | int | true | +| extraArgs | ExtraArgs are the list of additional arguments to be specified for Teleport Node Pod. | []string | false | + +[Back to Custom Resources](#custom-resources) + +#### TeleportSpec + +TeleportSpec defines the desired state of the settings for Teleport + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| node | Node is the settings of Teleport Node for the tenant team. | *[TeleportNodeSpec](#teleportnodespec) | false | +| applications | Applications are the list of applications to be used by the tenant team. | [][TeleportApplicationSpec](#teleportapplicationspec) | false | + +[Back to Custom Resources](#custom-resources) + +#### Tenant + +Tenant is the Schema for the tenants API + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| metadata | | metav1.ObjectMeta | false | +| spec | | [TenantSpec](#tenantspec) | false | +| status | | [TenantStatus](#tenantstatus) | false | + +[Back to Custom Resources](#custom-resources) + +#### TenantList + +TenantList contains a list of Tenant + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| metadata | | metav1.ListMeta | false | +| items | | [][Tenant](#tenant) | true | + +[Back to Custom Resources](#custom-resources) + +#### TenantSpec + +TenantSpec defines the desired state of Tenant + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| namespaces | | [][NamespaceSpec](#namespacespec) | false | +| argocd | | *[ArgoCDSpec](#argocdspec) | false | +| teleport | | *[TeleportSpec](#teleportspec) | false | + +[Back to Custom Resources](#custom-resources) diff --git a/pkg/config/types.go b/pkg/config/types.go index f168a35..0e6e7cb 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -7,8 +7,34 @@ import ( // Config represents the configuration file of neco-tenant-controller. type Config struct { - ArgoCDNamespace string `json:"argocdNamespace,omitempty"` - TeleportNamespace string `json:"teleportNamespace,omitempty"` + Namespace NamespaceConfig `json:"namespace,omitempty"` + ArgoCD ArgoCDConfig `json:"argocd,omitempty"` + Teleport TeleportConfig `json:"teleport,omitempty"` +} + +// NamespaceConfig represents the configuration about Namespaces +type NamespaceConfig struct { + // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller + CommonLabels map[string]string `json:"commonLabels,omitempty"` +} + +// ArgoCDConfig represents the configuration about Argo CD +type ArgoCDConfig struct { + // Namespace is the name of namespace where Argo CD is deployed + Namespace string `json:"namespace"` + // PermissiveValidation is the mode of validation for Application resources. + // If true is set, this does not deny Application resources but issues a warning. + PermissiveValidation bool `json:"permissiveValidation"` +} + +// TeleportConfig represents the configuration about Teleport +type TeleportConfig struct { + // Namespace is the name of namespace where Teleport Nodes are deployed + Namespace string `json:"namespace"` + // Image is the name of Teleport container image + Image string `json:"image"` + // LicenseSecretName is the name of secret resource contains a license key for Teleport Enterprise + LicenseSecretName string `json:"licenseSecretName"` } // Validate validates the configurations. From c1b43fd99c531865884847e256aa73935e1e14d0 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 26 Nov 2021 17:02:46 +0900 Subject: [PATCH 06/89] Add overview.md Signed-off-by: zoetrope --- docs/overview.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/overview.md diff --git a/docs/overview.md b/docs/overview.md new file mode 100644 index 0000000..f219b30 --- /dev/null +++ b/docs/overview.md @@ -0,0 +1,48 @@ +# Overview + +neco-tenant-controller is a Kubernetes controller to manage tenant team resources. + +## Features + +Cluster administrator can creates a [Tenant custom resource](./crd_tenant.md) for each tenant team. +neco-tenant-controller will automatically apply the following resources needed by the tenant team based on the Tenant resource. + +- Manage root namespaces + + Tenant users can use [Accurate][Accurate] to create a SubNamespace. + Because of that, users need a root namespace. + neco-tenant-controller creates multiple root namespaces for each tenant team. + Those namespaces are permission-controlled so that only the tenant users can access them. + +- Manage Argo CD AppProject resources + + Tenant users need an AppProject resource to deploy their manifests with [Argo CD][Argo CD]. + The AppProject can control namespaces where tenant users can deploy manifests. + neco-tenant-controller dynamically rewrites the AppProject resource each time tenant users creates a SubNamespace. + +- Manage Argo CD Application resources + + Tenant users need Application resources to deploy their manifests with Argo CD. + neco-tenant-controller creates Application resources for each tenant team. + +- Validate Argo CD Application resources + + Tenant users can specify any repository for an Application resource. + However, that's not appropriate from a security perspective. + Admission webhook will deny the creation of Application that contains an unauthorized repository. + +- Deploy Teleport Node + + Tenant users can use [Teleport][Teleport] to access our Kubernetes cluster. + Because of that, Teleport Node Pod is required. + neco-tenant-controller will deploy Teleport Node Pods for each tenant team. + +- Manage Teleport Applications + + Tenant users can use [Teleport][Teleport] to access applications in our Kubernetes cluster. + Because of that, Teleport Application Pod is required. + neco-tenant-controller will deploy Teleport Application Pods for each tenant team. + +[Accurate]: https://cybozu-go.github.io/accurate/ +[Argo CD]: https://argo-cd.readthedocs.io/ +[Teleport]: https://goteleport.com From 33788ef8138839241ec94808abfe1347a9f82d8a Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 14 Dec 2021 15:25:07 +0900 Subject: [PATCH 07/89] Move TenantWebhook to hooks Signed-off-by: zoetrope --- api/v1beta1/tenant_webhook.go | 75 --------------- cmd/neco-tenant-controller/sub/run.go | 12 ++- .../suite_test.go | 35 +++---- hooks/tenant.go | 95 +++++++++++++++++++ pkg/constants/meta.go | 7 ++ 5 files changed, 129 insertions(+), 95 deletions(-) delete mode 100644 api/v1beta1/tenant_webhook.go rename api/v1beta1/webhook_suite_test.go => hooks/suite_test.go (81%) create mode 100644 hooks/tenant.go create mode 100644 pkg/constants/meta.go diff --git a/api/v1beta1/tenant_webhook.go b/api/v1beta1/tenant_webhook.go deleted file mode 100644 index 5ec78fc..0000000 --- a/api/v1beta1/tenant_webhook.go +++ /dev/null @@ -1,75 +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 v1beta1 - -import ( - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/webhook" -) - -// log is for logging in this package. -var tenantlog = logf.Log.WithName("tenant-resource") - -func (r *Tenant) SetupWebhookWithManager(mgr ctrl.Manager) error { - return ctrl.NewWebhookManagedBy(mgr). - For(r). - Complete() -} - -// TODO(user): EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! - -//+kubebuilder:webhook:path=/mutate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=mtenant.kb.io,admissionReviewVersions=v1 - -var _ webhook.Defaulter = &Tenant{} - -// Default implements webhook.Defaulter so a webhook will be registered for the type -func (r *Tenant) Default() { - tenantlog.Info("default", "name", r.Name) - - // TODO(user): fill in your defaulting logic. -} - -// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. -//+kubebuilder:webhook:path=/validate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions=v1 - -var _ webhook.Validator = &Tenant{} - -// ValidateCreate implements webhook.Validator so a webhook will be registered for the type -func (r *Tenant) ValidateCreate() error { - tenantlog.Info("validate create", "name", r.Name) - - // TODO(user): fill in your validation logic upon object creation. - return nil -} - -// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type -func (r *Tenant) ValidateUpdate(old runtime.Object) error { - tenantlog.Info("validate update", "name", r.Name) - - // TODO(user): fill in your validation logic upon object update. - return nil -} - -// ValidateDelete implements webhook.Validator so a webhook will be registered for the type -func (r *Tenant) ValidateDelete() error { - tenantlog.Info("validate delete", "name", r.Name) - - // TODO(user): fill in your validation logic upon object deletion. - return nil -} diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index e5a8320..1c9a0b7 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -8,14 +8,16 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" - tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "github.com/cybozu-go/neco-tenant-controller/controllers" + "github.com/cybozu-go/neco-tenant-controller/hooks" "github.com/cybozu-go/neco-tenant-controller/pkg/config" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) func subMain(ns, addr string, port int) error { @@ -26,7 +28,7 @@ func subMain(ns, addr string, port int) error { if err := clientgoscheme.AddToScheme(scheme); err != nil { return fmt.Errorf("unable to add client-go objects: %w", err) } - if err := tenantv1beta1.AddToScheme(scheme); err != nil { + if err := multitenancyv1beta1.AddToScheme(scheme); err != nil { return fmt.Errorf("unable to add neco-tenant-controller objects: %w", err) } @@ -65,9 +67,11 @@ func subMain(ns, addr string, port int) error { return fmt.Errorf("unable to create Namespace controller: %w", err) } - if err = (&tenantv1beta1.Tenant{}).SetupWebhookWithManager(mgr); err != nil { - return fmt.Errorf("unable to create Tenant webhook: %w", err) + dec, err := admission.NewDecoder(scheme) + if err != nil { + return fmt.Errorf("unable to create admission decoder: %w", err) } + hooks.SetupTenantWebhook(mgr, dec) //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/api/v1beta1/webhook_suite_test.go b/hooks/suite_test.go similarity index 81% rename from api/v1beta1/webhook_suite_test.go rename to hooks/suite_test.go index a508891..fd86def 100644 --- a/api/v1beta1/webhook_suite_test.go +++ b/hooks/suite_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package v1beta1 +package hooks import ( "context" @@ -28,46 +28,44 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - admissionv1beta1 "k8s.io/api/admission/v1beta1" //+kubebuilder:scaffold:imports + tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + admissionv1beta1 "k8s.io/api/admission/v1beta1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/rest" + clientgoscheme "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" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) // 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 var k8sClient client.Client var testEnv *envtest.Environment -var ctx context.Context -var cancel context.CancelFunc +var cancelMgr context.CancelFunc func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "Webhook Suite", - []Reporter{printer.NewlineReporter{}}) + RunSpecs(t, "Webhook Suite") } var _ = BeforeSuite(func() { logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - ctx, cancel = context.WithCancel(context.TODO()) + ctx, cancel := context.WithCancel(context.TODO()) + cancelMgr = cancel By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ - Paths: []string{filepath.Join("..", "..", "config", "webhook")}, + Paths: []string{filepath.Join("..", "config", "webhook")}, }, } @@ -76,7 +74,9 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := runtime.NewScheme() - err = AddToScheme(scheme) + err = tenantv1beta1.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + err = clientgoscheme.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = admissionv1beta1.AddToScheme(scheme) @@ -100,7 +100,10 @@ var _ = BeforeSuite(func() { }) Expect(err).NotTo(HaveOccurred()) - err = (&Tenant{}).SetupWebhookWithManager(mgr) + dec, err := admission.NewDecoder(scheme) + Expect(err).NotTo(HaveOccurred()) + + err = SetupTenantWebhook(mgr, dec) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:webhook @@ -126,7 +129,7 @@ var _ = BeforeSuite(func() { }, 60) var _ = AfterSuite(func() { - cancel() + cancelMgr() By("tearing down the test environment") err := testEnv.Stop() Expect(err).NotTo(HaveOccurred()) diff --git a/hooks/tenant.go b/hooks/tenant.go new file mode 100644 index 0000000..1630bb8 --- /dev/null +++ b/hooks/tenant.go @@ -0,0 +1,95 @@ +/* +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 hooks + +import ( + "context" + "encoding/json" + "net/http" + + multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + admissionv1 "k8s.io/api/admission/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +//+kubebuilder:webhook:path=/mutate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=mtenant.kb.io,admissionReviewVersions={v1} + +type tenantMutator struct { + dec *admission.Decoder +} + +var _ admission.Handler = &tenantMutator{} + +func (m *tenantMutator) Handle(ctx context.Context, req admission.Request) admission.Response { + if req.Operation != admissionv1.Create { + return admission.Allowed("") + } + + sn := &multitenancyv1beta1.Tenant{} + if err := m.dec.Decode(req, sn); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + + sn.Finalizers = []string{constants.Finalizer} + data, err := json.Marshal(sn) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + return admission.PatchResponseFromRaw(req.Object.Raw, data) +} + +//+kubebuilder:webhook:path=/validate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions={v1} + +type tenantValidator struct { + client.Client + dec *admission.Decoder +} + +var _ admission.Handler = &tenantValidator{} + +func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) admission.Response { + if req.Operation != admissionv1.Create { + return admission.Allowed("") + } + + sn := &multitenancyv1beta1.Tenant{} + if err := v.dec.Decode(req, sn); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + return admission.Allowed("") +} + +// SetupTenantWebhook registers the webhooks for Tenant +func SetupTenantWebhook(mgr manager.Manager, dec *admission.Decoder) { + serv := mgr.GetWebhookServer() + + m := &tenantMutator{ + dec: dec, + } + serv.Register("/mutate-multi-tenancy-cybozu-com-v1beta1-tenant", &webhook.Admission{Handler: m}) + + v := &tenantValidator{ + Client: mgr.GetClient(), + dec: dec, + } + serv.Register("/validate-multi-tenancy-cybozu-com-v1beta1-tenant", &webhook.Admission{Handler: v}) +} diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go new file mode 100644 index 0000000..eb814be --- /dev/null +++ b/pkg/constants/meta.go @@ -0,0 +1,7 @@ +package constants + +// MetaPrefix is the MetaPrefix for labels, annotations, and finalizers of Accurate. +const MetaPrefix = "multi-tenancy.cybozu.com/" + +// Finalizer is the finalizer ID of Accurate. +const Finalizer = MetaPrefix + "finalizer" From 89356d07d1d134f5e8d1cde960516a2fd4288b14 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 14 Dec 2021 17:16:32 +0900 Subject: [PATCH 08/89] Add application controller and webhook Signed-off-by: zoetrope --- cmd/neco-tenant-controller/sub/run.go | 32 +++++++++++++++++++ controllers/application_controller.go | 36 ++++++++++++++++++++++ controllers/tenant_controller.go | 10 ++++-- hooks/application.go | 44 +++++++++++++++++++++++++++ pkg/argocd/application.go | 16 ++++++++++ 5 files changed, 135 insertions(+), 3 deletions(-) create mode 100644 controllers/application_controller.go create mode 100644 hooks/application.go create mode 100644 pkg/argocd/application.go diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index 1c9a0b7..0eddfe6 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -14,7 +14,10 @@ import ( "github.com/cybozu-go/neco-tenant-controller/pkg/config" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -43,6 +46,7 @@ func subMain(ns, addr string, port int) error { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, + NewClient: NewCachingClient, MetricsBindAddress: options.metricsAddr, HealthProbeBindAddress: options.probeAddr, LeaderElection: true, @@ -67,11 +71,19 @@ func subMain(ns, addr string, port int) error { return fmt.Errorf("unable to create Namespace controller: %w", err) } + if err := (&controllers.ApplicationReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + return fmt.Errorf("unable to create Namespace controller: %w", err) + } + dec, err := admission.NewDecoder(scheme) if err != nil { return fmt.Errorf("unable to create admission decoder: %w", err) } hooks.SetupTenantWebhook(mgr, dec) + hooks.SetupApplicationWebhook(mgr, dec) //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { @@ -87,3 +99,23 @@ func subMain(ns, addr string, port int) error { } return nil } + +// NewCachingClient is an alternative implementation of controller-runtime's +// default client for manager.Manager. +// https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cluster#DefaultNewClient +// +// The only difference is that this implementation sets `CacheUnstructured` to `true` to +// cache unstructured objects. +func NewCachingClient(cache cache.Cache, config *rest.Config, options client.Options, uncachedObjects ...client.Object) (client.Client, error) { + c, err := client.New(config, options) + if err != nil { + return nil, err + } + + return client.NewDelegatingClient(client.NewDelegatingClientInput{ + CacheReader: cache, + Client: c, + UncachedObjects: uncachedObjects, + CacheUnstructured: true, + }) +} diff --git a/controllers/application_controller.go b/controllers/application_controller.go new file mode 100644 index 0000000..3f3d4e0 --- /dev/null +++ b/controllers/application_controller.go @@ -0,0 +1,36 @@ +package controllers + +import ( + "context" + + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +// ApplicationReconciler reconciles an Application object +type ApplicationReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=argoproj.io,resources=applications,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=argoproj.io,resources=applications/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=argoproj.io,resources=applications/finalizers,verbs=update + +func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(argocd.Application()). + Complete(r) +} diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 521b4b4..f58a1d6 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -19,12 +19,11 @@ package controllers import ( "context" + multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" ) // TenantReconciler reconciles a Tenant object @@ -47,9 +46,14 @@ type TenantReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logger := log.FromContext(ctx) // TODO(user): your logic here + tenant := &multitenancyv1beta1.Tenant{} + if err := r.Get(ctx, req.NamespacedName, tenant); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + logger.Info("reconcile tenant", "spec", tenant.Spec) return ctrl.Result{}, nil } diff --git a/hooks/application.go b/hooks/application.go new file mode 100644 index 0000000..add578a --- /dev/null +++ b/hooks/application.go @@ -0,0 +1,44 @@ +package hooks + +import ( + "context" + "net/http" + + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + admissionv1 "k8s.io/api/admission/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/manager" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" +) + +//+kubebuilder:webhook:path=/validate-argoproj-io-application,mutating=false,failurePolicy=fail,sideEffects=None,groups=argoproj.io,resources=applications,verbs=create;update,versions=v1alpha1,name=vapplication.kb.io,admissionReviewVersions={v1} + +type applicationValidator struct { + client.Client + dec *admission.Decoder +} + +var _ admission.Handler = &applicationValidator{} + +func (v *applicationValidator) Handle(ctx context.Context, req admission.Request) admission.Response { + if req.Operation != admissionv1.Create { + return admission.Allowed("") + } + app := argocd.Application() + if err := v.dec.Decode(req, app); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + return admission.Allowed("") +} + +// SetupApplicationWebhook registers the webhooks for Application +func SetupApplicationWebhook(mgr manager.Manager, dec *admission.Decoder) { + serv := mgr.GetWebhookServer() + + v := &applicationValidator{ + Client: mgr.GetClient(), + dec: dec, + } + serv.Register("/validate-argoproj-io-application", &webhook.Admission{Handler: v}) +} diff --git a/pkg/argocd/application.go b/pkg/argocd/application.go new file mode 100644 index 0000000..6158d6f --- /dev/null +++ b/pkg/argocd/application.go @@ -0,0 +1,16 @@ +package argocd + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func Application() *unstructured.Unstructured { + app := &unstructured.Unstructured{} + app.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "argoproj.io", + Version: "v1alpha1", + Kind: "Application", + }) + return app +} From 55cd66f6b412ddf4b8a0b44f9def43364ac2661a Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 14 Dec 2021 17:17:37 +0900 Subject: [PATCH 09/89] Add Tiltfile Signed-off-by: zoetrope --- .dockerignore | 1 - .gitignore | 2 + Tiltfile | 50 ++++++ api/v1beta1/zz_generated.deepcopy.go | 167 +++++++++++++++++- .../multi-tenancy.cybozu.com_tenants.yaml | 130 +++++++++++++- config/default/kustomization.yaml | 60 +++---- config/dev/kustomization.yaml | 4 + config/dev/manager.yaml | 9 + config/manager/configmap.yaml | 7 + config/manager/kustomization.yaml | 1 + config/manager/manager.yaml | 12 ++ config/rbac/role.yaml | 26 +++ kind-with-registry.sh | 89 ++++++++++ teardown-kind-with-registry.sh | 41 +++++ 14 files changed, 561 insertions(+), 38 deletions(-) create mode 100644 Tiltfile create mode 100644 config/dev/kustomization.yaml create mode 100644 config/dev/manager.yaml create mode 100644 config/manager/configmap.yaml create mode 100755 kind-with-registry.sh create mode 100755 teardown-kind-with-registry.sh diff --git a/.dockerignore b/.dockerignore index 573b6c3..84442e6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,4 +1,3 @@ -/bin /config /docs /e2e diff --git a/.gitignore b/.gitignore index 3058053..7ecd55d 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,5 @@ # go mod /vendor + +/tilt_modules diff --git a/Tiltfile b/Tiltfile new file mode 100644 index 0000000..c33fd0a --- /dev/null +++ b/Tiltfile @@ -0,0 +1,50 @@ +load('ext://restart_process', 'docker_build_with_restart') +load('ext://cert_manager', 'deploy_cert_manager') + +def kubebuilder(): + + DOCKERFILE = '''FROM golang:alpine + WORKDIR / + COPY ./bin/manager / + CMD ["/manager"] + ''' + + def manifests(): + return 'controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases;' + + def generate(): + return 'controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' + + def binary(): + return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/manager cmd/neco-tenant-controller/main.go' + + installed = local("which kubebuilder") + print("kubebuilder is present:", installed) + + DIRNAME = os.path.basename(os. getcwd()) + + local(manifests() + generate()) + + local_resource('CRD', manifests() + 'kustomize build config/crd | kubectl apply -f -', deps=["api"], ignore=['*/*/zz_generated.deepcopy.go']) + + watch_settings(ignore=['config/crd/bases/', 'config/rbac/role.yaml', 'config/webhook/manifests.yaml']) + k8s_yaml(kustomize('./config/dev')) + + deps = ['controllers', 'pkg', 'hooks', 'cmd', 'version.go'] + deps.append('api') + + local_resource('Watch&Compile', generate() + binary(), deps=deps, ignore=['*/*/zz_generated.deepcopy.go']) + + local_resource('Sample YAML', 'kubectl apply -f ./config/samples', deps=["./config/samples"], resource_deps=[DIRNAME + "-controller-manager"]) + + docker_build_with_restart('controller:latest', '.', + dockerfile_contents=DOCKERFILE, + entrypoint='/manager', + only=['./bin/manager'], + live_update=[ + sync('./bin/manager', '/manager'), + ] + ) + +# deploy_cert_manager(version="v1.6.1") +kubebuilder() diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index c24af61..b8c4fa0 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -5,15 +5,161 @@ package v1beta1 import ( - "k8s.io/apimachinery/pkg/runtime" + runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDApplicationSpec) DeepCopyInto(out *ArgoCDApplicationSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDApplicationSpec. +func (in *ArgoCDApplicationSpec) DeepCopy() *ArgoCDApplicationSpec { + if in == nil { + return nil + } + out := new(ArgoCDApplicationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ArgoCDSpec) DeepCopyInto(out *ArgoCDSpec) { + *out = *in + if in.Applications != nil { + in, out := &in.Applications, &out.Applications + *out = make([]ArgoCDApplicationSpec, len(*in)) + copy(*out, *in) + } + if in.Repositories != nil { + in, out := &in.Repositories, &out.Repositories + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ExtraAdmins != nil { + in, out := &in.ExtraAdmins, &out.ExtraAdmins + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDSpec. +func (in *ArgoCDSpec) DeepCopy() *ArgoCDSpec { + if in == nil { + return nil + } + out := new(ArgoCDSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NamespaceSpec) DeepCopyInto(out *NamespaceSpec) { + *out = *in + if in.Labels != nil { + in, out := &in.Labels, &out.Labels + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Annotations != nil { + in, out := &in.Annotations, &out.Annotations + *out = make(map[string]string, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.ExtraAdmins != nil { + in, out := &in.ExtraAdmins, &out.ExtraAdmins + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSpec. +func (in *NamespaceSpec) DeepCopy() *NamespaceSpec { + if in == nil { + return nil + } + out := new(NamespaceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TeleportApplicationSpec) DeepCopyInto(out *TeleportApplicationSpec) { + *out = *in + if in.ExtraArgs != nil { + in, out := &in.ExtraArgs, &out.ExtraArgs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportApplicationSpec. +func (in *TeleportApplicationSpec) DeepCopy() *TeleportApplicationSpec { + if in == nil { + return nil + } + out := new(TeleportApplicationSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TeleportNodeSpec) DeepCopyInto(out *TeleportNodeSpec) { + *out = *in + if in.ExtraArgs != nil { + in, out := &in.ExtraArgs, &out.ExtraArgs + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportNodeSpec. +func (in *TeleportNodeSpec) DeepCopy() *TeleportNodeSpec { + if in == nil { + return nil + } + out := new(TeleportNodeSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TeleportSpec) DeepCopyInto(out *TeleportSpec) { + *out = *in + if in.Node != nil { + in, out := &in.Node, &out.Node + *out = new(TeleportNodeSpec) + (*in).DeepCopyInto(*out) + } + if in.Applications != nil { + in, out := &in.Applications, &out.Applications + *out = make([]TeleportApplicationSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportSpec. +func (in *TeleportSpec) DeepCopy() *TeleportSpec { + if in == nil { + return nil + } + out := new(TeleportSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Tenant) DeepCopyInto(out *Tenant) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) out.Status = in.Status } @@ -70,6 +216,23 @@ func (in *TenantList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = *in + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]NamespaceSpec, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.ArgoCD != nil { + in, out := &in.ArgoCD, &out.ArgoCD + *out = new(ArgoCDSpec) + (*in).DeepCopyInto(*out) + } + if in.Teleport != nil { + in, out := &in.Teleport, &out.Teleport + *out = new(TeleportSpec) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec. diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index 6791d22..ebaa73b 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.7.0 + controller-gen.kubebuilder.io/version: v0.4.0 creationTimestamp: null name: tenants.multi-tenancy.cybozu.com spec: @@ -36,10 +36,130 @@ spec: spec: description: TenantSpec defines the desired state of Tenant properties: - foo: - description: Foo is an example field of Tenant. Edit tenant_types.go - to remove/update - type: string + argocd: + description: ArgoCDSpec defines the desired state of the settings + for Argo CD + properties: + applications: + description: Applications are the list of Application resources + managed by the tenant team. + items: + description: ArgoCDApplicationSpec defines the desired state + of Application + properties: + name: + description: Name is the name of Application resource. + type: string + path: + description: Path is a directory path within the Git repository, + and is only valid for applications sourced from Git. + type: string + repoURL: + description: RepoURL is the URL to the repository (Git or + Helm) that contains the application manifests. + type: string + targetRevision: + description: TargetRevision defines the revision of the + source to sync the application to. In case of Git, this + can be commit, tag, or branch. If omitted, will equal + to HEAD. In case of Helm, this is a semver tag for the + Chart's version. + type: string + required: + - name + - path + - repoURL + - targetRevision + type: object + type: array + extraAdmins: + description: ExtraAdmins are the names of the team to add to the + AppProject user. Specify this if you want other tenant teams + to be able to use your AppProject. + items: + type: string + type: array + repositories: + description: Repositories are the list of repositories used by + the tenant team. + items: + type: string + type: array + type: object + namespaces: + items: + description: NamespaceSpec defines the desired state of Namespace + properties: + annotations: + additionalProperties: + type: string + description: Annotations are the annotations to add to the namespace + type: object + extraAdmins: + description: ExtraAdmins are the names of the team to add to + the namespace administrator. Specify this if you want other + tenant teams to be able to use your namespace. + items: + type: string + type: array + labels: + additionalProperties: + type: string + description: Labels are the labels to add to the namespace + type: object + name: + description: Name is the name of namespace to be generated + type: string + required: + - name + type: object + type: array + teleport: + description: TeleportSpec defines the desired state of the settings + for Teleport + properties: + applications: + description: Applications are the list of applications to be used + by the tenant team. + items: + description: TeleportApplicationSpec defines the desired state + of Teleport Application. + properties: + extraArgs: + description: ExtraArgs are the list of additional arguments + to be specified for Teleport Application Pod. + items: + type: string + type: array + name: + description: Name is the name of the application to proxy. + type: string + url: + description: URL is the internal address of the application + to proxy. + type: string + required: + - name + - url + type: object + type: array + node: + description: Node is the settings of Teleport Node for the tenant + team. + properties: + extraArgs: + description: ExtraArgs are the list of additional arguments + to be specified for Teleport Node Pod. + items: + type: string + type: array + replicas: + description: Replicas is the number of Teleport Node Pods. + type: integer + required: + - replicas + type: object + type: object type: object status: description: TenantStatus defines the observed state of Tenant diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index b159725..a65cb45 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -18,9 +18,9 @@ bases: - ../manager # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- ../webhook +- ../webhook # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. -#- ../certmanager +- ../certmanager # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. #- ../prometheus @@ -36,39 +36,39 @@ patchesStrategicMerge: # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # crd/kustomization.yaml -#- manager_webhook_patch.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 +- 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 +- 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/dev/kustomization.yaml b/config/dev/kustomization.yaml new file mode 100644 index 0000000..5c93302 --- /dev/null +++ b/config/dev/kustomization.yaml @@ -0,0 +1,4 @@ +resources: + - ../default +patchesStrategicMerge: + - ./manager.yaml diff --git a/config/dev/manager.yaml b/config/dev/manager.yaml new file mode 100644 index 0000000..da359fe --- /dev/null +++ b/config/dev/manager.yaml @@ -0,0 +1,9 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller-manager + namespace: system +spec: + template: + spec: + securityContext: null diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml new file mode 100644 index 0000000..b0f2817 --- /dev/null +++ b/config/manager/configmap.yaml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: controller-config + namespace: system +data: + config.yaml: | diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 2bcd3ee..7cda6f2 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -1,5 +1,6 @@ resources: - manager.yaml +- configmap.yaml generatorOptions: disableNameSuffixHash: true diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index cf11cec..a1a68cd 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -32,6 +32,11 @@ spec: args: - --leader-elect image: controller:latest + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace name: manager securityContext: allowPrivilegeEscalation: false @@ -56,5 +61,12 @@ spec: requests: cpu: 10m memory: 64Mi + volumeMounts: + - mountPath: /etc/neco-tenant-controller + name: config serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 + volumes: + - name: config + configMap: + name: controller-config diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index cc0d736..10c3a01 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -6,6 +6,32 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - argoproj.io + resources: + - applications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - argoproj.io + resources: + - applications/finalizers + verbs: + - update +- apiGroups: + - argoproj.io + resources: + - applications/status + verbs: + - get + - patch + - update - apiGroups: - multi-tenancy.cybozu.com resources: diff --git a/kind-with-registry.sh b/kind-with-registry.sh new file mode 100755 index 0000000..f586fcc --- /dev/null +++ b/kind-with-registry.sh @@ -0,0 +1,89 @@ +#!/bin/sh +# +# Adapted from: +# https://github.com/tilt-dev/kind-local/blob/master/kind-with-registry.sh +# https://github.com/kubernetes-sigs/kind/commits/master/site/static/examples/kind-with-registry.sh +# +# Copyright 2020 The Kubernetes Project +# +# 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. + +set -o errexit + +# desired cluster name; default is "kind" +KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}" +KIND_CLUSTER_OPTS="--name ${KIND_CLUSTER_NAME}" + +if [ -n "${KIND_CLUSTER_IMAGE}" ]; then + KIND_CLUSTER_OPTS="${KIND_CLUSTER_OPTS} --image ${KIND_CLUSTER_IMAGE}" +fi + +kind_version=$(kind version) +kind_network='kind' +reg_name='kind-registry' +reg_port='5000' +case "${kind_version}" in + "kind v0.7."* | "kind v0.6."* | "kind v0.5."*) + kind_network='bridge' + ;; +esac + +# create registry container unless it already exists +running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" +if [ "${running}" != 'true' ]; then + docker run \ + -d --restart=always -p "${reg_port}:5000" --name "${reg_name}" \ + registry:2 +fi + +reg_host="${reg_name}" +if [ "${kind_network}" = "bridge" ]; then + reg_host="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' "${reg_name}")" +fi +echo "Registry Host: ${reg_host}" + +# create a cluster with the local registry enabled in containerd +cat </dev/null || true)" +if [ "${running}" == 'true' ]; then + cid="$(docker inspect -f '{{.ID}}' "${reg_name}")" + echo "> Stopping and deleting Kind Registry container..." + docker stop $cid >/dev/null + docker rm $cid >/dev/null +fi + +echo "> Deleting Kind cluster..." +kind delete cluster --name=$KIND_CLUSTER_NAME From 9d2abe3365bfdfcfb3e8b2abe29e5da04bb9854d Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 15 Dec 2021 19:33:10 +0900 Subject: [PATCH 10/89] WIP Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 1 + cmd/neco-tenant-controller/sub/run.go | 8 +- .../multi-tenancy.cybozu.com_tenants.yaml | 4 +- config/dev/manager.yaml | 7 + config/manager/configmap.yaml | 23 ++ config/rbac/role.yaml | 44 ++- config/samples/application.yaml | 18 ++ ...enancy_v1beta1_tenant.yaml => tenant.yaml} | 5 +- config/webhook/manifests.yaml | 20 ++ controllers/application_controller.go | 10 +- controllers/tenant_controller.go | 294 +++++++++++++++++- pkg/argocd/appproject.go | 16 + pkg/config/types.go | 4 + pkg/constants/indexer.go | 3 + pkg/constants/meta.go | 4 + 15 files changed, 449 insertions(+), 12 deletions(-) create mode 100644 config/samples/application.yaml rename config/samples/{multi-tenancy_v1beta1_tenant.yaml => tenant.yaml} (61%) create mode 100644 pkg/argocd/appproject.go create mode 100644 pkg/constants/indexer.go diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 0547da1..b361b1d 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -131,6 +131,7 @@ type TenantStatus struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:resource:scope=Cluster // Tenant is the Schema for the tenants API type Tenant struct { diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index 0eddfe6..1e37cca 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -63,10 +63,14 @@ func subMain(ns, addr string, port int) error { if err := cfg.Validate(mgr.GetRESTMapper()); err != nil { return fmt.Errorf("invalid configurations: %w", err) } - + ctx := ctrl.SetupSignalHandler() + if err := controllers.SetupIndexForNamespace(ctx, mgr, cfg.Namespace.GroupKey); err != nil { + return fmt.Errorf("failed to setup indexer for namespaces: %w", err) + } if err := (&controllers.TenantReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), + Config: cfg, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create Namespace controller: %w", err) } @@ -94,7 +98,7 @@ func subMain(ns, addr string, port int) error { } logger.Info("starting manager") - if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + if err := mgr.Start(ctx); err != nil { return fmt.Errorf("problem running manager: %s", err) } return nil diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index ebaa73b..260cd55 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -4,7 +4,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.4.0 + controller-gen.kubebuilder.io/version: v0.7.0 creationTimestamp: null name: tenants.multi-tenancy.cybozu.com spec: @@ -14,7 +14,7 @@ spec: listKind: TenantList plural: tenants singular: tenant - scope: Namespaced + scope: Cluster versions: - name: v1beta1 schema: diff --git a/config/dev/manager.yaml b/config/dev/manager.yaml index da359fe..305e4da 100644 --- a/config/dev/manager.yaml +++ b/config/dev/manager.yaml @@ -7,3 +7,10 @@ spec: template: spec: securityContext: null + containers: + - command: + - /manager + args: + - --leader-elect + - --zap-devel=true + name: manager diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index b0f2817..523ad41 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -5,3 +5,26 @@ metadata: namespace: system data: config.yaml: | + namespace: + commonLabels: + accurate.cybozu.com/template: init-template + groupKey: team + argocd: + namespace: argocd + permissiveValidation: true + organization: cybozu-go + appProjectTemplate: | + apiVersion: argoproj.io/v1alpha1 + kind: AppProject + spec: + namespaceResourceBlacklist: + - group: "" + kind: ResourceQuota + - group: "" + kind: LimitRange + - group: networking.k8s.io + kind: NetworkPolicy + orphanedResources: + warn: false + sourceRepos: + - '*' diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 10c3a01..4b84046 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -21,17 +21,35 @@ rules: - apiGroups: - argoproj.io resources: - - applications/finalizers + - applications/status verbs: + - get + - patch - update - apiGroups: - argoproj.io resources: - - applications/status + - appprojects verbs: + - create + - delete - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list - patch - update + - watch - apiGroups: - multi-tenancy.cybozu.com resources: @@ -58,3 +76,25 @@ rules: - get - patch - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - bind + - escalate + - get + - list + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch diff --git a/config/samples/application.yaml b/config/samples/application.yaml new file mode 100644 index 0000000..538b5fd --- /dev/null +++ b/config/samples/application.yaml @@ -0,0 +1,18 @@ +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: sample + namespace: default +spec: + project: default + source: + repoURL: https://github.com/cybozu-go/neco-apps.git + targetRevision: release + path: argocd/base + destination: + server: https://kubernetes.default.svc + namespace: argocd + syncPolicy: + automated: + prune: true + selfHeal: true diff --git a/config/samples/multi-tenancy_v1beta1_tenant.yaml b/config/samples/tenant.yaml similarity index 61% rename from config/samples/multi-tenancy_v1beta1_tenant.yaml rename to config/samples/tenant.yaml index 8450a65..6293e15 100644 --- a/config/samples/multi-tenancy_v1beta1_tenant.yaml +++ b/config/samples/tenant.yaml @@ -3,4 +3,7 @@ kind: Tenant metadata: name: tenant-sample spec: - # TODO(user): Add fields here + namespaces: + - name: app-sample +# - name: app-foo + diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 49f529b..e676054 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -34,6 +34,26 @@ metadata: creationTimestamp: null name: validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-argoproj-io-application + failurePolicy: Fail + name: vapplication.kb.io + rules: + - apiGroups: + - argoproj.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - applications + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 3f3d4e0..559c627 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -18,12 +18,16 @@ type ApplicationReconciler struct { //+kubebuilder:rbac:groups=argoproj.io,resources=applications,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=argoproj.io,resources=applications/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=argoproj.io,resources=applications/finalizers,verbs=update func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + logger := log.FromContext(ctx) - // TODO(user): your logic here + app := argocd.Application() + if err := r.Get(ctx, req.NamespacedName, app); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + logger.Info("reconcile application", "spec", app.Object) return ctrl.Result{}, nil } diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index f58a1d6..f73763f 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -18,23 +18,46 @@ package controllers import ( "context" + "fmt" multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + "github.com/cybozu-go/neco-tenant-controller/pkg/config" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer/yaml" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" + "k8s.io/utils/pointer" 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/event" + "sigs.k8s.io/controller-runtime/pkg/handler" "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" ) // TenantReconciler reconciles a Tenant object type TenantReconciler struct { client.Client Scheme *runtime.Scheme + Config *config.Config } //+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants/status,verbs=get;update;patch //+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants/finalizers,verbs=update +//+kubebuilder:rbac:groups=argoproj.io,resources=appprojects,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=get;list;watch;escalate;bind // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. @@ -48,19 +71,286 @@ type TenantReconciler struct { func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) - // TODO(user): your logic here tenant := &multitenancyv1beta1.Tenant{} if err := r.Get(ctx, req.NamespacedName, tenant); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } - logger.Info("reconcile tenant", "spec", tenant.Spec) + + if tenant.DeletionTimestamp != nil { + logger.Info("starting finalization") + if err := r.finalize(ctx, tenant); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to finalize: %w", err) + } + logger.Info("finished finalization") + return ctrl.Result{}, nil + } + + err := r.reconcileNamespaces(ctx, tenant) + if err != nil { + return ctrl.Result{}, err + } + + err = r.reconcileArgoCD(ctx, tenant) + if err != nil { + return ctrl.Result{}, err + } return ctrl.Result{}, nil } +func containNamespace(roots []multitenancyv1beta1.NamespaceSpec, ns corev1.Namespace) bool { + for _, root := range roots { + if root.Name == ns.Name { + return true + } + } + return false +} + +func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *multitenancyv1beta1.Tenant, orphan bool) error { + nss := &corev1.NamespaceList{} + if err := r.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + return fmt.Errorf("failed to list namespaces: %w", err) + } + for _, ns := range nss.Items { + if orphan && containNamespace(tenant.Spec.Namespaces, ns) { + continue + } + newNs := ns.DeepCopy() + delete(newNs.Labels, constants.OwnerTenant) + delete(newNs.Labels, r.Config.Namespace.GroupKey) + patch := client.MergeFrom(&ns) + err := r.Patch(ctx, newNs, patch) + if err != nil { + return err + } + } + return nil +} + +func (r *TenantReconciler) finalize(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { + if !controllerutil.ContainsFinalizer(tenant, constants.Finalizer) { + return nil + } + + err := r.removeManagedLabels(ctx, tenant, false) + if err != nil { + return err + } + + controllerutil.RemoveFinalizer(tenant, constants.Finalizer) + return r.Update(ctx, tenant) +} + +func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { + logger := log.FromContext(ctx) + for _, ns := range tenant.Spec.Namespaces { + obj := &corev1.Namespace{} + obj.Name = ns.Name + op, err := ctrl.CreateOrUpdate(ctx, r.Client, obj, func() error { + if len(obj.Labels) == 0 { + obj.Labels = map[string]string{} + } + for k, v := range r.Config.Namespace.CommonLabels { + obj.Labels[k] = v + } + for k, v := range ns.Labels { + obj.Labels[k] = v + } + for k, v := range ns.Annotations { + obj.Annotations[k] = v + } + obj.Labels["accurate.cybozu.com/type"] = "root" + obj.Labels[r.Config.Namespace.GroupKey] = tenant.Name + obj.Labels[constants.OwnerTenant] = tenant.Name + + return nil + }) + if err != nil { + return err + } + + logger.Info("updated namespace", "op", op) + + rb := &rbacv1.RoleBinding{} + rb.SetNamespace(ns.Name) + rb.SetName(tenant.Name + "-admin") + + op, err = ctrl.CreateOrUpdate(ctx, r.Client, rb, func() error { + if rb.Labels == nil { + rb.Labels = map[string]string{} + } + rb.Labels[constants.OwnerTenant] = tenant.Name + + if rb.Annotations == nil { + rb.Annotations = map[string]string{} + } + rb.Annotations["accurate.cybozu.com/propagate"] = "update" + rb.RoleRef.Name = "admin" + rb.RoleRef.Kind = "ClusterRole" + rb.RoleRef.APIGroup = "rbac.authorization.k8s.io" + + rb.Subjects = []rbacv1.Subject{} + rb.Subjects = append(rb.Subjects, rbacv1.Subject{ + Kind: "Group", + APIGroup: "rbac.authorization.k8s.io", + Name: tenant.Name, + }) + rb.Subjects = append(rb.Subjects, rbacv1.Subject{ //TODO: + Kind: "ServiceAccount", + Namespace: r.Config.Teleport.Namespace, + Name: "node-" + tenant.Name, + }) + + for _, admin := range ns.ExtraAdmins { + rb.Subjects = append(rb.Subjects, rbacv1.Subject{ + Kind: "Group", + APIGroup: "rbac.authorization.k8s.io", + Name: admin, + }) + rb.Subjects = append(rb.Subjects, rbacv1.Subject{ + Kind: "ServiceAccount", + Namespace: r.Config.Teleport.Namespace, + Name: "node-" + admin, + }) + } + return nil + }) + if err != nil { + logger.Error(err, "failed to upsert RoleBinding") + return err + } + logger.Info("updated rolebinding", "op", op) + } + // Remove orphan labels + err := r.removeManagedLabels(ctx, tenant, true) + if err != nil { + return err + } + + return nil +} + +func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { + logger := log.FromContext(ctx) + + proj := argocd.AppProject() + + err := r.Get(ctx, client.ObjectKey{Namespace: r.Config.ArgoCD.Namespace, Name: tenant.Name}, proj) + if err != nil && !errors.IsNotFound(err) { + return err + } + + dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) + _, _, err = dec.Decode([]byte(r.Config.ArgoCD.AppProjectTemplate), nil, proj) + if err != nil { + return err + } + + nss := &corev1.NamespaceList{} + if err := r.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + return fmt.Errorf("failed to list namespaces: %w", err) + } + + proj.SetNamespace(r.Config.ArgoCD.Namespace) + proj.SetName(tenant.Name) + proj.SetLabels(map[string]string{ + constants.OwnerTenant: tenant.Name, + }) + + spec := proj.UnstructuredContent()["spec"].(map[string]interface{}) + + destinations, ok := spec["destinations"].([]map[string]interface{}) + if !ok { + destinations = []map[string]interface{}{} + } + + for _, ns := range nss.Items { + destinations = append(destinations, map[string]interface{}{ + "namespace": ns.Name, + "server": "*", + }) + } + + groups := []string{ + fmt.Sprintf("%s:%s", r.Config.ArgoCD.Organization, tenant.Name), + } + if tenant.Spec.ArgoCD != nil { + for _, extra := range tenant.Spec.ArgoCD.ExtraAdmins { + groups = append(groups, fmt.Sprintf("%s:%s", r.Config.ArgoCD.Organization, extra)) + } + } + + roles := []map[string]interface{}{ + { + "groups": groups, + "name": "admin", + "policies": []string{ + fmt.Sprintf("p, proj:%s:admin, applications, *, %s/*, allow", tenant.Name, tenant.Name), + }, + }, + } + + spec["destinations"] = destinations + spec["roles"] = roles + proj.UnstructuredContent()["spec"] = spec + + err = r.Patch(ctx, proj, client.Apply, &client.PatchOptions{ + Force: pointer.BoolPtr(true), + FieldManager: constants.FieldManager, + }) + + if err != nil { + return err + } + + logger.Info("AppProject successfully reconciled") + + return nil +} + // SetupWithManager sets up the controller with the Manager. func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { + tenantHandler := func(o client.Object, q workqueue.RateLimitingInterface) { + owner := o.GetLabels()[constants.OwnerTenant] + if owner == "" { + return + } + q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ + Name: owner, + }}) + } + + funcs := handler.Funcs{ + CreateFunc: func(ev event.CreateEvent, q workqueue.RateLimitingInterface) { + tenantHandler(ev.Object, q) + }, + UpdateFunc: func(ev event.UpdateEvent, q workqueue.RateLimitingInterface) { + if ev.ObjectNew.GetDeletionTimestamp() != nil { + return + } + tenantHandler(ev.ObjectOld, q) + }, + DeleteFunc: func(ev event.DeleteEvent, q workqueue.RateLimitingInterface) { + tenantHandler(ev.Object, q) + }, + } + return ctrl.NewControllerManagedBy(mgr). For(&multitenancyv1beta1.Tenant{}). + Watches(&source.Kind{Type: &corev1.Namespace{}}, funcs). + Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, funcs). + Watches(&source.Kind{Type: argocd.AppProject()}, funcs). Complete(r) } + +func SetupIndexForNamespace(ctx context.Context, mgr manager.Manager, groupKey string) error { + ns := &corev1.Namespace{} + return mgr.GetFieldIndexer().IndexField(ctx, ns, constants.NamespaceGroupKey, func(rawObj client.Object) []string { + group := rawObj.GetLabels()[groupKey] + if group == "" { + return nil + } + return []string{group} + }) +} diff --git a/pkg/argocd/appproject.go b/pkg/argocd/appproject.go new file mode 100644 index 0000000..746ee86 --- /dev/null +++ b/pkg/argocd/appproject.go @@ -0,0 +1,16 @@ +package argocd + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +func AppProject() *unstructured.Unstructured { + app := &unstructured.Unstructured{} + app.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "argoproj.io", + Version: "v1alpha1", + Kind: "AppProject", + }) + return app +} diff --git a/pkg/config/types.go b/pkg/config/types.go index 0e6e7cb..cbe2fc2 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -16,6 +16,7 @@ type Config struct { type NamespaceConfig struct { // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller CommonLabels map[string]string `json:"commonLabels,omitempty"` + GroupKey string `json:"groupKey"` } // ArgoCDConfig represents the configuration about Argo CD @@ -25,6 +26,9 @@ type ArgoCDConfig struct { // PermissiveValidation is the mode of validation for Application resources. // If true is set, this does not deny Application resources but issues a warning. PermissiveValidation bool `json:"permissiveValidation"` + + Organization string `json:"organization"` + AppProjectTemplate string `json:"appProjectTemplate"` } // TeleportConfig represents the configuration about Teleport diff --git a/pkg/constants/indexer.go b/pkg/constants/indexer.go new file mode 100644 index 0000000..26766b8 --- /dev/null +++ b/pkg/constants/indexer.go @@ -0,0 +1,3 @@ +package constants + +const NamespaceGroupKey = "namespace.group" diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go index eb814be..a5e5b2c 100644 --- a/pkg/constants/meta.go +++ b/pkg/constants/meta.go @@ -5,3 +5,7 @@ const MetaPrefix = "multi-tenancy.cybozu.com/" // Finalizer is the finalizer ID of Accurate. const Finalizer = MetaPrefix + "finalizer" + +const OwnerTenant = MetaPrefix + "owner-tenant" + +const FieldManager = MetaPrefix + "neco-tenant-controller" From 5d7d5bcc1341d9c16d7c46e821fd11d6cb2b5153 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 20 Dec 2021 17:01:06 +0900 Subject: [PATCH 11/89] Sync application Signed-off-by: zoetrope --- Tiltfile | 2 +- cmd/neco-tenant-controller/sub/run.go | 3 +- config/dev/manager.yaml | 4 +- config/rbac/role.yaml | 8 - config/samples/application.yaml | 16 +- config/webhook/manifests.yaml | 20 +++ controllers/application_controller.go | 217 +++++++++++++++++++++++++- controllers/tenant_controller.go | 9 +- hooks/application.go | 83 +++++++++- pkg/argocd/application.go | 2 +- pkg/argocd/appproject.go | 2 +- pkg/argocd/constants.go | 7 + pkg/constants/meta.go | 3 + 13 files changed, 344 insertions(+), 32 deletions(-) create mode 100644 pkg/argocd/constants.go diff --git a/Tiltfile b/Tiltfile index c33fd0a..e6bad74 100644 --- a/Tiltfile +++ b/Tiltfile @@ -39,7 +39,7 @@ def kubebuilder(): docker_build_with_restart('controller:latest', '.', dockerfile_contents=DOCKERFILE, - entrypoint='/manager', + entrypoint=['/manager', '--zap-devel=true'], only=['./bin/manager'], live_update=[ sync('./bin/manager', '/manager'), diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index 1e37cca..be20488 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -78,6 +78,7 @@ func subMain(ns, addr string, port int) error { if err := (&controllers.ApplicationReconciler{ Client: mgr.GetClient(), Scheme: mgr.GetScheme(), + Config: cfg, }).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create Namespace controller: %w", err) } @@ -87,7 +88,7 @@ func subMain(ns, addr string, port int) error { return fmt.Errorf("unable to create admission decoder: %w", err) } hooks.SetupTenantWebhook(mgr, dec) - hooks.SetupApplicationWebhook(mgr, dec) + hooks.SetupApplicationWebhook(mgr, dec, cfg) //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/dev/manager.yaml b/config/dev/manager.yaml index 305e4da..d3c7d0c 100644 --- a/config/dev/manager.yaml +++ b/config/dev/manager.yaml @@ -10,7 +10,5 @@ spec: containers: - command: - /manager - args: - - --leader-elect - - --zap-devel=true + args: null name: manager diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 4b84046..26ac440 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -18,14 +18,6 @@ rules: - patch - update - watch -- apiGroups: - - argoproj.io - resources: - - applications/status - verbs: - - get - - patch - - update - apiGroups: - argoproj.io resources: diff --git a/config/samples/application.yaml b/config/samples/application.yaml index 538b5fd..9c4170d 100644 --- a/config/samples/application.yaml +++ b/config/samples/application.yaml @@ -2,16 +2,20 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: sample - namespace: default + namespace: app-sample + labels: + foo: bar + finalizers: + - resources-finalizer.argocd.argoproj.io spec: - project: default + project: tenant-sample source: - repoURL: https://github.com/cybozu-go/neco-apps.git - targetRevision: release - path: argocd/base + repoURL: https://github.com/neco-test/apps-sandbox.git + targetRevision: master + path: testhttpd destination: server: https://kubernetes.default.svc - namespace: argocd + namespace: app-test syncPolicy: automated: prune: true diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index e676054..510901b 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -6,6 +6,26 @@ metadata: creationTimestamp: null name: mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-argoproj-io-application + failurePolicy: Fail + name: mapplication.kb.io + rules: + - apiGroups: + - argoproj.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - applications + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 559c627..85d7795 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -1,37 +1,246 @@ package controllers import ( + "bytes" "context" + "errors" + "fmt" + "strings" "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + "github.com/cybozu-go/neco-tenant-controller/pkg/config" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/pointer" 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/log" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + "sigs.k8s.io/structured-merge-diff/v4/typed" ) // ApplicationReconciler reconciles an Application object type ApplicationReconciler struct { client.Client Scheme *runtime.Scheme + Config *config.Config } //+kubebuilder:rbac:groups=argoproj.io,resources=applications,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=argoproj.io,resources=applications/status,verbs=get;update;patch func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) - app := argocd.Application() if err := r.Get(ctx, req.NamespacedName, app); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } - logger.Info("reconcile application", "spec", app.Object) + var argocdApp *unstructured.Unstructured + var tenantApp *unstructured.Unstructured + if req.Namespace == r.Config.ArgoCD.Namespace { + if app.GetDeletionTimestamp() != nil { + return ctrl.Result{}, nil + } + argocdApp = app + ownerNs := argocdApp.GetLabels()[constants.OwnerAppNamespace] + if len(ownerNs) == 0 { + return ctrl.Result{}, nil + } + ownerName := argocdApp.GetLabels()[constants.OwnerApplication] + if len(ownerName) == 0 { + return ctrl.Result{}, nil + } + tenantApp = argocd.Application() + err := r.Get(ctx, client.ObjectKey{Namespace: ownerNs, Name: ownerName}, tenantApp) + if err != nil { + return ctrl.Result{}, err + } + } else { + tenantApp = app + argocdApp = argocd.Application() + err := r.Get(ctx, client.ObjectKey{Namespace: r.Config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp) + if err != nil && !apierrors.IsNotFound(err) { + return ctrl.Result{}, err + } + if apierrors.IsNotFound(err) { + argocdApp = nil + } + + if tenantApp.GetDeletionTimestamp() != nil { + res, err := r.finalize(ctx, argocdApp, tenantApp) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to finalize: %w", err) + } + return res, nil + } + } + + err := r.reconcileApplication(ctx, argocdApp, tenantApp) + if err != nil { + return ctrl.Result{}, err + } return ctrl.Result{}, nil } +func (r *ApplicationReconciler) finalize(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) (ctrl.Result, error) { + logger := log.FromContext(ctx) + if !controllerutil.ContainsFinalizer(tenantApp, constants.Finalizer) && + !controllerutil.ContainsFinalizer(tenantApp, argocd.ResourcesFinalizer) { + return ctrl.Result{}, nil + } + if argocdApp == nil { + controllerutil.RemoveFinalizer(tenantApp, constants.Finalizer) + controllerutil.RemoveFinalizer(tenantApp, argocd.ResourcesFinalizer) + err := r.Update(ctx, tenantApp) + if err != nil { + return ctrl.Result{}, err + } + logger.Info("finished finalization") + return ctrl.Result{}, nil + } + if argocdApp.GetDeletionTimestamp() != nil { + return ctrl.Result{Requeue: true}, nil + } + + logger.Info("starting finalization") + err := r.Delete(ctx, argocdApp) + if err != nil { + return ctrl.Result{}, err + } + return ctrl.Result{Requeue: true}, nil +} + +func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { + logger := log.FromContext(ctx) + err := r.syncApplicationSpec(ctx, argocdApp, tenantApp) + if err != nil { + logger.Error(err, "failed to sync application spec") + return err + } + err = r.syncApplicationStatus(ctx, argocdApp, tenantApp) + if err != nil { + logger.Error(err, "failed to sync application status") + return err + } + return nil +} + +func (r *ApplicationReconciler) extractManagedFields(u *unstructured.Unstructured, manager string) (map[string]interface{}, error) { + fieldset := &fieldpath.Set{} + objManagedFields := u.GetManagedFields() + for _, mf := range objManagedFields { + if mf.Manager != manager || mf.Operation != metav1.ManagedFieldsOperationApply { + continue + } + fs := &fieldpath.Set{} + err := fs.FromJSON(bytes.NewReader(mf.FieldsV1.Raw)) + if err != nil { + return nil, err + } + fieldset = fieldset.Union(fs) + } + + d, err := typed.DeducedParseableType.FromUnstructured(u.Object) + if err != nil { + return nil, err + } + + x := d.ExtractItems(fieldset.Leaves()).AsValue().Unstructured() + m, ok := x.(map[string]interface{}) + if !ok { + return nil, errors.New("cannot cast") + } + + m["apiVersion"] = "argoproj.io/" + argocd.ApplicationVersion + m["kind"] = "Application" + m["metadata"].(map[string]interface{})["name"] = u.GetName() + m["metadata"].(map[string]interface{})["namespace"] = r.Config.ArgoCD.Namespace + return m, nil +} + +func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { + logger := log.FromContext(ctx) + + labels := make(map[string]string) + for k, v := range tenantApp.GetLabels() { + if strings.Contains(k, "kubernetes.io/") { + continue + } + labels[k] = v + } + labels[constants.OwnerApplication] = tenantApp.GetName() + labels[constants.OwnerAppNamespace] = tenantApp.GetNamespace() + + annotations := make(map[string]string) + for k, v := range tenantApp.GetAnnotations() { + if strings.Contains(k, "kubernetes.io/") { + continue + } + annotations[k] = v + } + var finalizers []string + for _, fin := range tenantApp.GetFinalizers() { + if fin == argocd.ResourcesFinalizer { + finalizers = append(finalizers, fin) + } + } + + newApp := argocd.Application() + newApp.UnstructuredContent()["spec"] = tenantApp.DeepCopy().UnstructuredContent()["spec"] + newApp.SetName(tenantApp.GetName()) + newApp.SetNamespace(r.Config.ArgoCD.Namespace) + if len(labels) != 0 { + newApp.SetLabels(labels) + } + if len(annotations) != 0 { + newApp.SetAnnotations(annotations) + } + if len(finalizers) != 0 { + newApp.SetFinalizers(finalizers) + } + + if argocdApp != nil { + managed, err := r.extractManagedFields(argocdApp, constants.FieldManager) + if err != nil { + logger.Error(err, "failed to extract managed fields") + return err + } + if equality.Semantic.DeepEqual(managed, newApp.UnstructuredContent()) { + return nil + } + } + + return r.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ + Force: pointer.BoolPtr(true), + FieldManager: constants.FieldManager, + }) +} + +func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { + if argocdApp == nil || + argocdApp.UnstructuredContent()["status"] == nil || + equality.Semantic.DeepEqual(argocdApp.UnstructuredContent()["status"], tenantApp.UnstructuredContent()["status"]) { + return nil + } + + newApp := argocd.Application() + newApp.SetNamespace(tenantApp.GetNamespace()) + newApp.SetName(tenantApp.GetName()) + newApp.UnstructuredContent()["spec"] = tenantApp.DeepCopy().UnstructuredContent()["spec"] + newApp.UnstructuredContent()["status"] = argocdApp.DeepCopy().UnstructuredContent()["status"] + + // MEMO: Use `r.Patch` instead of `r.Status().Patch()`, because the status of application is not a sub-resource. + return r.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ + Force: pointer.BoolPtr(true), + FieldManager: constants.FieldManager, + }) +} + // SetupWithManager sets up the controller with the Manager. func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index f73763f..42411fc 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -108,6 +108,7 @@ func containNamespace(roots []multitenancyv1beta1.NamespaceSpec, ns corev1.Names } func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *multitenancyv1beta1.Tenant, orphan bool) error { + logger := log.FromContext(ctx) nss := &corev1.NamespaceList{} if err := r.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) @@ -116,6 +117,7 @@ func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *mult if orphan && containNamespace(tenant.Spec.Namespaces, ns) { continue } + logger.Info("Remove labels", "ns", ns) newNs := ns.DeepCopy() delete(newNs.Labels, constants.OwnerTenant) delete(newNs.Labels, r.Config.Namespace.GroupKey) @@ -170,8 +172,6 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult return err } - logger.Info("updated namespace", "op", op) - rb := &rbacv1.RoleBinding{} rb.SetNamespace(ns.Name) rb.SetName(tenant.Name + "-admin") @@ -264,7 +264,6 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten if !ok { destinations = []map[string]interface{}{} } - for _, ns := range nss.Items { destinations = append(destinations, map[string]interface{}{ "namespace": ns.Name, @@ -347,6 +346,10 @@ func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { func SetupIndexForNamespace(ctx context.Context, mgr manager.Manager, groupKey string) error { ns := &corev1.Namespace{} return mgr.GetFieldIndexer().IndexField(ctx, ns, constants.NamespaceGroupKey, func(rawObj client.Object) []string { + nsType := rawObj.GetLabels()["accurate.cybozu.com/type"] + if nsType != "root" { + return nil + } group := rawObj.GetLabels()[groupKey] if group == "" { return nil diff --git a/hooks/application.go b/hooks/application.go index add578a..8689181 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -2,43 +2,118 @@ package hooks import ( "context" + "encoding/json" + "errors" + "fmt" "net/http" "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + "github.com/cybozu-go/neco-tenant-controller/pkg/config" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) +//+kubebuilder:webhook:path=/mutate-argoproj-io-application,mutating=true,failurePolicy=fail,sideEffects=None,groups=argoproj.io,resources=applications,verbs=create;update,versions=v1alpha1,name=mapplication.kb.io,admissionReviewVersions={v1} + +type applicationMutator struct { + dec *admission.Decoder + config *config.Config +} + +var _ admission.Handler = &applicationMutator{} + +func (m *applicationMutator) Handle(ctx context.Context, req admission.Request) admission.Response { + if req.Operation != admissionv1.Create { + return admission.Allowed("") + } + + app := argocd.Application() + if err := m.dec.Decode(req, app); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } + if app.GetNamespace() == m.config.ArgoCD.Namespace { + return admission.Allowed("") + } + + app.SetFinalizers([]string{constants.Finalizer}) + data, err := json.Marshal(app) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + return admission.PatchResponseFromRaw(req.Object.Raw, data) +} + //+kubebuilder:webhook:path=/validate-argoproj-io-application,mutating=false,failurePolicy=fail,sideEffects=None,groups=argoproj.io,resources=applications,verbs=create;update,versions=v1alpha1,name=vapplication.kb.io,admissionReviewVersions={v1} type applicationValidator struct { client.Client - dec *admission.Decoder + dec *admission.Decoder + config *config.Config } var _ admission.Handler = &applicationValidator{} func (v *applicationValidator) Handle(ctx context.Context, req admission.Request) admission.Response { - if req.Operation != admissionv1.Create { + if req.Operation != admissionv1.Create && req.Operation != admissionv1.Update { return admission.Allowed("") } + app := argocd.Application() if err := v.dec.Decode(req, app); err != nil { return admission.Errored(http.StatusBadRequest, err) } - return admission.Allowed("") + + if app.GetNamespace() == v.config.ArgoCD.Namespace { + return admission.Allowed("") + } + + ns := &corev1.Namespace{} + err := v.Client.Get(ctx, client.ObjectKey{Name: app.GetNamespace()}, ns) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + group, ok := ns.Labels[v.config.Namespace.GroupKey] + if !ok { + return admission.Denied("an application cannot be created on unmanaged namespaces") + } + + project, found, err := unstructured.NestedString(app.UnstructuredContent(), "spec", "project") + if err != nil { + return admission.Errored(http.StatusBadRequest, fmt.Errorf("unable to get spec.project; %w", err)) + } + if !found { + return admission.Errored(http.StatusBadRequest, errors.New("spec.project not found")) + } + + if group != project { + return admission.Denied("cannot specify projects for other tenants") + } + + return admission.Allowed("ok") } // SetupApplicationWebhook registers the webhooks for Application -func SetupApplicationWebhook(mgr manager.Manager, dec *admission.Decoder) { +func SetupApplicationWebhook(mgr manager.Manager, dec *admission.Decoder, config *config.Config) { serv := mgr.GetWebhookServer() + m := &applicationMutator{ + dec: dec, + config: config, + } + serv.Register("/mutate-argoproj-io-application", &webhook.Admission{Handler: m}) + v := &applicationValidator{ Client: mgr.GetClient(), dec: dec, + config: config, } serv.Register("/validate-argoproj-io-application", &webhook.Admission{Handler: v}) } diff --git a/pkg/argocd/application.go b/pkg/argocd/application.go index 6158d6f..eb7109c 100644 --- a/pkg/argocd/application.go +++ b/pkg/argocd/application.go @@ -9,7 +9,7 @@ func Application() *unstructured.Unstructured { app := &unstructured.Unstructured{} app.SetGroupVersionKind(schema.GroupVersionKind{ Group: "argoproj.io", - Version: "v1alpha1", + Version: ApplicationVersion, Kind: "Application", }) return app diff --git a/pkg/argocd/appproject.go b/pkg/argocd/appproject.go index 746ee86..7ad8e9d 100644 --- a/pkg/argocd/appproject.go +++ b/pkg/argocd/appproject.go @@ -9,7 +9,7 @@ func AppProject() *unstructured.Unstructured { app := &unstructured.Unstructured{} app.SetGroupVersionKind(schema.GroupVersionKind{ Group: "argoproj.io", - Version: "v1alpha1", + Version: AppProjectVersion, Kind: "AppProject", }) return app diff --git a/pkg/argocd/constants.go b/pkg/argocd/constants.go new file mode 100644 index 0000000..94de589 --- /dev/null +++ b/pkg/argocd/constants.go @@ -0,0 +1,7 @@ +package argocd + +const ( + ApplicationVersion = "v1alpha1" + AppProjectVersion = "v1alpha1" + ResourcesFinalizer = "resources-finalizer.argocd.argoproj.io" +) diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go index a5e5b2c..b1970d9 100644 --- a/pkg/constants/meta.go +++ b/pkg/constants/meta.go @@ -8,4 +8,7 @@ const Finalizer = MetaPrefix + "finalizer" const OwnerTenant = MetaPrefix + "owner-tenant" +const OwnerApplication = MetaPrefix + "owner-application" +const OwnerAppNamespace = MetaPrefix + "owner-app-namespace" + const FieldManager = MetaPrefix + "neco-tenant-controller" From f2257f12a57e63b1fc29ead231df5ce3835a3b42 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 21 Dec 2021 22:46:40 +0900 Subject: [PATCH 12/89] Finalize Signed-off-by: zoetrope --- cmd/neco-tenant-controller/sub/run.go | 2 +- config/samples/application.yaml | 6 +- config/samples/tenant.yaml | 13 ++++- controllers/application_controller.go | 82 ++++++++++++++++++++++++--- controllers/tenant_controller.go | 50 +++++++++++++++- hooks/application.go | 22 +++++++ pkg/argocd/application.go | 10 ++++ pkg/constants/meta.go | 1 - 8 files changed, 167 insertions(+), 19 deletions(-) diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index be20488..efa6b4e 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -79,7 +79,7 @@ func subMain(ns, addr string, port int) error { Client: mgr.GetClient(), Scheme: mgr.GetScheme(), Config: cfg, - }).SetupWithManager(mgr); err != nil { + }).SetupWithManager(ctx, mgr); err != nil { return fmt.Errorf("unable to create Namespace controller: %w", err) } diff --git a/config/samples/application.yaml b/config/samples/application.yaml index 9c4170d..b734913 100644 --- a/config/samples/application.yaml +++ b/config/samples/application.yaml @@ -2,20 +2,20 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: sample - namespace: app-sample + namespace: sub-1 labels: foo: bar finalizers: - resources-finalizer.argocd.argoproj.io spec: - project: tenant-sample + project: a-team source: repoURL: https://github.com/neco-test/apps-sandbox.git targetRevision: master path: testhttpd destination: server: https://kubernetes.default.svc - namespace: app-test + namespace: sub-1 syncPolicy: automated: prune: true diff --git a/config/samples/tenant.yaml b/config/samples/tenant.yaml index 6293e15..7b9cadb 100644 --- a/config/samples/tenant.yaml +++ b/config/samples/tenant.yaml @@ -1,9 +1,16 @@ apiVersion: multi-tenancy.cybozu.com/v1beta1 kind: Tenant metadata: - name: tenant-sample + name: a-team spec: namespaces: - - name: app-sample -# - name: app-foo + - name: app-a +--- +apiVersion: multi-tenancy.cybozu.com/v1beta1 +kind: Tenant +metadata: + name: b-team +spec: + namespaces: + - name: app-b diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 85d7795..8500b76 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -7,6 +7,16 @@ import ( "fmt" "strings" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + + "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/source" + + corev1 "k8s.io/api/core/v1" + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" "github.com/cybozu-go/neco-tenant-controller/pkg/config" "github.com/cybozu-go/neco-tenant-controller/pkg/constants" @@ -50,12 +60,8 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) if len(ownerNs) == 0 { return ctrl.Result{}, nil } - ownerName := argocdApp.GetLabels()[constants.OwnerApplication] - if len(ownerName) == 0 { - return ctrl.Result{}, nil - } tenantApp = argocd.Application() - err := r.Get(ctx, client.ObjectKey{Namespace: ownerNs, Name: ownerName}, tenantApp) + err := r.Get(ctx, client.ObjectKey{Namespace: ownerNs, Name: argocdApp.GetName()}, tenantApp) if err != nil { return ctrl.Result{}, err } @@ -117,7 +123,14 @@ func (r *ApplicationReconciler) finalize(ctx context.Context, argocdApp *unstruc func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { logger := log.FromContext(ctx) - err := r.syncApplicationSpec(ctx, argocdApp, tenantApp) + + err := r.validateProject(ctx, tenantApp) + if err != nil { + logger.Error(err, "failed to validate application project") + return err + } + + err = r.syncApplicationSpec(ctx, argocdApp, tenantApp) if err != nil { logger.Error(err, "failed to sync application spec") return err @@ -130,6 +143,36 @@ func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocd return nil } +func (r *ApplicationReconciler) validateProject(ctx context.Context, tenantApp *unstructured.Unstructured) error { + logger := log.FromContext(ctx) + + ns := &corev1.Namespace{} + err := r.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) + if err != nil { + return err + } + group := ns.Labels[r.Config.Namespace.GroupKey] + if group == "" { + logger.Info("Remove unmanaged application") + return r.Delete(ctx, tenantApp) + } + project, found, err := unstructured.NestedString(tenantApp.UnstructuredContent(), "spec", "project") + if err != nil { + return err + } + if !found { + return errors.New("spec.project not found") + } + if project != group { + logger.Info("Overwrite project", "before", project, "after", group) + err := unstructured.SetNestedField(tenantApp.UnstructuredContent(), group, "spec", "project") + if err != nil { + return err + } + } + return nil +} + func (r *ApplicationReconciler) extractManagedFields(u *unstructured.Unstructured, manager string) (map[string]interface{}, error) { fieldset := &fieldpath.Set{} objManagedFields := u.GetManagedFields() @@ -173,7 +216,6 @@ func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdA } labels[k] = v } - labels[constants.OwnerApplication] = tenantApp.GetName() labels[constants.OwnerAppNamespace] = tenantApp.GetNamespace() annotations := make(map[string]string) @@ -242,8 +284,32 @@ func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argoc } // SetupWithManager sets up the controller with the Manager. -func (r *ApplicationReconciler) SetupWithManager(mgr ctrl.Manager) error { +func (r *ApplicationReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error { + logger := log.FromContext(ctx) + + nsHandler := func(o client.Object, q workqueue.RateLimitingInterface) { + apps := argocd.ApplicationList() + err := mgr.GetClient().List(ctx, apps, client.InNamespace(o.GetName())) + if err != nil { + logger.Error(err, "failed to list applications") + return + } + for _, app := range apps.Items { + q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ + Namespace: app.GetNamespace(), + Name: app.GetName(), + }}) + } + } return ctrl.NewControllerManagedBy(mgr). For(argocd.Application()). + Watches(&source.Kind{Type: &corev1.Namespace{}}, handler.Funcs{ + UpdateFunc: func(ev event.UpdateEvent, q workqueue.RateLimitingInterface) { + if ev.ObjectNew.GetDeletionTimestamp() != nil { + return + } + nsHandler(ev.ObjectOld, q) + }, + }). Complete(r) } diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 42411fc..59baf77 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -26,7 +26,7 @@ import ( "github.com/cybozu-go/neco-tenant-controller/pkg/constants" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" - "k8s.io/apimachinery/pkg/api/errors" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" @@ -130,6 +130,41 @@ func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *mult return nil } +func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { + nss := &corev1.NamespaceList{} + if err := r.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + return fmt.Errorf("failed to list namespaces: %w", err) + } + + for _, ns := range nss.Items { + rb := &rbacv1.RoleBinding{} + err := r.Client.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: tenant.Name + "-admin"}, rb) + if apierrors.IsNotFound(err) { + continue + } + if err != nil { + return err + } + err = r.Client.Delete(ctx, rb) + if err != nil { + return err + } + } + return nil +} + +func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { + proj := argocd.AppProject() + err := r.Get(ctx, client.ObjectKey{Namespace: r.Config.ArgoCD.Namespace, Name: tenant.Name}, proj) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return r.Client.Delete(ctx, proj) +} + func (r *TenantReconciler) finalize(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { if !controllerutil.ContainsFinalizer(tenant, constants.Finalizer) { return nil @@ -139,6 +174,14 @@ func (r *TenantReconciler) finalize(ctx context.Context, tenant *multitenancyv1b if err != nil { return err } + err = r.removeRBAC(ctx, tenant) + if err != nil { + return err + } + err = r.removeAppProject(ctx, tenant) + if err != nil { + return err + } controllerutil.RemoveFinalizer(tenant, constants.Finalizer) return r.Update(ctx, tenant) @@ -237,7 +280,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten proj := argocd.AppProject() err := r.Get(ctx, client.ObjectKey{Namespace: r.Config.ArgoCD.Namespace, Name: tenant.Name}, proj) - if err != nil && !errors.IsNotFound(err) { + if err != nil && !apierrors.IsNotFound(err) { return err } @@ -248,7 +291,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten } nss := &corev1.NamespaceList{} - if err := r.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + if err := r.List(ctx, nss, client.MatchingLabels{r.Config.Namespace.GroupKey: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } @@ -260,6 +303,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten spec := proj.UnstructuredContent()["spec"].(map[string]interface{}) + logger.Info("nss", "count", len(nss.Items)) destinations, ok := spec["destinations"].([]map[string]interface{}) if !ok { destinations = []map[string]interface{}{} diff --git a/hooks/application.go b/hooks/application.go index 8689181..8250f1c 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -74,6 +74,10 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request return admission.Allowed("") } + if app.GetDeletionTimestamp() != nil { + return admission.Allowed("") + } + ns := &corev1.Namespace{} err := v.Client.Get(ctx, client.ObjectKey{Name: app.GetNamespace()}, ns) if err != nil { @@ -85,6 +89,24 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request return admission.Denied("an application cannot be created on unmanaged namespaces") } + apps := argocd.ApplicationList() + err = v.Client.List(ctx, apps, client.InNamespace(v.config.ArgoCD.Namespace)) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + for _, a := range apps.Items { + if app.GetName() == a.GetName() { + ownerNs := a.GetLabels()[constants.OwnerAppNamespace] + if ownerNs == "" { + break + } + if app.GetNamespace() == ownerNs { + break + } + return admission.Denied("cannot create an application with the same name") + } + } + project, found, err := unstructured.NestedString(app.UnstructuredContent(), "spec", "project") if err != nil { return admission.Errored(http.StatusBadRequest, fmt.Errorf("unable to get spec.project; %w", err)) diff --git a/pkg/argocd/application.go b/pkg/argocd/application.go index eb7109c..69b6da4 100644 --- a/pkg/argocd/application.go +++ b/pkg/argocd/application.go @@ -14,3 +14,13 @@ func Application() *unstructured.Unstructured { }) return app } + +func ApplicationList() *unstructured.UnstructuredList { + apps := &unstructured.UnstructuredList{} + apps.SetGroupVersionKind(schema.GroupVersionKind{ + Group: "argoproj.io", + Version: ApplicationVersion, + Kind: "ApplicationList", + }) + return apps +} diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go index b1970d9..75a59bf 100644 --- a/pkg/constants/meta.go +++ b/pkg/constants/meta.go @@ -8,7 +8,6 @@ const Finalizer = MetaPrefix + "finalizer" const OwnerTenant = MetaPrefix + "owner-tenant" -const OwnerApplication = MetaPrefix + "owner-application" const OwnerAppNamespace = MetaPrefix + "owner-app-namespace" const FieldManager = MetaPrefix + "neco-tenant-controller" From 60c16094176bb5fc5ee580c7ad8486553252fe22 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 23 Dec 2021 19:22:32 +0900 Subject: [PATCH 13/89] Setup Signed-off-by: zoetrope --- e2e/Makefile | 43 +++++++++++++++---- e2e/accurate-values.yaml | 11 +++++ .../kind-with-registry.sh | 2 +- .../teardown-kind-with-registry.sh | 0 4 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 e2e/accurate-values.yaml rename kind-with-registry.sh => e2e/kind-with-registry.sh (97%) rename teardown-kind-with-registry.sh => e2e/teardown-kind-with-registry.sh (100%) diff --git a/e2e/Makefile b/e2e/Makefile index 9d3a62e..a004e95 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -1,9 +1,13 @@ KIND_VERSION = 0.11.1 KUBERNETES_VERSION = 1.22.2 +ARGOCD_VERSION = 2.1.8 +HELM_VERSION = 3.7.1 -KIND := $(dir $(shell pwd))/bin/kind -KUBECTL := $(dir $(shell pwd))/bin/kubectl -KUBECONFIG := $(shell pwd)/.kubeconfig +BIN_DIR := $(dir $(shell pwd))/bin +KIND := $(BIN_DIR)/kind +KUBECTL := $(BIN_DIR)/kubectl +HELM := $(BIN_DIR)/helm +ARGOCD = $(BIN_DIR)/argocd KIND_CONFIG = kind-config.yaml export KUBECTL KUBECONFIG @@ -18,15 +22,26 @@ help: @echo "stop Stop the kind cluster" .PHONY: start -start: $(KIND) $(KUBECTL) +start: $(KIND) $(KUBECTL) $(HELM) $(KIND) create cluster --name=neco-tenant-controller --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m cd ..; docker build --no-cache -t neco-tenant-controller:dev . $(KIND) load docker-image neco-tenant-controller:dev --name=neco-tenant-controller - $(KUBECTL) apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml - $(KUBECTL) -n cert-manager wait --for=condition=available --timeout=180s --all deployments + $(MAKE) prepare $(KUBECTL) apply -k ../config/default $(KUBECTL) -n tenant-system wait --for=condition=available --timeout=180s --all deployments +.PHONY: prepare +prepare: + $(KUBECTL) apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml + $(KUBECTL) -n cert-manager wait --for=condition=available --timeout=180s --all deployments + $(KUBECTL) create ns argocd + curl -sSLf https://raw.githubusercontent.com/argoproj/argo-cd/v$(ARGOCD_VERSION)/manifests/install.yaml | $(KUBECTL) -n argocd apply -f - + $(KUBECTL) -n argocd wait --for=condition=available --timeout=180s --all deployments + $(HELM) repo add accurate https://cybozu-go.github.io/accurate/ + $(HELM) repo update + $(HELM) install --create-namespace --namespace accurate accurate -f accurate-values.yaml accurate/accurate + $(KUBECTL) -n accurate wait --for=condition=available --timeout=180s --all deployments + .PHONY: test test: env PATH=$$(pwd)/../bin:$$PATH RUN_E2E=1 \ @@ -46,11 +61,23 @@ stop: $(KIND) -docker image prune -f $(KIND): - mkdir -p ../bin + mkdir -p $(BIN_DIR) curl -sfL -o $@ https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)/kind-linux-amd64 chmod a+x $@ $(KUBECTL): - mkdir -p ../bin + mkdir -p $(BIN_DIR) curl -sfL -o $@ https://dl.k8s.io/release/v$(KUBERNETES_VERSION)/bin/linux/amd64/kubectl chmod a+x $@ + +$(HELM): + mkdir -p $(BIN_DIR) + curl -L -sS https://get.helm.sh/helm-v$(HELM_VERSION)-linux-amd64.tar.gz \ + | tar xz -C $(BIN_DIR) --strip-components 1 linux-amd64/helm + +.PHONY: argocd +argocd: $(ARGOCD) +$(ARGOCD): + mkdir -p $(BIN_DIR) + curl -sSLf https://github.com/argoproj/argo-cd/releases/download/v$(ARGOCD_VERSION)/argocd-linux-amd64 -o $(ARGOCD) + chmod 755 $(ARGOCD) diff --git a/e2e/accurate-values.yaml b/e2e/accurate-values.yaml new file mode 100644 index 0000000..3d456c5 --- /dev/null +++ b/e2e/accurate-values.yaml @@ -0,0 +1,11 @@ +controller: + config: + labelKeys: + - team + watches: + - group: rbac.authorization.k8s.io + version: v1 + kind: Role + - group: rbac.authorization.k8s.io + version: v1 + kind: RoleBinding diff --git a/kind-with-registry.sh b/e2e/kind-with-registry.sh similarity index 97% rename from kind-with-registry.sh rename to e2e/kind-with-registry.sh index f586fcc..ecb6724 100755 --- a/kind-with-registry.sh +++ b/e2e/kind-with-registry.sh @@ -22,7 +22,7 @@ set -o errexit # desired cluster name; default is "kind" KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}" -KIND_CLUSTER_OPTS="--name ${KIND_CLUSTER_NAME}" +KIND_CLUSTER_OPTS="--name ${KIND_CLUSTER_NAME} --wait 3m" if [ -n "${KIND_CLUSTER_IMAGE}" ]; then KIND_CLUSTER_OPTS="${KIND_CLUSTER_OPTS} --image ${KIND_CLUSTER_IMAGE}" diff --git a/teardown-kind-with-registry.sh b/e2e/teardown-kind-with-registry.sh similarity index 100% rename from teardown-kind-with-registry.sh rename to e2e/teardown-kind-with-registry.sh From 9bcdcdb799c894bdf32d614bef8bfe9928fb55a2 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 23 Dec 2021 19:25:24 +0900 Subject: [PATCH 14/89] WIP docs Signed-off-by: zoetrope --- docs/config.md | 22 ++++++++++-- docs/design.md | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ docs/overview.md | 21 ++++-------- 3 files changed, 114 insertions(+), 17 deletions(-) create mode 100644 docs/design.md diff --git a/docs/config.md b/docs/config.md index 6572cf7..b0ce8d1 100644 --- a/docs/config.md +++ b/docs/config.md @@ -7,10 +7,10 @@ The repository includes an example as follows: ```yaml -namespaces: +namespace: # Labels to add to all namespaces to be deployed by neco-tenant-controller commonLabels: - - accurate.cybozu.com/template: init-template + accurate.cybozu.com/template: init-template argocd: # The name of namespace where Argo CD is deployed @@ -19,6 +19,24 @@ argocd: # If true is set, this does not deny Application resources but issues a warning. permissiveValidation: true + organization: "cybozu-go" + + appProjectTemplate: | + apiVersion: argoproj.io/v1alpha1 + kind: AppProject + spec: + namespaceResourceBlacklist: + - group: "" + kind: ResourceQuota + - group: "" + kind: LimitRange + - group: networking.k8s.io + kind: NetworkPolicy + orphanedResources: + warn: false + sourceRepos: + - '*' + teleport: # The name of namespace where Teleport Nodes are deployed namespace: teleport diff --git a/docs/design.md b/docs/design.md new file mode 100644 index 0000000..75eba7b --- /dev/null +++ b/docs/design.md @@ -0,0 +1,88 @@ +# Design notes + +## Overview + +`neco-tenant-controller` is a custom controller that uses [Accurate][] and [Argo CD][] to provide multi-tenant environment for Kubernetes cluster. + +## Motivation + +We have developed the following mechanism for multi-tenant [Argo CD][]. + +https://blog.kintone.io/entry/production-grade-delivery-workflow-using-argocd#Multi-tenancy + +However, the above mechanism has the following problems: + +- Tenant users cannot create app-of-apps Application resources. They need to ask an administrator for that. +- Application resources are not strictly validated. Tenant users can specify Project for other tenants, and can also specify duplicate names. +- When a SubNamespace is created in [HNC][] or [Accurate][], an administrator needs to add it to the destinations of Application resources. + (Argo CD supports specifying wildcards in destinations, but that is not enough for us.) + +## Goals + +- Develop a custom controller to automate the configuration for multi-tenancy. +- Automates the creation of root-namespaces and AppProject for administrators. +- Allow tenant users to create Application resources in any namespace. +- Perform strict validation of Application resources for security. + +## User stories + +### Adding a team + +Administrators only need to create one custom resource to add a team to a Kubernetes cluster. +No more manual operations to add Namespaces and Applications. + +### Adding an app-of-apps Application + +### Changing ownership + + +## Upgrade Strategy + + +## Alternatives + +### Argo CD: Multi-tenancy improvements + +Argo CD will improve multi-tenancy in the future. + +https://argo-cd.readthedocs.io/en/stable/roadmap/#multi-tenancy-improvements + +The above proposal would allow us to create Application resources in any namespace. + +If this feature is supported, we will migrate to it immediately. +So we need to design our controller to be easy to migrate to. + +### ApplicationSet + +ApplicationSet is one of the features of Argo CD which generates Application resources based on user input. + +https://argo-cd.readthedocs.io/en/stable/user-guide/application-set/ + +However, this feature does not give tenant users enough flexibility in their settings. + +### AppSource Controller + +AppSource controller is similar to our proposal. + +https://github.com/argoproj-labs/appsource + +But AppSource is still not production-ready. +Also, it does not solve our some problems. + +### Multiple Argo CD instance (argocd-operator) + +We considered having an Argo CD instance for each tenant team, but it turned out to be a permissions problem. + +### Other Continuous Delivery tools + +Other Continuous Delivery tools support multi-tenancy. + +- https://github.com/fluxcd/flux2 +- https://github.com/pipe-cd/pipe + +However, we love Argo CD (the many features and the useful UI). +We already have a lot of manifests managed by Argo CD. It's hard to switch to another tool now. + +[Argo CD]: https://argo-cd.readthedocs.io/ +[HNC]: https://github.com/kubernetes-sigs/hierarchical-namespaces +[Accurate]: https://cybozu-go.github.io/accurate/ diff --git a/docs/overview.md b/docs/overview.md index f219b30..de537e7 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -4,12 +4,14 @@ neco-tenant-controller is a Kubernetes controller to manage tenant team resource ## Features +### For Administrator + Cluster administrator can creates a [Tenant custom resource](./crd_tenant.md) for each tenant team. neco-tenant-controller will automatically apply the following resources needed by the tenant team based on the Tenant resource. - Manage root namespaces - Tenant users can use [Accurate][Accurate] to create a SubNamespace. + Tenant users can use [Accurate][] to create a SubNamespace. Because of that, users need a root namespace. neco-tenant-controller creates multiple root namespaces for each tenant team. Those namespaces are permission-controlled so that only the tenant users can access them. @@ -20,6 +22,9 @@ neco-tenant-controller will automatically apply the following resources needed b The AppProject can control namespaces where tenant users can deploy manifests. neco-tenant-controller dynamically rewrites the AppProject resource each time tenant users creates a SubNamespace. + +### For Tenant Users + - Manage Argo CD Application resources Tenant users need Application resources to deploy their manifests with Argo CD. @@ -31,18 +36,4 @@ neco-tenant-controller will automatically apply the following resources needed b However, that's not appropriate from a security perspective. Admission webhook will deny the creation of Application that contains an unauthorized repository. -- Deploy Teleport Node - - Tenant users can use [Teleport][Teleport] to access our Kubernetes cluster. - Because of that, Teleport Node Pod is required. - neco-tenant-controller will deploy Teleport Node Pods for each tenant team. - -- Manage Teleport Applications - - Tenant users can use [Teleport][Teleport] to access applications in our Kubernetes cluster. - Because of that, Teleport Application Pod is required. - neco-tenant-controller will deploy Teleport Application Pods for each tenant team. - -[Accurate]: https://cybozu-go.github.io/accurate/ -[Argo CD]: https://argo-cd.readthedocs.io/ [Teleport]: https://goteleport.com From f41157d83d8f0c56ffeee5c877816f0b0ff178ca Mon Sep 17 00:00:00 2001 From: Akihiro Ikezoe Date: Fri, 24 Dec 2021 15:53:40 +0900 Subject: [PATCH 15/89] Setup dev env Signed-off-by: zoetrope --- Makefile | 19 +++++++++++++++++++ api/v1beta1/zz_generated.deepcopy.go | 1 + .../samples/{tenant.yaml => 01_tenant.yaml} | 0 config/samples/02_namespace.yaml | 13 +++++++++++++ .../{application.yaml => 03_application.yaml} | 0 docs/development.md | 10 ++++++++++ e2e/Makefile | 9 +++++---- go.mod | 2 ++ {e2e => scripts}/kind-with-registry.sh | 11 ++++++++--- .../teardown-kind-with-registry.sh | 5 +++-- 10 files changed, 61 insertions(+), 9 deletions(-) rename config/samples/{tenant.yaml => 01_tenant.yaml} (100%) create mode 100644 config/samples/02_namespace.yaml rename config/samples/{application.yaml => 03_application.yaml} (100%) create mode 100644 docs/development.md rename {e2e => scripts}/kind-with-registry.sh (92%) rename {e2e => scripts}/teardown-kind-with-registry.sh (93%) diff --git a/Makefile b/Makefile index 8b65e5c..b4e317b 100644 --- a/Makefile +++ b/Makefile @@ -85,6 +85,25 @@ build: docker-build: docker build -t neco-tenant-controller:latest . +##@ Development +KIND_CLUSTER_NAME = tenant-dev +KIND := $(shell pwd)/bin/kind + +.PHONY: dev +dev: $(KIND) + KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) KIND_PATH=$(KIND) ./scripts/kind-with-registry.sh + $(MAKE) -C ./e2e/ prepare + +.PHONY: stop-dev +stop-dev: + KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) KIND_PATH=$(KIND) ./scripts/teardown-kind-with-registry.sh + +.PHONY: kind +kind: $(KIND) + +$(KIND): + $(MAKE) -C ./e2e/ kind + ##@ Tools CONTROLLER_GEN := $(shell pwd)/bin/controller-gen diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index b8c4fa0..06e5611 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated // Code generated by controller-gen. DO NOT EDIT. diff --git a/config/samples/tenant.yaml b/config/samples/01_tenant.yaml similarity index 100% rename from config/samples/tenant.yaml rename to config/samples/01_tenant.yaml diff --git a/config/samples/02_namespace.yaml b/config/samples/02_namespace.yaml new file mode 100644 index 0000000..08ece06 --- /dev/null +++ b/config/samples/02_namespace.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: init-template + labels: + accurate.cybozu.com/type: template +--- +apiVersion: v1 +kind: Namespace +metadata: + name: sub-1 + labels: + accurate.cybozu.com/parent: app-a diff --git a/config/samples/application.yaml b/config/samples/03_application.yaml similarity index 100% rename from config/samples/application.yaml rename to config/samples/03_application.yaml diff --git a/docs/development.md b/docs/development.md new file mode 100644 index 0000000..4cea61e --- /dev/null +++ b/docs/development.md @@ -0,0 +1,10 @@ +# Development + +``` +$ make dev +$ tilt up +``` + +``` +$ make stop-dev +``` diff --git a/e2e/Makefile b/e2e/Makefile index a004e95..7659989 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -3,7 +3,7 @@ KUBERNETES_VERSION = 1.22.2 ARGOCD_VERSION = 2.1.8 HELM_VERSION = 3.7.1 -BIN_DIR := $(dir $(shell pwd))/bin +BIN_DIR := $(dir $(shell pwd))bin KIND := $(BIN_DIR)/kind KUBECTL := $(BIN_DIR)/kubectl HELM := $(BIN_DIR)/helm @@ -11,7 +11,6 @@ ARGOCD = $(BIN_DIR)/argocd KIND_CONFIG = kind-config.yaml export KUBECTL KUBECONFIG - .PHONY: help help: @echo "Choose one of the following target" @@ -22,7 +21,7 @@ help: @echo "stop Stop the kind cluster" .PHONY: start -start: $(KIND) $(KUBECTL) $(HELM) +start: $(KIND) $(KUBECTL) $(KIND) create cluster --name=neco-tenant-controller --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m cd ..; docker build --no-cache -t neco-tenant-controller:dev . $(KIND) load docker-image neco-tenant-controller:dev --name=neco-tenant-controller @@ -31,7 +30,7 @@ start: $(KIND) $(KUBECTL) $(HELM) $(KUBECTL) -n tenant-system wait --for=condition=available --timeout=180s --all deployments .PHONY: prepare -prepare: +prepare: $(KUBECTL) $(HELM) $(KUBECTL) apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml $(KUBECTL) -n cert-manager wait --for=condition=available --timeout=180s --all deployments $(KUBECTL) create ns argocd @@ -60,6 +59,8 @@ stop: $(KIND) -docker image rm neco-tenant-controller:dev -docker image prune -f +.PHONY: kind +kind: $(KIND) $(KIND): mkdir -p $(BIN_DIR) curl -sfL -o $@ https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)/kind-linux-amd64 diff --git a/go.mod b/go.mod index 23587dd..b867e6a 100644 --- a/go.mod +++ b/go.mod @@ -10,6 +10,8 @@ require ( k8s.io/apimachinery v0.22.2 k8s.io/client-go v0.22.2 k8s.io/klog v1.0.0 + k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a sigs.k8s.io/controller-runtime v0.10.3 + sigs.k8s.io/structured-merge-diff/v4 v4.1.2 sigs.k8s.io/yaml v1.2.0 ) diff --git a/e2e/kind-with-registry.sh b/scripts/kind-with-registry.sh similarity index 92% rename from e2e/kind-with-registry.sh rename to scripts/kind-with-registry.sh index ecb6724..4bb6dcf 100755 --- a/e2e/kind-with-registry.sh +++ b/scripts/kind-with-registry.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/bin/bash -ex # # Adapted from: # https://github.com/tilt-dev/kind-local/blob/master/kind-with-registry.sh @@ -20,6 +20,7 @@ set -o errexit +KIND_PATH="${KIND_PATH:-kind}" # desired cluster name; default is "kind" KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}" KIND_CLUSTER_OPTS="--name ${KIND_CLUSTER_NAME} --wait 3m" @@ -28,7 +29,7 @@ if [ -n "${KIND_CLUSTER_IMAGE}" ]; then KIND_CLUSTER_OPTS="${KIND_CLUSTER_OPTS} --image ${KIND_CLUSTER_IMAGE}" fi -kind_version=$(kind version) +kind_version=$(${KIND_PATH} version) kind_network='kind' reg_name='kind-registry' reg_port='5000' @@ -53,9 +54,13 @@ fi echo "Registry Host: ${reg_host}" # create a cluster with the local registry enabled in containerd -cat < Deleting Kind cluster..." -kind delete cluster --name=$KIND_CLUSTER_NAME +${KIND_PATH} delete cluster --name=$KIND_CLUSTER_NAME From 48cfd8e1523654055cd591399a2455c806412093 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 24 Dec 2021 16:27:37 +0900 Subject: [PATCH 16/89] Use ctlptl istead of scripts Signed-off-by: zoetrope --- Makefile | 30 +++++--- cluster.yaml | 10 +++ config/default/kustomization.yaml | 2 +- docs/development.md | 3 +- scripts/kind-with-registry.sh | 94 -------------------------- scripts/teardown-kind-with-registry.sh | 42 ------------ 6 files changed, 33 insertions(+), 148 deletions(-) create mode 100644 cluster.yaml delete mode 100755 scripts/kind-with-registry.sh delete mode 100755 scripts/teardown-kind-with-registry.sh diff --git a/Makefile b/Makefile index b4e317b..e105938 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ CTRL_TOOLS_VERSION=0.7.0 CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) KUSTOMIZE_VERSION = 4.4.1 CRD_TO_MARKDOWN_VERSION = 0.0.3 +TILT_VERSION = 0.23.4 +CTLPTL_VERSION = 0.6.2 # Test tools BIN_DIR := $(shell pwd)/bin @@ -86,23 +88,31 @@ docker-build: docker build -t neco-tenant-controller:latest . ##@ Development -KIND_CLUSTER_NAME = tenant-dev -KIND := $(shell pwd)/bin/kind +TILT := $(shell pwd)/bin/tilt +CTLPTL := $(shell pwd)/bin/ctlptl .PHONY: dev -dev: $(KIND) - KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) KIND_PATH=$(KIND) ./scripts/kind-with-registry.sh +dev: $(CTLPTL) + $(CTLPTL) apply -f ./cluster.yaml $(MAKE) -C ./e2e/ prepare .PHONY: stop-dev -stop-dev: - KIND_CLUSTER_NAME=$(KIND_CLUSTER_NAME) KIND_PATH=$(KIND) ./scripts/teardown-kind-with-registry.sh +stop-dev: $(CTLPTL) + $(CTLPTL) delete -f ./cluster.yaml -.PHONY: kind -kind: $(KIND) +.PHONY: ctlptl +ctlptl: $(CTLPTL) +$(CTLPTL): + mkdir -p bin + curl -fsL https://github.com/tilt-dev/ctlptl/releases/download/v$(CTLPTL_VERSION)/ctlptl.$(CTLPTL_VERSION).linux.x86_64.tar.gz | \ + tar -C bin -xzf - -$(KIND): - $(MAKE) -C ./e2e/ kind +.PHONY: tilt +tilt: $(TILT) +$(TILT): + mkdir -p bin + curl -fsL https://github.com/tilt-dev/tilt/releases/download/v$(TILT_VERSION)/tilt.$(TILT_VERSION).linux.x86_64.tar.gz | \ + tar -C bin -xzf - ##@ Tools diff --git a/cluster.yaml b/cluster.yaml new file mode 100644 index 0000000..092400b --- /dev/null +++ b/cluster.yaml @@ -0,0 +1,10 @@ +apiVersion: ctlptl.dev/v1alpha1 +kind: Registry +name: ctlptl-registry +port: 5005 +--- +apiVersion: ctlptl.dev/v1alpha1 +kind: Cluster +name: kind-dev +product: kind +registry: ctlptl-registry diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index a65cb45..b4869b2 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -28,7 +28,7 @@ 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 +#- manager_auth_proxy_patch.yaml # Mount the controller config file for loading manager configurations # through a ComponentConfig type diff --git a/docs/development.md b/docs/development.md index 4cea61e..dee1d7f 100644 --- a/docs/development.md +++ b/docs/development.md @@ -2,7 +2,8 @@ ``` $ make dev -$ tilt up +$ make tilt +$ ./bin/tilt up ``` ``` diff --git a/scripts/kind-with-registry.sh b/scripts/kind-with-registry.sh deleted file mode 100755 index 4bb6dcf..0000000 --- a/scripts/kind-with-registry.sh +++ /dev/null @@ -1,94 +0,0 @@ -#!/bin/bash -ex -# -# Adapted from: -# https://github.com/tilt-dev/kind-local/blob/master/kind-with-registry.sh -# https://github.com/kubernetes-sigs/kind/commits/master/site/static/examples/kind-with-registry.sh -# -# Copyright 2020 The Kubernetes Project -# -# 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. - -set -o errexit - -KIND_PATH="${KIND_PATH:-kind}" -# desired cluster name; default is "kind" -KIND_CLUSTER_NAME="${KIND_CLUSTER_NAME:-kind}" -KIND_CLUSTER_OPTS="--name ${KIND_CLUSTER_NAME} --wait 3m" - -if [ -n "${KIND_CLUSTER_IMAGE}" ]; then - KIND_CLUSTER_OPTS="${KIND_CLUSTER_OPTS} --image ${KIND_CLUSTER_IMAGE}" -fi - -kind_version=$(${KIND_PATH} version) -kind_network='kind' -reg_name='kind-registry' -reg_port='5000' -case "${kind_version}" in - "kind v0.7."* | "kind v0.6."* | "kind v0.5."*) - kind_network='bridge' - ;; -esac - -# create registry container unless it already exists -running="$(docker inspect -f '{{.State.Running}}' "${reg_name}" 2>/dev/null || true)" -if [ "${running}" != 'true' ]; then - docker run \ - -d --restart=always -p "${reg_port}:5000" --name "${reg_name}" \ - registry:2 -fi - -reg_host="${reg_name}" -if [ "${kind_network}" = "bridge" ]; then - reg_host="$(docker inspect -f '{{.NetworkSettings.IPAddress}}' "${reg_name}")" -fi -echo "Registry Host: ${reg_host}" - -# create a cluster with the local registry enabled in containerd -cat </dev/null || true)" -if [ "${running}" == 'true' ]; then - cid="$(docker inspect -f '{{.ID}}' "${reg_name}")" - echo "> Stopping and deleting Kind Registry container..." - docker stop $cid >/dev/null - docker rm $cid >/dev/null -fi - -echo "> Deleting Kind cluster..." -${KIND_PATH} delete cluster --name=$KIND_CLUSTER_NAME From 2f6bca2426a1de5c65caa120a464b9fe733989b5 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 24 Dec 2021 17:10:31 +0900 Subject: [PATCH 17/89] Templating Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 68 +--------- api/v1beta1/zz_generated.deepcopy.go | 103 +-------------- .../multi-tenancy.cybozu.com_tenants.yaml | 84 ------------ config/manager/configmap.yaml | 38 +++++- controllers/tenant_controller.go | 122 ++++++++---------- pkg/config/types.go | 20 +-- 6 files changed, 94 insertions(+), 341 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index b361b1d..9566815 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -26,8 +26,7 @@ import ( // TenantSpec defines the desired state of Tenant type TenantSpec struct { Namespaces []NamespaceSpec `json:"namespaces,omitempty"` - ArgoCD *ArgoCDSpec `json:"argocd,omitempty"` - Teleport *TeleportSpec `json:"teleport,omitempty"` + ArgoCD ArgoCDSpec `json:"argocd,omitempty"` } // NamespaceSpec defines the desired state of Namespace @@ -52,77 +51,12 @@ type NamespaceSpec struct { // ArgoCDSpec defines the desired state of the settings for Argo CD type ArgoCDSpec struct { - // Applications are the list of Application resources managed by the tenant team. - // +optional - Applications []ArgoCDApplicationSpec `json:"applications,omitempty"` - - // Repositories are the list of repositories used by the tenant team. - // +optional - Repositories []string `json:"repositories,omitempty"` - // ExtraAdmins are the names of the team to add to the AppProject user. // Specify this if you want other tenant teams to be able to use your AppProject. // +optional ExtraAdmins []string `json:"extraAdmins,omitempty"` } -// ArgoCDApplicationSpec defines the desired state of Application -type ArgoCDApplicationSpec struct { - // Name is the name of Application resource. - // +kubebuilder:validation:Required - Name string `json:"name"` - - // Path is a directory path within the Git repository, and is only valid for applications sourced from Git. - // +kubebuilder:validation:Required - Path string `json:"path"` - - // RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. - // +kubebuilder:validation:Required - RepoURL string `json:"repoURL"` - - // TargetRevision defines the revision of the source to sync the application to. - // In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. - // In case of Helm, this is a semver tag for the Chart's version. - // +kubebuilder:validation:Required - TargetRevision string `json:"targetRevision"` -} - -// TeleportSpec defines the desired state of the settings for Teleport -type TeleportSpec struct { - // Node is the settings of Teleport Node for the tenant team. - // +optional - Node *TeleportNodeSpec `json:"node,omitempty"` - - // Applications are the list of applications to be used by the tenant team. - // +optional - Applications []TeleportApplicationSpec `json:"applications,omitempty"` -} - -type TeleportNodeSpec struct { - // Replicas is the number of Teleport Node Pods. - // +kubebuilder:validation:Required - Replicas int `json:"replicas"` - - // ExtraArgs are the list of additional arguments to be specified for Teleport Node Pod. - // +optional - ExtraArgs []string `json:"extraArgs,omitempty"` -} - -// TeleportApplicationSpec defines the desired state of Teleport Application. -type TeleportApplicationSpec struct { - // Name is the name of the application to proxy. - // +kubebuilder:validation:Required - Name string `json:"name"` - - // URL is the internal address of the application to proxy. - // +kubebuilder:validation:Required - URL string `json:"url"` - - // ExtraArgs are the list of additional arguments to be specified for Teleport Application Pod. - // +optional - ExtraArgs []string `json:"extraArgs,omitempty"` -} - // TenantStatus defines the observed state of Tenant type TenantStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 06e5611..70b7f01 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -9,34 +9,9 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *ArgoCDApplicationSpec) DeepCopyInto(out *ArgoCDApplicationSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDApplicationSpec. -func (in *ArgoCDApplicationSpec) DeepCopy() *ArgoCDApplicationSpec { - if in == nil { - return nil - } - out := new(ArgoCDApplicationSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArgoCDSpec) DeepCopyInto(out *ArgoCDSpec) { *out = *in - if in.Applications != nil { - in, out := &in.Applications, &out.Applications - *out = make([]ArgoCDApplicationSpec, len(*in)) - copy(*out, *in) - } - if in.Repositories != nil { - in, out := &in.Repositories, &out.Repositories - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.ExtraAdmins != nil { in, out := &in.ExtraAdmins, &out.ExtraAdmins *out = make([]string, len(*in)) @@ -88,73 +63,6 @@ func (in *NamespaceSpec) DeepCopy() *NamespaceSpec { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeleportApplicationSpec) DeepCopyInto(out *TeleportApplicationSpec) { - *out = *in - if in.ExtraArgs != nil { - in, out := &in.ExtraArgs, &out.ExtraArgs - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportApplicationSpec. -func (in *TeleportApplicationSpec) DeepCopy() *TeleportApplicationSpec { - if in == nil { - return nil - } - out := new(TeleportApplicationSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeleportNodeSpec) DeepCopyInto(out *TeleportNodeSpec) { - *out = *in - if in.ExtraArgs != nil { - in, out := &in.ExtraArgs, &out.ExtraArgs - *out = make([]string, len(*in)) - copy(*out, *in) - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportNodeSpec. -func (in *TeleportNodeSpec) DeepCopy() *TeleportNodeSpec { - if in == nil { - return nil - } - out := new(TeleportNodeSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TeleportSpec) DeepCopyInto(out *TeleportSpec) { - *out = *in - if in.Node != nil { - in, out := &in.Node, &out.Node - *out = new(TeleportNodeSpec) - (*in).DeepCopyInto(*out) - } - if in.Applications != nil { - in, out := &in.Applications, &out.Applications - *out = make([]TeleportApplicationSpec, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TeleportSpec. -func (in *TeleportSpec) DeepCopy() *TeleportSpec { - if in == nil { - return nil - } - out := new(TeleportSpec) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Tenant) DeepCopyInto(out *Tenant) { *out = *in @@ -224,16 +132,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.ArgoCD != nil { - in, out := &in.ArgoCD, &out.ArgoCD - *out = new(ArgoCDSpec) - (*in).DeepCopyInto(*out) - } - if in.Teleport != nil { - in, out := &in.Teleport, &out.Teleport - *out = new(TeleportSpec) - (*in).DeepCopyInto(*out) - } + in.ArgoCD.DeepCopyInto(&out.ArgoCD) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec. diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index 260cd55..1468aca 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -40,38 +40,6 @@ spec: description: ArgoCDSpec defines the desired state of the settings for Argo CD properties: - applications: - description: Applications are the list of Application resources - managed by the tenant team. - items: - description: ArgoCDApplicationSpec defines the desired state - of Application - properties: - name: - description: Name is the name of Application resource. - type: string - path: - description: Path is a directory path within the Git repository, - and is only valid for applications sourced from Git. - type: string - repoURL: - description: RepoURL is the URL to the repository (Git or - Helm) that contains the application manifests. - type: string - targetRevision: - description: TargetRevision defines the revision of the - source to sync the application to. In case of Git, this - can be commit, tag, or branch. If omitted, will equal - to HEAD. In case of Helm, this is a semver tag for the - Chart's version. - type: string - required: - - name - - path - - repoURL - - targetRevision - type: object - type: array extraAdmins: description: ExtraAdmins are the names of the team to add to the AppProject user. Specify this if you want other tenant teams @@ -79,12 +47,6 @@ spec: items: type: string type: array - repositories: - description: Repositories are the list of repositories used by - the tenant team. - items: - type: string - type: array type: object namespaces: items: @@ -114,52 +76,6 @@ spec: - name type: object type: array - teleport: - description: TeleportSpec defines the desired state of the settings - for Teleport - properties: - applications: - description: Applications are the list of applications to be used - by the tenant team. - items: - description: TeleportApplicationSpec defines the desired state - of Teleport Application. - properties: - extraArgs: - description: ExtraArgs are the list of additional arguments - to be specified for Teleport Application Pod. - items: - type: string - type: array - name: - description: Name is the name of the application to proxy. - type: string - url: - description: URL is the internal address of the application - to proxy. - type: string - required: - - name - - url - type: object - type: array - node: - description: Node is the settings of Teleport Node for the tenant - team. - properties: - extraArgs: - description: ExtraArgs are the list of additional arguments - to be specified for Teleport Node Pod. - items: - type: string - type: array - replicas: - description: Replicas is the number of Teleport Node Pods. - type: integer - required: - - replicas - type: object - type: object type: object status: description: TenantStatus defines the observed state of Tenant diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index 523ad41..57464ac 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -9,14 +9,39 @@ data: commonLabels: accurate.cybozu.com/template: init-template groupKey: team + rolebindingTemplate: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ .Name }} + - kind: ServiceAccount + name: node-{{ .Name }} + namespace: teleport + {{ range .ExtraAdmins }} + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ . }} + - kind: ServiceAccount + name: node-{{ . }} + namespace: teleport + {{ end }} argocd: namespace: argocd - permissiveValidation: true - organization: cybozu-go appProjectTemplate: | apiVersion: argoproj.io/v1alpha1 kind: AppProject spec: + destinations: + {{ range .Namespaces }} + - namespace: {{ . }} + server: '*' + {{ end }} namespaceResourceBlacklist: - group: "" kind: ResourceQuota @@ -26,5 +51,14 @@ data: kind: NetworkPolicy orphanedResources: warn: false + roles: + - groups: + - cybozu-private:{{ .Name }} + {{ range .ExtraAdmins }} + - cybozu-private:{{ . }} + {{ end }} + name: admin + policies: + - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow sourceRepos: - '*' diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 59baf77..9802322 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -17,8 +17,10 @@ limitations under the License. package controllers import ( + "bytes" "context" "fmt" + "text/template" multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" @@ -31,6 +33,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "k8s.io/apimachinery/pkg/types" + k8syaml "k8s.io/apimachinery/pkg/util/yaml" "k8s.io/client-go/util/workqueue" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" @@ -215,11 +218,32 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult return err } + tpl, err := template.New("RoleBinding Template").Parse(r.Config.Namespace.RoleBindingTemplate) + if err != nil { + return err + } + + var buf bytes.Buffer + err = tpl.Execute(&buf, struct { + Name string + ExtraAdmins []string + }{ + Name: tenant.Name, + ExtraAdmins: ns.ExtraAdmins, + }) + if err != nil { + return err + } + rb := &rbacv1.RoleBinding{} rb.SetNamespace(ns.Name) rb.SetName(tenant.Name + "-admin") op, err = ctrl.CreateOrUpdate(ctx, r.Client, rb, func() error { + err = k8syaml.Unmarshal(buf.Bytes(), rb) + if err != nil { + return err + } if rb.Labels == nil { rb.Labels = map[string]string{} } @@ -229,34 +253,6 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult rb.Annotations = map[string]string{} } rb.Annotations["accurate.cybozu.com/propagate"] = "update" - rb.RoleRef.Name = "admin" - rb.RoleRef.Kind = "ClusterRole" - rb.RoleRef.APIGroup = "rbac.authorization.k8s.io" - - rb.Subjects = []rbacv1.Subject{} - rb.Subjects = append(rb.Subjects, rbacv1.Subject{ - Kind: "Group", - APIGroup: "rbac.authorization.k8s.io", - Name: tenant.Name, - }) - rb.Subjects = append(rb.Subjects, rbacv1.Subject{ //TODO: - Kind: "ServiceAccount", - Namespace: r.Config.Teleport.Namespace, - Name: "node-" + tenant.Name, - }) - - for _, admin := range ns.ExtraAdmins { - rb.Subjects = append(rb.Subjects, rbacv1.Subject{ - Kind: "Group", - APIGroup: "rbac.authorization.k8s.io", - Name: admin, - }) - rb.Subjects = append(rb.Subjects, rbacv1.Subject{ - Kind: "ServiceAccount", - Namespace: r.Config.Teleport.Namespace, - Name: "node-" + admin, - }) - } return nil }) if err != nil { @@ -284,59 +280,45 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten return err } - dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) - _, _, err = dec.Decode([]byte(r.Config.ArgoCD.AppProjectTemplate), nil, proj) - if err != nil { - return err - } - nss := &corev1.NamespaceList{} if err := r.List(ctx, nss, client.MatchingLabels{r.Config.Namespace.GroupKey: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } - - proj.SetNamespace(r.Config.ArgoCD.Namespace) - proj.SetName(tenant.Name) - proj.SetLabels(map[string]string{ - constants.OwnerTenant: tenant.Name, - }) - - spec := proj.UnstructuredContent()["spec"].(map[string]interface{}) - - logger.Info("nss", "count", len(nss.Items)) - destinations, ok := spec["destinations"].([]map[string]interface{}) - if !ok { - destinations = []map[string]interface{}{} - } - for _, ns := range nss.Items { - destinations = append(destinations, map[string]interface{}{ - "namespace": ns.Name, - "server": "*", - }) + namespaces := make([]string, len(nss.Items)) + for i, ns := range nss.Items { + namespaces[i] = ns.Name } - groups := []string{ - fmt.Sprintf("%s:%s", r.Config.ArgoCD.Organization, tenant.Name), + tpl, err := template.New("AppProject Template").Parse(r.Config.ArgoCD.AppProjectTemplate) + if err != nil { + return err } - if tenant.Spec.ArgoCD != nil { - for _, extra := range tenant.Spec.ArgoCD.ExtraAdmins { - groups = append(groups, fmt.Sprintf("%s:%s", r.Config.ArgoCD.Organization, extra)) - } + + var buf bytes.Buffer + err = tpl.Execute(&buf, struct { + Name string + Namespaces []string + ExtraAdmins []string + }{ + Name: tenant.Name, + Namespaces: namespaces, + ExtraAdmins: tenant.Spec.ArgoCD.ExtraAdmins, + }) + if err != nil { + return err } - roles := []map[string]interface{}{ - { - "groups": groups, - "name": "admin", - "policies": []string{ - fmt.Sprintf("p, proj:%s:admin, applications, *, %s/*, allow", tenant.Name, tenant.Name), - }, - }, + dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) + _, _, err = dec.Decode([]byte(buf.String()), nil, proj) + if err != nil { + return err } - spec["destinations"] = destinations - spec["roles"] = roles - proj.UnstructuredContent()["spec"] = spec + proj.SetNamespace(r.Config.ArgoCD.Namespace) + proj.SetName(tenant.Name) + proj.SetLabels(map[string]string{ + constants.OwnerTenant: tenant.Name, + }) err = r.Patch(ctx, proj, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), diff --git a/pkg/config/types.go b/pkg/config/types.go index cbe2fc2..53da8df 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -9,38 +9,26 @@ import ( type Config struct { Namespace NamespaceConfig `json:"namespace,omitempty"` ArgoCD ArgoCDConfig `json:"argocd,omitempty"` - Teleport TeleportConfig `json:"teleport,omitempty"` } // NamespaceConfig represents the configuration about Namespaces type NamespaceConfig struct { // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller CommonLabels map[string]string `json:"commonLabels,omitempty"` - GroupKey string `json:"groupKey"` + + GroupKey string `json:"groupKey"` + + RoleBindingTemplate string `json:"rolebindingTemplate"` } // ArgoCDConfig represents the configuration about Argo CD type ArgoCDConfig struct { // Namespace is the name of namespace where Argo CD is deployed Namespace string `json:"namespace"` - // PermissiveValidation is the mode of validation for Application resources. - // If true is set, this does not deny Application resources but issues a warning. - PermissiveValidation bool `json:"permissiveValidation"` - Organization string `json:"organization"` AppProjectTemplate string `json:"appProjectTemplate"` } -// TeleportConfig represents the configuration about Teleport -type TeleportConfig struct { - // Namespace is the name of namespace where Teleport Nodes are deployed - Namespace string `json:"namespace"` - // Image is the name of Teleport container image - Image string `json:"image"` - // LicenseSecretName is the name of secret resource contains a license key for Teleport Enterprise - LicenseSecretName string `json:"licenseSecretName"` -} - // Validate validates the configurations. func (c *Config) Validate(mapper meta.RESTMapper) error { return nil From 7ab73dfe86fb0d9b58ee58eb451ef3d212133a46 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 24 Dec 2021 22:12:46 +0900 Subject: [PATCH 18/89] Add config tests Signed-off-by: zoetrope --- cmd/neco-tenant-controller/sub/run.go | 2 +- pkg/config/testdata/config.yaml | 13 ++ pkg/config/testdata/invalid.yaml | 14 ++ pkg/config/teyps_test.go | 198 ++++++++++++++++++++++++++ pkg/config/types.go | 28 +++- 5 files changed, 252 insertions(+), 3 deletions(-) create mode 100644 pkg/config/testdata/config.yaml create mode 100644 pkg/config/testdata/invalid.yaml create mode 100644 pkg/config/teyps_test.go diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index efa6b4e..f596935 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -60,7 +60,7 @@ func subMain(ns, addr string, port int) error { return fmt.Errorf("unable to start manager: %w", err) } - if err := cfg.Validate(mgr.GetRESTMapper()); err != nil { + if err := cfg.Validate(); err != nil { return fmt.Errorf("invalid configurations: %w", err) } ctx := ctrl.SetupSignalHandler() diff --git a/pkg/config/testdata/config.yaml b/pkg/config/testdata/config.yaml new file mode 100644 index 0000000..9c8d7d1 --- /dev/null +++ b/pkg/config/testdata/config.yaml @@ -0,0 +1,13 @@ +namespace: + commonLabels: + foo: bar + a: b + groupKey: abc + rolebindingTemplate: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding +argocd: + namespace: argo + appProjectTemplate: | + apiVersion: argoproj.io/v1alpha1 + kind: AppProject diff --git a/pkg/config/testdata/invalid.yaml b/pkg/config/testdata/invalid.yaml new file mode 100644 index 0000000..03e0860 --- /dev/null +++ b/pkg/config/testdata/invalid.yaml @@ -0,0 +1,14 @@ +namespace: + commonLabels: + foo: bar + a: b + groupKey: abc + rolebindingTemplate: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding +argocd: + namespace: argo + appProjectTemplate: | + apiVersion: argoproj.io/v1alpha1 + kind: AppProject +invalid: data diff --git a/pkg/config/teyps_test.go b/pkg/config/teyps_test.go new file mode 100644 index 0000000..055183f --- /dev/null +++ b/pkg/config/teyps_test.go @@ -0,0 +1,198 @@ +package config + +import ( + _ "embed" + "github.com/google/go-cmp/cmp" + "testing" +) + +//go:embed testdata/config.yaml +var validData []byte + +//go:embed testdata/invalid.yaml +var invalidData []byte + +func TestLoad(t *testing.T) { + c := &Config{} + err := c.Load(validData) + if err != nil { + t.Fatal(err) + } + + if !cmp.Equal(c.Namespace.CommonLabels, map[string]string{"foo": "bar", "a": "b"}) { + t.Error("wrong common labels:", cmp.Diff(c.Namespace.CommonLabels, map[string]string{"foo": "bar", "a": "b"})) + } + if c.Namespace.GroupKey != "abc" { + t.Error("wrong group key:", cmp.Diff(c.Namespace.GroupKey, "abc")) + } + if c.Namespace.RoleBindingTemplate != `apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +` { + t.Error("wrong rolebinding template:", cmp.Diff(c.Namespace.RoleBindingTemplate, `apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +`)) + } + + if c.ArgoCD.Namespace != "argo" { + t.Error("wrong argocd namespace:", cmp.Diff(c.ArgoCD.Namespace, "argo")) + } + if c.ArgoCD.AppProjectTemplate != `apiVersion: argoproj.io/v1alpha1 +kind: AppProject +` { + t.Error("wrong appproject template:", cmp.Diff(c.ArgoCD.AppProjectTemplate, `apiVersion: argoproj.io/v1alpha1 +kind: AppProject +`)) + } + + c = &Config{} + err = c.Load(invalidData) + if err == nil { + t.Fatal("invalid data are loaded successfully") + } + t.Log(err) +} + +func TestValidate(t *testing.T) { + testcases := []struct { + name string + config *Config + isValid bool + }{ + { + name: "valid config", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo": "bar", + "a": "b", + }, + GroupKey: "abc", + RoleBindingTemplate: "kind: RoleBinding", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "argo", + AppProjectTemplate: "kind: AppProject", + }, + }, + isValid: true, + }, + { + name: "invalid common labels", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo!": "bar", + "a": "b/c", + }, + GroupKey: "abc", + RoleBindingTemplate: "kind: RoleBinding", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "argo", + AppProjectTemplate: "kind: AppProject", + }, + }, + isValid: false, + }, + { + name: "invalid group key", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo": "bar", + "a": "b", + }, + GroupKey: "abc@", + RoleBindingTemplate: "kind: RoleBinding", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "argo", + AppProjectTemplate: "kind: AppProject", + }, + }, + isValid: false, + }, + { + name: "empty group key", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo": "bar", + "a": "b", + }, + GroupKey: "", + RoleBindingTemplate: "kind: RoleBinding", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "argo", + AppProjectTemplate: "kind: AppProject", + }, + }, + isValid: false, + }, + { + name: "empty rolebinding template", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo": "bar", + "a": "b", + }, + GroupKey: "abc", + RoleBindingTemplate: "", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "argo", + AppProjectTemplate: "kind: AppProject", + }, + }, + isValid: false, + }, + { + name: "invalid namespace", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo": "bar", + "a": "b", + }, + GroupKey: "abc", + RoleBindingTemplate: "kind: RoleBinding", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "invalid/argo", + AppProjectTemplate: "kind: AppProject", + }, + }, + isValid: false, + }, + { + name: "empty appproject template", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo": "bar", + "a": "b", + }, + GroupKey: "abc", + RoleBindingTemplate: "kind: RoleBinding", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "argo", + AppProjectTemplate: "", + }, + }, + isValid: false, + }, + } + + for _, testcase := range testcases { + err := testcase.config.Validate() + if testcase.isValid && err != nil { + t.Fatalf("%s: %s", testcase.name, err) + } + if !testcase.isValid && err == nil { + t.Fatalf("%s: invalid data are validated successfully", testcase.name) + } + } +} diff --git a/pkg/config/types.go b/pkg/config/types.go index 53da8df..1c03bf5 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -1,7 +1,10 @@ package config import ( - "k8s.io/apimachinery/pkg/api/meta" + "errors" + v1labelvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" "sigs.k8s.io/yaml" ) @@ -30,7 +33,28 @@ type ArgoCDConfig struct { } // Validate validates the configurations. -func (c *Config) Validate(mapper meta.RESTMapper) error { +func (c *Config) Validate() error { + + var allErrs field.ErrorList + allErrs = append(allErrs, v1labelvalidation.ValidateLabels(c.Namespace.CommonLabels, field.NewPath("namespace", "commonLabels"))...) + + allErrs = append(allErrs, v1labelvalidation.ValidateLabelName(c.Namespace.GroupKey, field.NewPath("namespace", "groupKey"))...) + + if len(c.Namespace.RoleBindingTemplate) == 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("namespace", "rolebindingTemplate"), c.Namespace.RoleBindingTemplate, "should not be empty")) + } + + for _, msg := range validation.IsDNS1123Subdomain(c.ArgoCD.Namespace) { + allErrs = append(allErrs, field.Invalid(field.NewPath("argocd", "namespace"), c.ArgoCD.Namespace, msg)) + } + if len(c.ArgoCD.AppProjectTemplate) == 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("argocd", "appProjectTemplate"), c.ArgoCD.AppProjectTemplate, "should not be empty")) + } + + if len(allErrs) != 0 { + return errors.New(allErrs.ToAggregate().Error()) + } + return nil } From 53393e8d6e4e421bfe6a54ee5311b3441aec8e5f Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 24 Dec 2021 23:09:13 +0900 Subject: [PATCH 19/89] Add tests for application webhook Signed-off-by: zoetrope --- .gitignore | 1 + Makefile | 11 +++- hooks/application.go | 2 +- hooks/application_test.go | 116 ++++++++++++++++++++++++++++++++++++++ hooks/suite_test.go | 32 ++++++++++- hooks/tenant_test.go | 1 + 6 files changed, 158 insertions(+), 5 deletions(-) create mode 100644 hooks/application_test.go create mode 100644 hooks/tenant_test.go diff --git a/.gitignore b/.gitignore index 7ecd55d..b5a3d70 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Test binary, build with `go test -c` *.test +/test # Output of the go coverage tool, specifically when used with LiteIDE *.out diff --git a/Makefile b/Makefile index e105938..8c7a8af 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,7 @@ KUSTOMIZE_VERSION = 4.4.1 CRD_TO_MARKDOWN_VERSION = 0.0.3 TILT_VERSION = 0.23.4 CTLPTL_VERSION = 0.6.2 +ARGOCD_VERSION = 2.1.8 # Test tools BIN_DIR := $(shell pwd)/bin @@ -62,10 +63,18 @@ check-generate: $(MAKE) manifests generate apidoc git diff --exit-code --name-only +.PHONY: crds +crds: + mkdir -p test/crd/ + curl -fsL -o test/crd/application.yaml https://raw.githubusercontent.com/argoproj/argo-cd/v$(ARGOCD_VERSION)/manifests/crds/application-crd.yaml + curl -fsL -o test/crd/appproject.yaml https://raw.githubusercontent.com/argoproj/argo-cd/v$(ARGOCD_VERSION)/manifests/crds/appproject-crd.yaml + .PHONY: envtest -envtest: setup-envtest +envtest: setup-envtest crds source <($(SETUP_ENVTEST) use -p env); \ go test -v -count 1 -race ./controllers -ginkgo.progress -ginkgo.v -ginkgo.failFast + source <($(SETUP_ENVTEST) use -p env); \ + go test -v -count 1 -race ./hooks -ginkgo.progress -ginkgo.v -ginkgo.failFast .PHONY: test test: test-tools diff --git a/hooks/application.go b/hooks/application.go index 8250f1c..ee137e7 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -116,7 +116,7 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request } if group != project { - return admission.Denied("cannot specify projects for other tenants") + return admission.Denied("cannot specify a project for other tenants") } return admission.Allowed("ok") diff --git a/hooks/application_test.go b/hooks/application_test.go new file mode 100644 index 0000000..3c00312 --- /dev/null +++ b/hooks/application_test.go @@ -0,0 +1,116 @@ +package hooks + +import ( + "context" + + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +func fillApplication(name, namespace, project string) (*unstructured.Unstructured, error) { + app := argocd.Application() + app.SetName(name) + app.SetNamespace(namespace) + err := unstructured.SetNestedField(app.UnstructuredContent(), project, "spec", "project") + if err != nil { + return nil, err + } + err = unstructured.SetNestedField(app.UnstructuredContent(), "https://github.com/neco-test/apps-sandbox.git", "spec", "source", "repoURL") + if err != nil { + return nil, err + } + err = unstructured.SetNestedMap(app.UnstructuredContent(), map[string]interface{}{}, "spec", "destination") + if err != nil { + return nil, err + } + return app, nil +} + +var _ = Describe("Application webhook", func() { + ctx := context.Background() + + It("should allow creating an application on argocd namespace", func() { + app, err := fillApplication("app-on-argocd", "argocd", "default") + Expect(err).NotTo(HaveOccurred()) + + err = k8sClient.Create(ctx, app) + Expect(err).NotTo(HaveOccurred()) + + Expect(controllerutil.ContainsFinalizer(app, constants.Finalizer)).To(BeFalse()) + }) + + It("should allow creating a normal application", func() { + app, err := fillApplication("normal-app", "sub-1", "a-team") + Expect(err).NotTo(HaveOccurred()) + + err = k8sClient.Create(ctx, app) + Expect(err).NotTo(HaveOccurred()) + + Expect(controllerutil.ContainsFinalizer(app, constants.Finalizer)).To(BeTrue()) + }) + + It("should deny creating an application on unmanaged namespace", func() { + app, err := fillApplication("unmanaged-app", "default", "a-team") + Expect(err).NotTo(HaveOccurred()) + + err = k8sClient.Create(ctx, app) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("an application cannot be created on unmanaged namespaces")) + }) + + It("should deny creating an application managed by other application", func() { + app, err := fillApplication("other-app", "argocd", "a-team") + Expect(err).NotTo(HaveOccurred()) + app.SetLabels(map[string]string{ + constants.OwnerAppNamespace: "sub-other", + }) + err = k8sClient.Create(ctx, app) + Expect(err).NotTo(HaveOccurred()) + + app, err = fillApplication("other-app", "sub-1", "a-team") + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Create(ctx, app) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("cannot create an application with the same name")) + }) + + It("should allow creating an application managed by nobody", func() { + app, err := fillApplication("nobody-app", "argocd", "a-team") + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Create(ctx, app) + Expect(err).NotTo(HaveOccurred()) + + app, err = fillApplication("nobody-app", "sub-1", "a-team") + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Create(ctx, app) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should allow creating an application managed by myself", func() { + app, err := fillApplication("my-app", "argocd", "a-team") + Expect(err).NotTo(HaveOccurred()) + app.SetLabels(map[string]string{ + constants.OwnerAppNamespace: "sub-1", + }) + err = k8sClient.Create(ctx, app) + Expect(err).NotTo(HaveOccurred()) + + app, err = fillApplication("my-app", "sub-1", "a-team") + Expect(err).NotTo(HaveOccurred()) + err = k8sClient.Create(ctx, app) + Expect(err).NotTo(HaveOccurred()) + }) + + It("should deny creating an application with other tenant project", func() { + app, err := fillApplication("unmanaged-app", "sub-1", "b-team") + Expect(err).NotTo(HaveOccurred()) + + err = k8sClient.Create(ctx, app) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("cannot specify a project for other tenants")) + }) +}) diff --git a/hooks/suite_test.go b/hooks/suite_test.go index fd86def..8953b77 100644 --- a/hooks/suite_test.go +++ b/hooks/suite_test.go @@ -30,7 +30,9 @@ import ( //+kubebuilder:scaffold:imports tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/pkg/config" admissionv1beta1 "k8s.io/api/admission/v1beta1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -62,7 +64,10 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{ + filepath.Join("..", "config", "crd", "bases"), + filepath.Join("..", "test", "crd"), + }, ErrorIfCRDPathMissing: false, WebhookInstallOptions: envtest.WebhookInstallOptions{ Paths: []string{filepath.Join("..", "config", "webhook")}, @@ -103,8 +108,16 @@ var _ = BeforeSuite(func() { dec, err := admission.NewDecoder(scheme) Expect(err).NotTo(HaveOccurred()) - err = SetupTenantWebhook(mgr, dec) - Expect(err).NotTo(HaveOccurred()) + config := &config.Config{ + Namespace: config.NamespaceConfig{ + GroupKey: "team", + }, + ArgoCD: config.ArgoCDConfig{ + Namespace: "argocd", + }, + } + SetupTenantWebhook(mgr, dec) + SetupApplicationWebhook(mgr, dec, config) //+kubebuilder:scaffold:webhook @@ -114,6 +127,19 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) }() + ns := &corev1.Namespace{} + ns.Name = "argocd" + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "sub-1" + ns.Labels = map[string]string{ + "team": "a-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + // wait for the webhook server to get ready dialer := &net.Dialer{Timeout: time.Second} addrPort := fmt.Sprintf("%s:%d", webhookInstallOptions.LocalServingHost, webhookInstallOptions.LocalServingPort) diff --git a/hooks/tenant_test.go b/hooks/tenant_test.go new file mode 100644 index 0000000..6de678e --- /dev/null +++ b/hooks/tenant_test.go @@ -0,0 +1 @@ +package hooks From afd310ebc6600af2cf7128b41c249b42b0761904 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sat, 25 Dec 2021 01:29:55 +0900 Subject: [PATCH 20/89] Setup controller tests Signed-off-by: zoetrope --- cmd/neco-tenant-controller/sub/run.go | 44 ++++------------- controllers/application_controller.go | 56 +++++++++++----------- controllers/application_controller_test.go | 51 ++++++++++++++++++++ controllers/suite_test.go | 35 +++++++++----- controllers/tenant_controller.go | 53 ++++++++++---------- controllers/tenant_controller_test.go | 51 ++++++++++++++++++++ hooks/suite_test.go | 5 +- pkg/client/caching_client.go | 27 +++++++++++ 8 files changed, 223 insertions(+), 99 deletions(-) create mode 100644 controllers/application_controller_test.go create mode 100644 controllers/tenant_controller_test.go create mode 100644 pkg/client/caching_client.go diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index f596935..e2170a1 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -11,13 +11,11 @@ import ( multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "github.com/cybozu-go/neco-tenant-controller/controllers" "github.com/cybozu-go/neco-tenant-controller/hooks" + cacheclient "github.com/cybozu-go/neco-tenant-controller/pkg/client" "github.com/cybozu-go/neco-tenant-controller/pkg/config" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -46,7 +44,7 @@ func subMain(ns, addr string, port int) error { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, - NewClient: NewCachingClient, + NewClient: cacheclient.NewCachingClient, MetricsBindAddress: options.metricsAddr, HealthProbeBindAddress: options.probeAddr, LeaderElection: true, @@ -67,19 +65,17 @@ func subMain(ns, addr string, port int) error { if err := controllers.SetupIndexForNamespace(ctx, mgr, cfg.Namespace.GroupKey); err != nil { return fmt.Errorf("failed to setup indexer for namespaces: %w", err) } - if err := (&controllers.TenantReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Config: cfg, - }).SetupWithManager(mgr); err != nil { + if err := controllers.NewTenantReconciler( + mgr.GetClient(), + cfg, + ).SetupWithManager(mgr); err != nil { return fmt.Errorf("unable to create Namespace controller: %w", err) } - if err := (&controllers.ApplicationReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), - Config: cfg, - }).SetupWithManager(ctx, mgr); err != nil { + if err := controllers.NewApplicationReconciler( + mgr.GetClient(), + cfg, + ).SetupWithManager(ctx, mgr); err != nil { return fmt.Errorf("unable to create Namespace controller: %w", err) } @@ -104,23 +100,3 @@ func subMain(ns, addr string, port int) error { } return nil } - -// NewCachingClient is an alternative implementation of controller-runtime's -// default client for manager.Manager. -// https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cluster#DefaultNewClient -// -// The only difference is that this implementation sets `CacheUnstructured` to `true` to -// cache unstructured objects. -func NewCachingClient(cache cache.Cache, config *rest.Config, options client.Options, uncachedObjects ...client.Object) (client.Client, error) { - c, err := client.New(config, options) - if err != nil { - return nil, err - } - - return client.NewDelegatingClient(client.NewDelegatingClientInput{ - CacheReader: cache, - Client: c, - UncachedObjects: uncachedObjects, - CacheUnstructured: true, - }) -} diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 8500b76..4d73685 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -7,51 +7,53 @@ import ( "fmt" "strings" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - "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/source" - - corev1 "k8s.io/api/core/v1" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" "github.com/cybozu-go/neco-tenant-controller/pkg/config" "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/workqueue" "k8s.io/utils/pointer" 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/event" + "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" + "sigs.k8s.io/controller-runtime/pkg/source" "sigs.k8s.io/structured-merge-diff/v4/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/typed" ) +func NewApplicationReconciler(client client.Client, config *config.Config) *ApplicationReconciler { + return &ApplicationReconciler{ + client: client, + config: config, + } +} + // ApplicationReconciler reconciles an Application object type ApplicationReconciler struct { - client.Client - Scheme *runtime.Scheme - Config *config.Config + client client.Client + config *config.Config } //+kubebuilder:rbac:groups=argoproj.io,resources=applications,verbs=get;list;watch;create;update;patch;delete func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { app := argocd.Application() - if err := r.Get(ctx, req.NamespacedName, app); err != nil { + if err := r.client.Get(ctx, req.NamespacedName, app); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } var argocdApp *unstructured.Unstructured var tenantApp *unstructured.Unstructured - if req.Namespace == r.Config.ArgoCD.Namespace { + if req.Namespace == r.config.ArgoCD.Namespace { if app.GetDeletionTimestamp() != nil { return ctrl.Result{}, nil } @@ -61,14 +63,14 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, nil } tenantApp = argocd.Application() - err := r.Get(ctx, client.ObjectKey{Namespace: ownerNs, Name: argocdApp.GetName()}, tenantApp) + err := r.client.Get(ctx, client.ObjectKey{Namespace: ownerNs, Name: argocdApp.GetName()}, tenantApp) if err != nil { return ctrl.Result{}, err } } else { tenantApp = app argocdApp = argocd.Application() - err := r.Get(ctx, client.ObjectKey{Namespace: r.Config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp) + err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp) if err != nil && !apierrors.IsNotFound(err) { return ctrl.Result{}, err } @@ -102,7 +104,7 @@ func (r *ApplicationReconciler) finalize(ctx context.Context, argocdApp *unstruc if argocdApp == nil { controllerutil.RemoveFinalizer(tenantApp, constants.Finalizer) controllerutil.RemoveFinalizer(tenantApp, argocd.ResourcesFinalizer) - err := r.Update(ctx, tenantApp) + err := r.client.Update(ctx, tenantApp) if err != nil { return ctrl.Result{}, err } @@ -114,7 +116,7 @@ func (r *ApplicationReconciler) finalize(ctx context.Context, argocdApp *unstruc } logger.Info("starting finalization") - err := r.Delete(ctx, argocdApp) + err := r.client.Delete(ctx, argocdApp) if err != nil { return ctrl.Result{}, err } @@ -147,14 +149,14 @@ func (r *ApplicationReconciler) validateProject(ctx context.Context, tenantApp * logger := log.FromContext(ctx) ns := &corev1.Namespace{} - err := r.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) + err := r.client.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) if err != nil { return err } - group := ns.Labels[r.Config.Namespace.GroupKey] + group := ns.Labels[r.config.Namespace.GroupKey] if group == "" { logger.Info("Remove unmanaged application") - return r.Delete(ctx, tenantApp) + return r.client.Delete(ctx, tenantApp) } project, found, err := unstructured.NestedString(tenantApp.UnstructuredContent(), "spec", "project") if err != nil { @@ -202,7 +204,7 @@ func (r *ApplicationReconciler) extractManagedFields(u *unstructured.Unstructure m["apiVersion"] = "argoproj.io/" + argocd.ApplicationVersion m["kind"] = "Application" m["metadata"].(map[string]interface{})["name"] = u.GetName() - m["metadata"].(map[string]interface{})["namespace"] = r.Config.ArgoCD.Namespace + m["metadata"].(map[string]interface{})["namespace"] = r.config.ArgoCD.Namespace return m, nil } @@ -235,7 +237,7 @@ func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdA newApp := argocd.Application() newApp.UnstructuredContent()["spec"] = tenantApp.DeepCopy().UnstructuredContent()["spec"] newApp.SetName(tenantApp.GetName()) - newApp.SetNamespace(r.Config.ArgoCD.Namespace) + newApp.SetNamespace(r.config.ArgoCD.Namespace) if len(labels) != 0 { newApp.SetLabels(labels) } @@ -257,7 +259,7 @@ func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdA } } - return r.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ + return r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), FieldManager: constants.FieldManager, }) @@ -277,7 +279,7 @@ func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argoc newApp.UnstructuredContent()["status"] = argocdApp.DeepCopy().UnstructuredContent()["status"] // MEMO: Use `r.Patch` instead of `r.Status().Patch()`, because the status of application is not a sub-resource. - return r.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ + return r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), FieldManager: constants.FieldManager, }) diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go new file mode 100644 index 0000000..354b1b1 --- /dev/null +++ b/controllers/application_controller_test.go @@ -0,0 +1,51 @@ +package controllers + +import ( + "context" + "time" + + "github.com/cybozu-go/neco-tenant-controller/pkg/client" + tenantconfig "github.com/cybozu-go/neco-tenant-controller/pkg/config" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + ctrl "sigs.k8s.io/controller-runtime" +) + +var _ = Describe("Application controller", func() { + ctx := context.Background() + var stopFunc func() + + BeforeEach(func() { + mgr, err := ctrl.NewManager(k8sCfg, ctrl.Options{ + Scheme: scheme, + LeaderElection: false, + MetricsBindAddress: "0", + NewClient: client.NewCachingClient, + }) + Expect(err).ToNot(HaveOccurred()) + + config := &tenantconfig.Config{} + ar := NewApplicationReconciler(mgr.GetClient(), config) + err = ar.SetupWithManager(ctx, mgr) + Expect(err).ToNot(HaveOccurred()) + + ctx, cancel := context.WithCancel(ctx) + stopFunc = cancel + go func() { + err := mgr.Start(ctx) + if err != nil { + panic(err) + } + }() + time.Sleep(100 * time.Millisecond) + }) + + AfterEach(func() { + stopFunc() + time.Sleep(100 * time.Millisecond) + }) + + It("should sync an application", func() { + + }) +}) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index f747677..f31fb01 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -19,34 +19,40 @@ package controllers import ( "path/filepath" "testing" + "time" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + + "k8s.io/apimachinery/pkg/runtime" + + multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" - "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "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" - - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" //+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 +var k8sCfg *rest.Config var k8sClient client.Client +var scheme *runtime.Scheme var testEnv *envtest.Environment func TestAPIs(t *testing.T) { RegisterFailHandler(Fail) - RunSpecsWithDefaultAndCustomReporters(t, - "Controller Suite", - []Reporter{printer.NewlineReporter{}}) + SetDefaultEventuallyTimeout(20 * time.Second) + SetDefaultEventuallyPollingInterval(100 * time.Millisecond) + SetDefaultConsistentlyDuration(5 * time.Second) + SetDefaultConsistentlyPollingInterval(100 * time.Millisecond) + + RunSpecs(t, "Controller Suite") } var _ = BeforeSuite(func() { @@ -54,20 +60,27 @@ var _ = BeforeSuite(func() { By("bootstrapping test environment") testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, + CRDDirectoryPaths: []string{ + filepath.Join("..", "config", "crd", "bases"), + filepath.Join("..", "test", "crd"), + }, ErrorIfCRDPathMissing: true, } cfg, err := testEnv.Start() Expect(err).NotTo(HaveOccurred()) Expect(cfg).NotTo(BeNil()) + k8sCfg = cfg - err = multitenancyv1beta1.AddToScheme(scheme.Scheme) + scheme = runtime.NewScheme() + err = clientgoscheme.AddToScheme(scheme) + Expect(err).NotTo(HaveOccurred()) + err = multitenancyv1beta1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) + k8sClient, err = client.New(cfg, client.Options{Scheme: scheme}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 9802322..1c929fb 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -30,7 +30,6 @@ import ( rbacv1 "k8s.io/api/rbac/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "k8s.io/apimachinery/pkg/types" k8syaml "k8s.io/apimachinery/pkg/util/yaml" @@ -47,11 +46,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) +func NewTenantReconciler(client client.Client, config *config.Config) *TenantReconciler { + return &TenantReconciler{ + client: client, + config: config, + } +} + // TenantReconciler reconciles a Tenant object type TenantReconciler struct { - client.Client - Scheme *runtime.Scheme - Config *config.Config + client client.Client + config *config.Config } //+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants,verbs=get;list;watch;create;update;patch;delete @@ -75,7 +80,7 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr logger := log.FromContext(ctx) tenant := &multitenancyv1beta1.Tenant{} - if err := r.Get(ctx, req.NamespacedName, tenant); err != nil { + if err := r.client.Get(ctx, req.NamespacedName, tenant); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -113,7 +118,7 @@ func containNamespace(roots []multitenancyv1beta1.NamespaceSpec, ns corev1.Names func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *multitenancyv1beta1.Tenant, orphan bool) error { logger := log.FromContext(ctx) nss := &corev1.NamespaceList{} - if err := r.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } for _, ns := range nss.Items { @@ -123,9 +128,9 @@ func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *mult logger.Info("Remove labels", "ns", ns) newNs := ns.DeepCopy() delete(newNs.Labels, constants.OwnerTenant) - delete(newNs.Labels, r.Config.Namespace.GroupKey) + delete(newNs.Labels, r.config.Namespace.GroupKey) patch := client.MergeFrom(&ns) - err := r.Patch(ctx, newNs, patch) + err := r.client.Patch(ctx, newNs, patch) if err != nil { return err } @@ -135,20 +140,20 @@ func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *mult func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { nss := &corev1.NamespaceList{} - if err := r.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } for _, ns := range nss.Items { rb := &rbacv1.RoleBinding{} - err := r.Client.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: tenant.Name + "-admin"}, rb) + err := r.client.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: tenant.Name + "-admin"}, rb) if apierrors.IsNotFound(err) { continue } if err != nil { return err } - err = r.Client.Delete(ctx, rb) + err = r.client.Delete(ctx, rb) if err != nil { return err } @@ -158,14 +163,14 @@ func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *multitenancyv func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { proj := argocd.AppProject() - err := r.Get(ctx, client.ObjectKey{Namespace: r.Config.ArgoCD.Namespace, Name: tenant.Name}, proj) + err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenant.Name}, proj) if apierrors.IsNotFound(err) { return nil } if err != nil { return err } - return r.Client.Delete(ctx, proj) + return r.client.Delete(ctx, proj) } func (r *TenantReconciler) finalize(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { @@ -187,7 +192,7 @@ func (r *TenantReconciler) finalize(ctx context.Context, tenant *multitenancyv1b } controllerutil.RemoveFinalizer(tenant, constants.Finalizer) - return r.Update(ctx, tenant) + return r.client.Update(ctx, tenant) } func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { @@ -195,11 +200,11 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult for _, ns := range tenant.Spec.Namespaces { obj := &corev1.Namespace{} obj.Name = ns.Name - op, err := ctrl.CreateOrUpdate(ctx, r.Client, obj, func() error { + op, err := ctrl.CreateOrUpdate(ctx, r.client, obj, func() error { if len(obj.Labels) == 0 { obj.Labels = map[string]string{} } - for k, v := range r.Config.Namespace.CommonLabels { + for k, v := range r.config.Namespace.CommonLabels { obj.Labels[k] = v } for k, v := range ns.Labels { @@ -209,7 +214,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult obj.Annotations[k] = v } obj.Labels["accurate.cybozu.com/type"] = "root" - obj.Labels[r.Config.Namespace.GroupKey] = tenant.Name + obj.Labels[r.config.Namespace.GroupKey] = tenant.Name obj.Labels[constants.OwnerTenant] = tenant.Name return nil @@ -218,7 +223,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult return err } - tpl, err := template.New("RoleBinding Template").Parse(r.Config.Namespace.RoleBindingTemplate) + tpl, err := template.New("RoleBinding Template").Parse(r.config.Namespace.RoleBindingTemplate) if err != nil { return err } @@ -239,7 +244,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult rb.SetNamespace(ns.Name) rb.SetName(tenant.Name + "-admin") - op, err = ctrl.CreateOrUpdate(ctx, r.Client, rb, func() error { + op, err = ctrl.CreateOrUpdate(ctx, r.client, rb, func() error { err = k8syaml.Unmarshal(buf.Bytes(), rb) if err != nil { return err @@ -275,13 +280,13 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten proj := argocd.AppProject() - err := r.Get(ctx, client.ObjectKey{Namespace: r.Config.ArgoCD.Namespace, Name: tenant.Name}, proj) + err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenant.Name}, proj) if err != nil && !apierrors.IsNotFound(err) { return err } nss := &corev1.NamespaceList{} - if err := r.List(ctx, nss, client.MatchingLabels{r.Config.Namespace.GroupKey: tenant.Name}); err != nil { + if err := r.client.List(ctx, nss, client.MatchingLabels{r.config.Namespace.GroupKey: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } namespaces := make([]string, len(nss.Items)) @@ -289,7 +294,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten namespaces[i] = ns.Name } - tpl, err := template.New("AppProject Template").Parse(r.Config.ArgoCD.AppProjectTemplate) + tpl, err := template.New("AppProject Template").Parse(r.config.ArgoCD.AppProjectTemplate) if err != nil { return err } @@ -314,13 +319,13 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten return err } - proj.SetNamespace(r.Config.ArgoCD.Namespace) + proj.SetNamespace(r.config.ArgoCD.Namespace) proj.SetName(tenant.Name) proj.SetLabels(map[string]string{ constants.OwnerTenant: tenant.Name, }) - err = r.Patch(ctx, proj, client.Apply, &client.PatchOptions{ + err = r.client.Patch(ctx, proj, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), FieldManager: constants.FieldManager, }) diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go new file mode 100644 index 0000000..22d3626 --- /dev/null +++ b/controllers/tenant_controller_test.go @@ -0,0 +1,51 @@ +package controllers + +import ( + "context" + "time" + + "github.com/cybozu-go/neco-tenant-controller/pkg/client" + tenantconfig "github.com/cybozu-go/neco-tenant-controller/pkg/config" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + ctrl "sigs.k8s.io/controller-runtime" +) + +var _ = Describe("Tenant controller", func() { + ctx := context.Background() + var stopFunc func() + + BeforeEach(func() { + mgr, err := ctrl.NewManager(k8sCfg, ctrl.Options{ + Scheme: scheme, + LeaderElection: false, + MetricsBindAddress: "0", + NewClient: client.NewCachingClient, + }) + Expect(err).ToNot(HaveOccurred()) + + config := &tenantconfig.Config{} + tr := NewTenantReconciler(mgr.GetClient(), config) + err = tr.SetupWithManager(mgr) + Expect(err).ToNot(HaveOccurred()) + + ctx, cancel := context.WithCancel(ctx) + stopFunc = cancel + go func() { + err := mgr.Start(ctx) + if err != nil { + panic(err) + } + }() + time.Sleep(100 * time.Millisecond) + }) + + AfterEach(func() { + stopFunc() + time.Sleep(100 * time.Millisecond) + }) + + It("should create a tenant", func() { + + }) +}) diff --git a/hooks/suite_test.go b/hooks/suite_test.go index 8953b77..104d60c 100644 --- a/hooks/suite_test.go +++ b/hooks/suite_test.go @@ -25,12 +25,11 @@ import ( "testing" "time" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - //+kubebuilder:scaffold:imports tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "github.com/cybozu-go/neco-tenant-controller/pkg/config" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" admissionv1beta1 "k8s.io/api/admission/v1beta1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" diff --git a/pkg/client/caching_client.go b/pkg/client/caching_client.go new file mode 100644 index 0000000..8442acb --- /dev/null +++ b/pkg/client/caching_client.go @@ -0,0 +1,27 @@ +package client + +import ( + "k8s.io/client-go/rest" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +// NewCachingClient is an alternative implementation of controller-runtime's +// default client for manager.Manager. +// https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cluster#DefaultNewClient +// +// The only difference is that this implementation sets `CacheUnstructured` to `true` to +// cache unstructured objects. +func NewCachingClient(cache cache.Cache, config *rest.Config, options client.Options, uncachedObjects ...client.Object) (client.Client, error) { + c, err := client.New(config, options) + if err != nil { + return nil, err + } + + return client.NewDelegatingClient(client.NewDelegatingClientInput{ + CacheReader: cache, + Client: c, + UncachedObjects: uncachedObjects, + CacheUnstructured: true, + }) +} From c4f0de1c61d574e7e1d9bfe4e597037cd7f702f3 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sat, 25 Dec 2021 13:15:11 +0900 Subject: [PATCH 21/89] Add tests for application controller Signed-off-by: zoetrope --- controllers/application_controller.go | 83 +++++--- controllers/application_controller_test.go | 228 ++++++++++++++++++++- controllers/suite_test.go | 32 +++ controllers/tenant_controller_test.go | 2 + pkg/constants/meta.go | 3 + 5 files changed, 317 insertions(+), 31 deletions(-) diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 4d73685..58026d3 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -126,11 +126,14 @@ func (r *ApplicationReconciler) finalize(ctx context.Context, argocdApp *unstruc func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { logger := log.FromContext(ctx) - err := r.validateProject(ctx, tenantApp) + removed, err := r.fixProject(ctx, argocdApp, tenantApp) if err != nil { logger.Error(err, "failed to validate application project") return err } + if removed { + return nil + } err = r.syncApplicationSpec(ctx, argocdApp, tenantApp) if err != nil { @@ -145,34 +148,47 @@ func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocd return nil } -func (r *ApplicationReconciler) validateProject(ctx context.Context, tenantApp *unstructured.Unstructured) error { +func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) (removed bool, err error) { logger := log.FromContext(ctx) ns := &corev1.Namespace{} - err := r.client.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) + err = r.client.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) if err != nil { - return err + return } group := ns.Labels[r.config.Namespace.GroupKey] if group == "" { - logger.Info("Remove unmanaged application") - return r.client.Delete(ctx, tenantApp) + if argocdApp != nil { + logger.Info("Remove unmanaged application") + err = r.client.Delete(ctx, argocdApp) + } + removed = true + return } project, found, err := unstructured.NestedString(tenantApp.UnstructuredContent(), "spec", "project") if err != nil { - return err + return } if !found { - return errors.New("spec.project not found") + err = errors.New("spec.project not found") + return } if project != group { logger.Info("Overwrite project", "before", project, "after", group) - err := unstructured.SetNestedField(tenantApp.UnstructuredContent(), group, "spec", "project") + newApp := argocd.Application() + newApp.SetNamespace(tenantApp.GetNamespace()) + newApp.SetName(tenantApp.GetName()) + err = unstructured.SetNestedField(newApp.UnstructuredContent(), group, "spec", "project") if err != nil { - return err + return } + err = r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ + Force: pointer.BoolPtr(true), + FieldManager: constants.ProjectFieldManager, + }) + return } - return nil + return } func (r *ApplicationReconciler) extractManagedFields(u *unstructured.Unstructured, manager string) (map[string]interface{}, error) { @@ -196,16 +212,21 @@ func (r *ApplicationReconciler) extractManagedFields(u *unstructured.Unstructure } x := d.ExtractItems(fieldset.Leaves()).AsValue().Unstructured() - m, ok := x.(map[string]interface{}) + managed, ok := x.(map[string]interface{}) if !ok { - return nil, errors.New("cannot cast") + managed = make(map[string]interface{}) } - m["apiVersion"] = "argoproj.io/" + argocd.ApplicationVersion - m["kind"] = "Application" - m["metadata"].(map[string]interface{})["name"] = u.GetName() - m["metadata"].(map[string]interface{})["namespace"] = r.config.ArgoCD.Namespace - return m, nil + managed["apiVersion"] = "argoproj.io/" + argocd.ApplicationVersion + managed["kind"] = "Application" + metadata, ok := managed["metadata"].(map[string]interface{}) + if !ok { + metadata = make(map[string]interface{}) + } + metadata["name"] = u.GetName() + metadata["namespace"] = r.config.ArgoCD.Namespace + managed["metadata"] = metadata + return managed, nil } func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { @@ -249,7 +270,7 @@ func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdA } if argocdApp != nil { - managed, err := r.extractManagedFields(argocdApp, constants.FieldManager) + managed, err := r.extractManagedFields(argocdApp, constants.SpecFieldManager) if err != nil { logger.Error(err, "failed to extract managed fields") return err @@ -261,27 +282,33 @@ func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdA return r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), - FieldManager: constants.FieldManager, + FieldManager: constants.SpecFieldManager, }) } func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { - if argocdApp == nil || - argocdApp.UnstructuredContent()["status"] == nil || - equality.Semantic.DeepEqual(argocdApp.UnstructuredContent()["status"], tenantApp.UnstructuredContent()["status"]) { - return nil - } + logger := log.FromContext(ctx) newApp := argocd.Application() newApp.SetNamespace(tenantApp.GetNamespace()) newApp.SetName(tenantApp.GetName()) - newApp.UnstructuredContent()["spec"] = tenantApp.DeepCopy().UnstructuredContent()["spec"] - newApp.UnstructuredContent()["status"] = argocdApp.DeepCopy().UnstructuredContent()["status"] + if argocdApp != nil && argocdApp.UnstructuredContent()["status"] != nil { + newApp.UnstructuredContent()["status"] = argocdApp.DeepCopy().UnstructuredContent()["status"] + } + + managed, err := r.extractManagedFields(tenantApp, constants.StatusFieldManager) + if err != nil { + logger.Error(err, "failed to extract managed fields") + return err + } + if equality.Semantic.DeepEqual(managed, newApp.UnstructuredContent()) { + return nil + } // MEMO: Use `r.Patch` instead of `r.Status().Patch()`, because the status of application is not a sub-resource. return r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), - FieldManager: constants.FieldManager, + FieldManager: constants.StatusFieldManager, }) } diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index 354b1b1..513cd42 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -2,29 +2,66 @@ package controllers import ( "context" + "errors" "time" - "github.com/cybozu-go/neco-tenant-controller/pkg/client" + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + cacheclient "github.com/cybozu-go/neco-tenant-controller/pkg/client" tenantconfig "github.com/cybozu-go/neco-tenant-controller/pkg/config" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) +func fillApplication(name, namespace, project string) (*unstructured.Unstructured, error) { + app := argocd.Application() + app.SetName(name) + app.SetNamespace(namespace) + err := unstructured.SetNestedField(app.UnstructuredContent(), project, "spec", "project") + if err != nil { + return nil, err + } + err = unstructured.SetNestedField(app.UnstructuredContent(), "https://github.com/neco-test/apps-sandbox.git", "spec", "source", "repoURL") + if err != nil { + return nil, err + } + err = unstructured.SetNestedMap(app.UnstructuredContent(), map[string]interface{}{}, "spec", "destination") + if err != nil { + return nil, err + } + return app, nil +} + var _ = Describe("Application controller", func() { ctx := context.Background() var stopFunc func() + var config *tenantconfig.Config BeforeEach(func() { mgr, err := ctrl.NewManager(k8sCfg, ctrl.Options{ Scheme: scheme, LeaderElection: false, MetricsBindAddress: "0", - NewClient: client.NewCachingClient, + NewClient: cacheclient.NewCachingClient, }) Expect(err).ToNot(HaveOccurred()) - config := &tenantconfig.Config{} + config = &tenantconfig.Config{ + Namespace: tenantconfig.NamespaceConfig{ + CommonLabels: nil, + GroupKey: "team", + RoleBindingTemplate: "", + }, + ArgoCD: tenantconfig.ArgoCDConfig{ + Namespace: "argocd", + AppProjectTemplate: "", + }, + } ar := NewApplicationReconciler(mgr.GetClient(), config) err = ar.SetupWithManager(ctx, mgr) Expect(err).ToNot(HaveOccurred()) @@ -46,6 +83,191 @@ var _ = Describe("Application controller", func() { }) It("should sync an application", func() { + tenantApp, err := fillApplication("app", "sub-1", "a-team") + tenantApp.SetLabels(map[string]string{ + "kubernetes.io/name": "app", + "foo": "bar", + }) + tenantApp.SetAnnotations(map[string]string{ + "kubernetes.io/name": "app", + "abc": "def", + }) + tenantApp.SetFinalizers([]string{ + "resources-finalizer.argocd.argoproj.io", + "my.finalizer", + }) + Expect(err).ToNot(HaveOccurred()) + + By("syncing an application spec") + err = k8sClient.Create(ctx, tenantApp) + Expect(err).ToNot(HaveOccurred()) + + argocdApp := argocd.Application() + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp); err != nil { + return err + } + return nil + }).Should(Succeed()) + + Expect(argocdApp.GetLabels()[constants.OwnerAppNamespace]).Should(Equal("sub-1")) + Expect(argocdApp.GetLabels()["foo"]).Should(Equal("bar")) + Expect(argocdApp.GetLabels()).ShouldNot(HaveKey("kubernetes.io/name")) + Expect(argocdApp.GetAnnotations()["abc"]).Should(Equal("def")) + Expect(argocdApp.GetAnnotations()).ShouldNot(HaveKey("kubernetes.io/name")) + Expect(argocdApp.GetFinalizers()).Should(ContainElement("resources-finalizer.argocd.argoproj.io")) + Expect(argocdApp.GetFinalizers()).ShouldNot(ContainElement("my.finalizer")) + Expect(argocdApp.UnstructuredContent()["spec"]).Should(Equal(tenantApp.UnstructuredContent()["spec"])) + + By("syncing an application status") + err = unstructured.SetNestedField(argocdApp.UnstructuredContent(), "Healthy", "status", "health", "status") + Expect(err).ToNot(HaveOccurred()) + err = unstructured.SetNestedField(argocdApp.UnstructuredContent(), "successfully synced", "status", "operationState", "message") + Expect(err).ToNot(HaveOccurred()) + err = unstructured.SetNestedField(argocdApp.UnstructuredContent(), "Succeeded", "status", "operationState", "phase") + Expect(err).ToNot(HaveOccurred()) + err = unstructured.SetNestedField(argocdApp.UnstructuredContent(), "abcdefg", "status", "operationState", "operation", "sync", "revision") + Expect(err).ToNot(HaveOccurred()) + err = unstructured.SetNestedField(argocdApp.UnstructuredContent(), time.Now().UTC().Format(time.RFC3339), "status", "operationState", "startedAt") + Expect(err).ToNot(HaveOccurred()) + err = k8sClient.Update(ctx, argocdApp) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: tenantApp.GetNamespace(), Name: tenantApp.GetName()}, tenantApp); err != nil { + return err + } + if tenantApp.UnstructuredContent()["status"] == nil { + return errors.New("status is nil") + } + return nil + }).Should(Succeed()) + Expect(tenantApp.UnstructuredContent()["status"]).Should(Equal(argocdApp.UnstructuredContent()["status"])) + + }) + + It("should remove an application on unmanaged namespace", func() { + tenantApp, err := fillApplication("unmanaged-app", "sub-2", "a-team") + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(ctx, tenantApp) + Expect(err).ToNot(HaveOccurred()) + + argocdApp := argocd.Application() + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp); err != nil { + return err + } + return nil + }).Should(Succeed()) + + ns := &corev1.Namespace{} + ns.Name = "sub-2" + ns.Labels = map[string]string{} + err = k8sClient.Update(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("application still exists") + }).Should(Succeed()) + }) + + It("should fix project", func() { + tenantApp, err := fillApplication("changed-app", "sub-3", "a-team") + Expect(err).ToNot(HaveOccurred()) + + err = k8sClient.Create(ctx, tenantApp) + Expect(err).ToNot(HaveOccurred()) + argocdApp := argocd.Application() + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp); err != nil { + return err + } + return nil + }).Should(Succeed()) + + ns := &corev1.Namespace{} + ns.Name = "sub-3" + ns.Labels = map[string]string{ + "team": "b-team", + } + err = k8sClient.Update(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: tenantApp.GetNamespace(), Name: tenantApp.GetName()}, tenantApp); err != nil { + return err + } + project, found, err := unstructured.NestedString(tenantApp.UnstructuredContent(), "spec", "project") + if err != nil { + return err + } + if !found { + return errors.New("spec.project not found") + } + if project != "b-team" { + return errors.New("spec.project has not been fixed: " + project) + } + return nil + }).Should(Succeed()) + + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp); err != nil { + return err + } + project, found, err := unstructured.NestedString(argocdApp.UnstructuredContent(), "spec", "project") + if err != nil { + return err + } + if !found { + return errors.New("spec.project not found") + } + if project != "b-team" { + return errors.New("spec.project has not been fixed: " + project) + } + return nil + }).Should(Succeed()) + }) + + It("should remove application", func() { + tenantApp, err := fillApplication("removed-app", "sub-1", "a-team") + tenantApp.SetFinalizers([]string{constants.Finalizer}) + Expect(err).ToNot(HaveOccurred()) + + By("syncing an application spec") + err = k8sClient.Create(ctx, tenantApp) + Expect(err).ToNot(HaveOccurred()) + + argocdApp := argocd.Application() + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp); err != nil { + return err + } + return nil + }).Should(Succeed()) + + By("deleting an application") + err = k8sClient.Delete(ctx, tenantApp) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("application still exists") + }).Should(Succeed()) }) + }) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index f31fb01..f226187 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -17,10 +17,13 @@ limitations under the License. package controllers import ( + "context" "path/filepath" "testing" "time" + corev1 "k8s.io/api/core/v1" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/apimachinery/pkg/runtime" @@ -84,6 +87,35 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) + ctx := context.Background() + ns := &corev1.Namespace{} + ns.Name = "argocd" + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "sub-1" + ns.Labels = map[string]string{ + "team": "a-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "sub-2" + ns.Labels = map[string]string{ + "team": "a-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "sub-3" + ns.Labels = map[string]string{ + "team": "a-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) }, 60) var _ = AfterSuite(func() { diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 22d3626..477ce66 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -28,6 +28,8 @@ var _ = Describe("Tenant controller", func() { tr := NewTenantReconciler(mgr.GetClient(), config) err = tr.SetupWithManager(mgr) Expect(err).ToNot(HaveOccurred()) + err = SetupIndexForNamespace(ctx, mgr, config.Namespace.GroupKey) + Expect(err).ToNot(HaveOccurred()) ctx, cancel := context.WithCancel(ctx) stopFunc = cancel diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go index 75a59bf..53b4288 100644 --- a/pkg/constants/meta.go +++ b/pkg/constants/meta.go @@ -11,3 +11,6 @@ const OwnerTenant = MetaPrefix + "owner-tenant" const OwnerAppNamespace = MetaPrefix + "owner-app-namespace" const FieldManager = MetaPrefix + "neco-tenant-controller" +const StatusFieldManager = FieldManager + "/status" +const SpecFieldManager = FieldManager + "/spec" +const ProjectFieldManager = FieldManager + "/project" From ea57ad602ee39e4cac7ee4da782b34112633e16a Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sat, 25 Dec 2021 17:48:38 +0900 Subject: [PATCH 22/89] SSA for namespace and rolebinding Signed-off-by: zoetrope --- controllers/tenant_controller.go | 140 +++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 46 deletions(-) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 1c929fb..ca0d4ef 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -28,11 +28,15 @@ import ( "github.com/cybozu-go/neco-tenant-controller/pkg/constants" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" + "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" "k8s.io/apimachinery/pkg/types" k8syaml "k8s.io/apimachinery/pkg/util/yaml" + accorev1 "k8s.io/client-go/applyconfigurations/core/v1" + acrbacv1 "k8s.io/client-go/applyconfigurations/rbac/v1" "k8s.io/client-go/util/workqueue" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" @@ -195,30 +199,86 @@ func (r *TenantReconciler) finalize(ctx context.Context, tenant *multitenancyv1b return r.client.Update(ctx, tenant) } +func (r *TenantReconciler) patchNamespace(ctx context.Context, ns *accorev1.NamespaceApplyConfiguration) error { + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ns) + if err != nil { + return err + } + patch := &unstructured.Unstructured{ + Object: obj, + } + + var orig corev1.Namespace + err = r.client.Get(ctx, client.ObjectKey{Name: *ns.Name}, &orig) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + managed, err := accorev1.ExtractNamespace(&orig, constants.FieldManager) + if err != nil { + return err + } + + if equality.Semantic.DeepEqual(ns, managed) { + return nil + } + + return r.client.Patch(ctx, patch, client.Apply, &client.PatchOptions{ + FieldManager: constants.FieldManager, + Force: pointer.Bool(true), + }) +} + +func (r *TenantReconciler) patchRoleBinding(ctx context.Context, rb *acrbacv1.RoleBindingApplyConfiguration) error { + obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(rb) + if err != nil { + return err + } + patch := &unstructured.Unstructured{ + Object: obj, + } + + var orig rbacv1.RoleBinding + err = r.client.Get(ctx, client.ObjectKey{Namespace: *rb.Namespace, Name: *rb.Name}, &orig) + if err != nil && !apierrors.IsNotFound(err) { + return err + } + + managed, err := acrbacv1.ExtractRoleBinding(&orig, constants.FieldManager) + if err != nil { + return err + } + + if equality.Semantic.DeepEqual(rb, managed) { + return nil + } + + return r.client.Patch(ctx, patch, client.Apply, &client.PatchOptions{ + FieldManager: constants.FieldManager, + Force: pointer.Bool(true), + }) +} + func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { - logger := log.FromContext(ctx) for _, ns := range tenant.Spec.Namespaces { - obj := &corev1.Namespace{} - obj.Name = ns.Name - op, err := ctrl.CreateOrUpdate(ctx, r.client, obj, func() error { - if len(obj.Labels) == 0 { - obj.Labels = map[string]string{} - } - for k, v := range r.config.Namespace.CommonLabels { - obj.Labels[k] = v - } - for k, v := range ns.Labels { - obj.Labels[k] = v - } - for k, v := range ns.Annotations { - obj.Annotations[k] = v - } - obj.Labels["accurate.cybozu.com/type"] = "root" - obj.Labels[r.config.Namespace.GroupKey] = tenant.Name - obj.Labels[constants.OwnerTenant] = tenant.Name - - return nil - }) + namespace := accorev1.Namespace(ns.Name) + labels := make(map[string]string) + for k, v := range r.config.Namespace.CommonLabels { + labels[k] = v + } + for k, v := range ns.Labels { + labels[k] = v + } + labels["accurate.cybozu.com/type"] = "root" + labels[r.config.Namespace.GroupKey] = tenant.Name + labels[constants.OwnerTenant] = tenant.Name + namespace.WithLabels(labels) + annotations := make(map[string]string) + for k, v := range ns.Annotations { + annotations[k] = v + } + namespace.WithAnnotations(annotations) + err := r.patchNamespace(ctx, namespace) if err != nil { return err } @@ -227,7 +287,6 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult if err != nil { return err } - var buf bytes.Buffer err = tpl.Execute(&buf, struct { Name string @@ -240,31 +299,22 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult return err } - rb := &rbacv1.RoleBinding{} - rb.SetNamespace(ns.Name) - rb.SetName(tenant.Name + "-admin") - - op, err = ctrl.CreateOrUpdate(ctx, r.client, rb, func() error { - err = k8syaml.Unmarshal(buf.Bytes(), rb) - if err != nil { - return err - } - if rb.Labels == nil { - rb.Labels = map[string]string{} - } - rb.Labels[constants.OwnerTenant] = tenant.Name - - if rb.Annotations == nil { - rb.Annotations = map[string]string{} - } - rb.Annotations["accurate.cybozu.com/propagate"] = "update" - return nil + rb := acrbacv1.RoleBinding(tenant.Name+"-admin", ns.Name) + err = k8syaml.Unmarshal(buf.Bytes(), rb) + if err != nil { + return err + } + rb.WithLabels(map[string]string{ + constants.OwnerTenant: tenant.Name, }) + rb.WithAnnotations(map[string]string{ + "accurate.cybozu.com/propagate": "update", + }) + + err = r.patchRoleBinding(ctx, rb) if err != nil { - logger.Error(err, "failed to upsert RoleBinding") return err } - logger.Info("updated rolebinding", "op", op) } // Remove orphan labels err := r.removeManagedLabels(ctx, tenant, true) @@ -279,7 +329,6 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten logger := log.FromContext(ctx) proj := argocd.AppProject() - err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenant.Name}, proj) if err != nil && !apierrors.IsNotFound(err) { return err @@ -329,7 +378,6 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten Force: pointer.BoolPtr(true), FieldManager: constants.FieldManager, }) - if err != nil { return err } From a6c35d5f54149fe60366d9039d82f166a94cffce Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sat, 25 Dec 2021 18:33:44 +0900 Subject: [PATCH 23/89] Add tests for tenant controller Signed-off-by: zoetrope --- config/manager/configmap.yaml | 6 +- controllers/application_controller.go | 49 +- controllers/application_controller_test.go | 23 +- controllers/suite_test.go | 8 + controllers/tenant_controller.go | 164 +++--- controllers/tenant_controller_test.go | 496 +++++++++++++++++- controllers/testdata/appprojecttemplate.yaml | 26 + controllers/testdata/rolebindingtemplate.yaml | 15 + docs/config.md | 2 - go.mod | 1 + hooks/tenant.go | 6 +- pkg/client/extract.go | 48 ++ 12 files changed, 718 insertions(+), 126 deletions(-) create mode 100644 controllers/testdata/appprojecttemplate.yaml create mode 100644 controllers/testdata/rolebindingtemplate.yaml create mode 100644 pkg/client/extract.go diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index 57464ac..42d3a53 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -47,15 +47,13 @@ data: kind: ResourceQuota - group: "" kind: LimitRange - - group: networking.k8s.io - kind: NetworkPolicy orphanedResources: warn: false roles: - groups: - - cybozu-private:{{ .Name }} + - cybozu-go:{{ .Name }} {{ range .ExtraAdmins }} - - cybozu-private:{{ . }} + - cybozu-go:{{ . }} {{ end }} name: admin policies: diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 58026d3..e2a346c 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -1,19 +1,18 @@ package controllers import ( - "bytes" "context" "errors" "fmt" "strings" "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + extract "github.com/cybozu-go/neco-tenant-controller/pkg/client" "github.com/cybozu-go/neco-tenant-controller/pkg/config" "github.com/cybozu-go/neco-tenant-controller/pkg/constants" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/workqueue" @@ -26,8 +25,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/source" - "sigs.k8s.io/structured-merge-diff/v4/fieldpath" - "sigs.k8s.io/structured-merge-diff/v4/typed" ) func NewApplicationReconciler(client client.Client, config *config.Config) *ApplicationReconciler { @@ -158,7 +155,7 @@ func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstr } group := ns.Labels[r.config.Namespace.GroupKey] if group == "" { - if argocdApp != nil { + if argocdApp != nil && argocdApp.GetDeletionTimestamp() == nil { logger.Info("Remove unmanaged application") err = r.client.Delete(ctx, argocdApp) } @@ -191,44 +188,6 @@ func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstr return } -func (r *ApplicationReconciler) extractManagedFields(u *unstructured.Unstructured, manager string) (map[string]interface{}, error) { - fieldset := &fieldpath.Set{} - objManagedFields := u.GetManagedFields() - for _, mf := range objManagedFields { - if mf.Manager != manager || mf.Operation != metav1.ManagedFieldsOperationApply { - continue - } - fs := &fieldpath.Set{} - err := fs.FromJSON(bytes.NewReader(mf.FieldsV1.Raw)) - if err != nil { - return nil, err - } - fieldset = fieldset.Union(fs) - } - - d, err := typed.DeducedParseableType.FromUnstructured(u.Object) - if err != nil { - return nil, err - } - - x := d.ExtractItems(fieldset.Leaves()).AsValue().Unstructured() - managed, ok := x.(map[string]interface{}) - if !ok { - managed = make(map[string]interface{}) - } - - managed["apiVersion"] = "argoproj.io/" + argocd.ApplicationVersion - managed["kind"] = "Application" - metadata, ok := managed["metadata"].(map[string]interface{}) - if !ok { - metadata = make(map[string]interface{}) - } - metadata["name"] = u.GetName() - metadata["namespace"] = r.config.ArgoCD.Namespace - managed["metadata"] = metadata - return managed, nil -} - func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { logger := log.FromContext(ctx) @@ -270,7 +229,7 @@ func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdA } if argocdApp != nil { - managed, err := r.extractManagedFields(argocdApp, constants.SpecFieldManager) + managed, err := extract.ExtractManagedFields(argocdApp, constants.SpecFieldManager) if err != nil { logger.Error(err, "failed to extract managed fields") return err @@ -296,7 +255,7 @@ func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argoc newApp.UnstructuredContent()["status"] = argocdApp.DeepCopy().UnstructuredContent()["status"] } - managed, err := r.extractManagedFields(tenantApp, constants.StatusFieldManager) + managed, err := extract.ExtractManagedFields(tenantApp, constants.StatusFieldManager) if err != nil { logger.Error(err, "failed to extract managed fields") return err diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index 513cd42..300a757 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -11,6 +11,7 @@ import ( "github.com/cybozu-go/neco-tenant-controller/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -110,10 +111,14 @@ var _ = Describe("Application controller", func() { return nil }).Should(Succeed()) - Expect(argocdApp.GetLabels()[constants.OwnerAppNamespace]).Should(Equal("sub-1")) - Expect(argocdApp.GetLabels()["foo"]).Should(Equal("bar")) + Expect(argocdApp.GetLabels()).Should(MatchAllKeys(Keys{ + constants.OwnerAppNamespace: Equal("sub-1"), + "foo": Equal("bar"), + })) Expect(argocdApp.GetLabels()).ShouldNot(HaveKey("kubernetes.io/name")) - Expect(argocdApp.GetAnnotations()["abc"]).Should(Equal("def")) + Expect(argocdApp.GetAnnotations()).Should(MatchAllKeys(Keys{ + "abc": Equal("def"), + })) Expect(argocdApp.GetAnnotations()).ShouldNot(HaveKey("kubernetes.io/name")) Expect(argocdApp.GetFinalizers()).Should(ContainElement("resources-finalizer.argocd.argoproj.io")) Expect(argocdApp.GetFinalizers()).ShouldNot(ContainElement("my.finalizer")) @@ -143,7 +148,6 @@ var _ = Describe("Application controller", func() { return nil }).Should(Succeed()) Expect(tenantApp.UnstructuredContent()["status"]).Should(Equal(argocdApp.UnstructuredContent()["status"])) - }) It("should remove an application on unmanaged namespace", func() { @@ -268,6 +272,17 @@ var _ = Describe("Application controller", func() { } return errors.New("application still exists") }).Should(Succeed()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: tenantApp.GetNamespace(), Name: tenantApp.GetName()}, tenantApp) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("application still exists") + }).Should(Succeed()) }) }) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index f226187..cb6176f 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -116,6 +116,14 @@ var _ = BeforeSuite(func() { } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "sub-4" + ns.Labels = map[string]string{ + "team": "x-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) }, 60) var _ = AfterSuite(func() { diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index ca0d4ef..ff06a11 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -22,8 +22,9 @@ import ( "fmt" "text/template" - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + extract "github.com/cybozu-go/neco-tenant-controller/pkg/client" "github.com/cybozu-go/neco-tenant-controller/pkg/config" "github.com/cybozu-go/neco-tenant-controller/pkg/constants" corev1 "k8s.io/api/core/v1" @@ -81,19 +82,15 @@ type TenantReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) - - tenant := &multitenancyv1beta1.Tenant{} + tenant := &tenantv1beta1.Tenant{} if err := r.client.Get(ctx, req.NamespacedName, tenant); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } if tenant.DeletionTimestamp != nil { - logger.Info("starting finalization") if err := r.finalize(ctx, tenant); err != nil { return ctrl.Result{}, fmt.Errorf("failed to finalize: %w", err) } - logger.Info("finished finalization") return ctrl.Result{}, nil } @@ -110,7 +107,7 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, nil } -func containNamespace(roots []multitenancyv1beta1.NamespaceSpec, ns corev1.Namespace) bool { +func containNamespace(roots []tenantv1beta1.NamespaceSpec, ns corev1.Namespace) bool { for _, root := range roots { if root.Name == ns.Name { return true @@ -119,84 +116,90 @@ func containNamespace(roots []multitenancyv1beta1.NamespaceSpec, ns corev1.Names return false } -func (r *TenantReconciler) removeManagedLabels(ctx context.Context, tenant *multitenancyv1beta1.Tenant, orphan bool) error { - logger := log.FromContext(ctx) - nss := &corev1.NamespaceList{} - if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { - return fmt.Errorf("failed to list namespaces: %w", err) +func (r *TenantReconciler) banishNamespace(ctx context.Context, ns *corev1.Namespace) error { + managed, err := accorev1.ExtractNamespace(ns, constants.FieldManager) + if err != nil { + return err } - for _, ns := range nss.Items { - if orphan && containNamespace(tenant.Spec.Namespaces, ns) { - continue - } - logger.Info("Remove labels", "ns", ns) - newNs := ns.DeepCopy() - delete(newNs.Labels, constants.OwnerTenant) - delete(newNs.Labels, r.config.Namespace.GroupKey) - patch := client.MergeFrom(&ns) - err := r.client.Patch(ctx, newNs, patch) - if err != nil { - return err - } + delete(managed.Labels, constants.OwnerTenant) + delete(managed.Labels, r.config.Namespace.GroupKey) + for k := range r.config.Namespace.CommonLabels { + delete(managed.Labels, k) + } + err = r.patchNamespace(ctx, managed) + if err != nil { + return err } return nil } -func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { - nss := &corev1.NamespaceList{} - if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { - return fmt.Errorf("failed to list namespaces: %w", err) +func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *tenantv1beta1.Tenant, ns *corev1.Namespace) error { + rb := &rbacv1.RoleBinding{} + err := r.client.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: tenant.Name + "-admin"}, rb) + if apierrors.IsNotFound(err) { + return nil } - - for _, ns := range nss.Items { - rb := &rbacv1.RoleBinding{} - err := r.client.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: tenant.Name + "-admin"}, rb) - if apierrors.IsNotFound(err) { - continue - } - if err != nil { - return err - } - err = r.client.Delete(ctx, rb) - if err != nil { - return err - } + if rb.DeletionTimestamp != nil { + return nil + } + if err != nil { + return err + } + err = r.client.Delete(ctx, rb) + if err != nil { + return err } return nil } -func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { +func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *tenantv1beta1.Tenant) error { proj := argocd.AppProject() err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenant.Name}, proj) if apierrors.IsNotFound(err) { return nil } + if proj.GetDeletionTimestamp() != nil { + return nil + } if err != nil { return err } return r.client.Delete(ctx, proj) } -func (r *TenantReconciler) finalize(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { +func (r *TenantReconciler) finalize(ctx context.Context, tenant *tenantv1beta1.Tenant) error { + logger := log.FromContext(ctx) if !controllerutil.ContainsFinalizer(tenant, constants.Finalizer) { return nil } - - err := r.removeManagedLabels(ctx, tenant, false) - if err != nil { - return err + logger.Info("starting finalization") + nss := &corev1.NamespaceList{} + if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + return fmt.Errorf("failed to list namespaces: %w", err) } - err = r.removeRBAC(ctx, tenant) - if err != nil { - return err + for _, ns := range nss.Items { + err := r.banishNamespace(ctx, &ns) + if err != nil { + return err + } + err = r.removeRBAC(ctx, tenant, &ns) + if err != nil { + return err + } } - err = r.removeAppProject(ctx, tenant) + err := r.removeAppProject(ctx, tenant) if err != nil { return err } controllerutil.RemoveFinalizer(tenant, constants.Finalizer) - return r.client.Update(ctx, tenant) + err = r.client.Update(ctx, tenant) + if err != nil { + logger.Error(err, "failed to remove finalizer") + return err + } + logger.Info("finished finalization") + return nil } func (r *TenantReconciler) patchNamespace(ctx context.Context, ns *accorev1.NamespaceApplyConfiguration) error { @@ -259,7 +262,7 @@ func (r *TenantReconciler) patchRoleBinding(ctx context.Context, rb *acrbacv1.Ro }) } -func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { +func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *tenantv1beta1.Tenant) error { for _, ns := range tenant.Spec.Namespaces { namespace := accorev1.Namespace(ns.Name) labels := make(map[string]string) @@ -316,24 +319,48 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *mult return err } } - // Remove orphan labels - err := r.removeManagedLabels(ctx, tenant, true) - if err != nil { - return err + nss := &corev1.NamespaceList{} + if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + return fmt.Errorf("failed to list namespaces: %w", err) + } + for _, ns := range nss.Items { + if containNamespace(tenant.Spec.Namespaces, ns) { + continue + } + err := r.banishNamespace(ctx, &ns) + if err != nil { + return err + } + err = r.removeRBAC(ctx, tenant, &ns) + if err != nil { + return err + } } return nil } -func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multitenancyv1beta1.Tenant) error { +func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *tenantv1beta1.Tenant) error { logger := log.FromContext(ctx) - proj := argocd.AppProject() - err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenant.Name}, proj) + orig := argocd.AppProject() + err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenant.Name}, orig) if err != nil && !apierrors.IsNotFound(err) { + logger.Error(err, "failed to get AppProject") return err } + if len(tenant.Spec.Namespaces) == 0 { + if apierrors.IsNotFound(err) { + return nil + } + if orig.GetDeletionTimestamp() != nil { + return nil + } + logger.Info("remove AppProject", "proj", orig, "deletion", orig.GetDeletionTimestamp()) + return r.client.Delete(ctx, orig) + } + nss := &corev1.NamespaceList{} if err := r.client.List(ctx, nss, client.MatchingLabels{r.config.Namespace.GroupKey: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) @@ -362,6 +389,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten return err } + proj := argocd.AppProject() dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) _, _, err = dec.Decode([]byte(buf.String()), nil, proj) if err != nil { @@ -374,14 +402,22 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *multiten constants.OwnerTenant: tenant.Name, }) + managed, err := extract.ExtractManagedFields(orig, constants.FieldManager) + if err != nil { + return err + } + if equality.Semantic.DeepEqual(proj, managed) { + return nil + } + err = r.client.Patch(ctx, proj, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), FieldManager: constants.FieldManager, }) if err != nil { + logger.Error(err, "failed to patch AppProject") return err } - logger.Info("AppProject successfully reconciled") return nil @@ -415,7 +451,7 @@ func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { } return ctrl.NewControllerManagedBy(mgr). - For(&multitenancyv1beta1.Tenant{}). + For(&tenantv1beta1.Tenant{}). Watches(&source.Kind{Type: &corev1.Namespace{}}, funcs). Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, funcs). Watches(&source.Kind{Type: argocd.AppProject()}, funcs). diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 477ce66..ed3125a 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -2,29 +2,60 @@ package controllers import ( "context" + _ "embed" + "errors" "time" - "github.com/cybozu-go/neco-tenant-controller/pkg/client" + tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + cacheclient "github.com/cybozu-go/neco-tenant-controller/pkg/client" tenantconfig "github.com/cybozu-go/neco-tenant-controller/pkg/config" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" ) +//go:embed testdata/appprojecttemplate.yaml +var appprojectTemplate string + +//go:embed testdata/rolebindingtemplate.yaml +var rolebindingTemplate string + var _ = Describe("Tenant controller", func() { ctx := context.Background() var stopFunc func() + var config *tenantconfig.Config BeforeEach(func() { mgr, err := ctrl.NewManager(k8sCfg, ctrl.Options{ Scheme: scheme, LeaderElection: false, MetricsBindAddress: "0", - NewClient: client.NewCachingClient, + NewClient: cacheclient.NewCachingClient, }) Expect(err).ToNot(HaveOccurred()) - config := &tenantconfig.Config{} + config = &tenantconfig.Config{ + Namespace: tenantconfig.NamespaceConfig{ + CommonLabels: map[string]string{ + "accurate.cybozu.com/template": "init-template", + }, + GroupKey: "team", + RoleBindingTemplate: rolebindingTemplate, + }, + ArgoCD: tenantconfig.ArgoCDConfig{ + Namespace: "argocd", + AppProjectTemplate: appprojectTemplate, + }, + } tr := NewTenantReconciler(mgr.GetClient(), config) err = tr.SetupWithManager(mgr) Expect(err).ToNot(HaveOccurred()) @@ -47,7 +78,464 @@ var _ = Describe("Tenant controller", func() { time.Sleep(100 * time.Millisecond) }) - It("should create a tenant", func() { + It("should create root namespaces, rolebindings and an appproject", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "x-team", + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + { + Name: "app-x", + Labels: map[string]string{ + "foo": "bar", + }, + Annotations: map[string]string{ + "abc": "def", + }, + ExtraAdmins: []string{ + "c-team", + }, + }, + }, + ArgoCD: tenantv1beta1.ArgoCDSpec{ + ExtraAdmins: []string{ + "d-team", + }, + }, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).ToNot(HaveOccurred()) + + ns := &corev1.Namespace{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-x"}, ns); err != nil { + return err + } + return nil + }).Should(Succeed()) + + Expect(ns.Labels).Should(MatchAllKeys(Keys{ + "kubernetes.io/metadata.name": Equal("app-x"), + "accurate.cybozu.com/type": Equal("root"), + config.Namespace.GroupKey: Equal("x-team"), + constants.OwnerTenant: Equal("x-team"), + "foo": Equal("bar"), + "accurate.cybozu.com/template": Equal("init-template"), + })) + Expect(ns.Annotations).Should(MatchAllKeys(Keys{ + "abc": Equal("def"), + })) + + rb := &rbacv1.RoleBinding{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-x", Name: "x-team-admin"}, rb); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(rb.RoleRef.Name).Should(Equal("admin")) + Expect(rb.Subjects).Should(ConsistOf([]rbacv1.Subject{ + { + Kind: "Group", + APIGroup: "rbac.authorization.k8s.io", + Name: "x-team", + }, + { + Kind: "Group", + APIGroup: "rbac.authorization.k8s.io", + Name: "c-team", + }, + })) + + proj := argocd.AppProject() + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: "x-team"}, proj); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(proj.UnstructuredContent()["spec"]).Should(MatchAllKeys(Keys{ + "destinations": ConsistOf( + MatchAllKeys(Keys{ + "namespace": Equal("app-x"), + "server": Equal("*"), + }), + MatchAllKeys(Keys{ + "namespace": Equal("sub-4"), + "server": Equal("*"), + }), + ), + "namespaceResourceBlacklist": ConsistOf( + MatchAllKeys(Keys{ + "group": Equal(""), + "kind": Equal("ResourceQuota"), + }), + MatchAllKeys(Keys{ + "group": Equal(""), + "kind": Equal("LimitRange"), + }), + ), + "orphanedResources": MatchAllKeys(Keys{ + "warn": Equal(false), + }), + "roles": ConsistOf( + MatchAllKeys(Keys{ + "groups": ConsistOf("cybozu-go:x-team", "cybozu-go:d-team"), + "name": Equal("admin"), + "policies": ConsistOf("p, proj:x-team:admin, applications, *, x-team/*, allow"), + }), + ), + "sourceRepos": ConsistOf("*"), + })) + }) + + It("should banish root namespace", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "y-team", + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + {Name: "app-y1"}, + {Name: "app-y2"}, + }, + ArgoCD: tenantv1beta1.ArgoCDSpec{}, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).ToNot(HaveOccurred()) + + nsy1 := &corev1.Namespace{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-y1"}, nsy1); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(nsy1.Labels).Should(MatchAllKeys(Keys{ + "kubernetes.io/metadata.name": Equal("app-y1"), + "accurate.cybozu.com/type": Equal("root"), + config.Namespace.GroupKey: Equal("y-team"), + constants.OwnerTenant: Equal("y-team"), + "accurate.cybozu.com/template": Equal("init-template"), + })) + + nsy2 := &corev1.Namespace{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-y2"}, nsy2); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(nsy2.Labels).Should(MatchAllKeys(Keys{ + "kubernetes.io/metadata.name": Equal("app-y2"), + "accurate.cybozu.com/type": Equal("root"), + config.Namespace.GroupKey: Equal("y-team"), + constants.OwnerTenant: Equal("y-team"), + "accurate.cybozu.com/template": Equal("init-template"), + })) + + rby1 := &rbacv1.RoleBinding{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-y1", Name: "y-team-admin"}, rby1); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(rby1.RoleRef.Name).Should(Equal("admin")) + Expect(rby1.Subjects).Should(ConsistOf([]rbacv1.Subject{ + { + Kind: "Group", + APIGroup: "rbac.authorization.k8s.io", + Name: "y-team", + }, + })) + rby2 := &rbacv1.RoleBinding{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-y2", Name: "y-team-admin"}, rby2); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(rby2.RoleRef.Name).Should(Equal("admin")) + Expect(rby2.Subjects).Should(ConsistOf([]rbacv1.Subject{ + { + Kind: "Group", + APIGroup: "rbac.authorization.k8s.io", + Name: "y-team", + }, + })) + + proj := argocd.AppProject() + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: "y-team"}, proj); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(proj.UnstructuredContent()["spec"]).Should(MatchAllKeys(Keys{ + "destinations": ConsistOf( + MatchAllKeys(Keys{ + "namespace": Equal("app-y1"), + "server": Equal("*"), + }), + MatchAllKeys(Keys{ + "namespace": Equal("app-y2"), + "server": Equal("*"), + }), + ), + "namespaceResourceBlacklist": ConsistOf( + MatchAllKeys(Keys{ + "group": Equal(""), + "kind": Equal("ResourceQuota"), + }), + MatchAllKeys(Keys{ + "group": Equal(""), + "kind": Equal("LimitRange"), + }), + ), + "orphanedResources": MatchAllKeys(Keys{ + "warn": Equal(false), + }), + "roles": ConsistOf( + MatchAllKeys(Keys{ + "groups": ConsistOf("cybozu-go:y-team"), + "name": Equal("admin"), + "policies": ConsistOf("p, proj:y-team:admin, applications, *, y-team/*, allow"), + }), + ), + "sourceRepos": ConsistOf("*"), + })) + + By("removing app-y2") + tenant.Spec.Namespaces = []tenantv1beta1.NamespaceSpec{ + {Name: "app-y1"}, + } + err = k8sClient.Update(ctx, tenant) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-y2"}, nsy2) + if err != nil { + return err + } + if nsy2.Labels[constants.OwnerTenant] != "" { + return errors.New("owner label still exists") + } + return nil + }).Should(Succeed()) + Expect(nsy2.Labels).Should(MatchAllKeys(Keys{ + "kubernetes.io/metadata.name": Equal("app-y2"), + "accurate.cybozu.com/type": Equal("root"), + })) + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-y2", Name: "y-team-admin"}, rby2) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("rolebinding still exists") + }).Should(Succeed()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: "y-team"}, proj) + if err != nil { + return err + } + destinations, found, err := unstructured.NestedSlice(proj.UnstructuredContent(), "spec", "destinations") + if err != nil { + return err + } + if !found { + return errors.New("destinations not found") + } + for _, d := range destinations { + if d.(map[string]interface{})["namespace"] == "app-y2" { + return errors.New("destination still exists") + } + } + return nil + }).Should(Succeed()) + + By("removing app-y1") + tenant.Spec.Namespaces = []tenantv1beta1.NamespaceSpec{} + err = k8sClient.Update(ctx, tenant) + Expect(err).ToNot(HaveOccurred()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-y1"}, nsy1) + if err != nil { + return err + } + if nsy1.Labels[constants.OwnerTenant] != "" { + return errors.New("owner label still exists") + } + return nil + }).Should(Succeed()) + Expect(nsy1.Labels).Should(MatchAllKeys(Keys{ + "kubernetes.io/metadata.name": Equal("app-y1"), + "accurate.cybozu.com/type": Equal("root"), + })) + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-y1", Name: "y-team-admin"}, rby1) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("rolebinding still exists") + }).Should(Succeed()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: "y-team"}, proj) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("appproject still exists") + }).Should(Succeed()) + }) + + It("should remove tenant", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "z-team", + Finalizers: []string{constants.Finalizer}, + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + {Name: "app-z"}, + }, + ArgoCD: tenantv1beta1.ArgoCDSpec{}, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).ToNot(HaveOccurred()) + + ns := &corev1.Namespace{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-z"}, ns); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(ns.Labels).Should(MatchAllKeys(Keys{ + "kubernetes.io/metadata.name": Equal("app-z"), + "accurate.cybozu.com/type": Equal("root"), + config.Namespace.GroupKey: Equal("z-team"), + constants.OwnerTenant: Equal("z-team"), + "accurate.cybozu.com/template": Equal("init-template"), + })) + + rb := &rbacv1.RoleBinding{} + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-z", Name: "z-team-admin"}, rb); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(rb.RoleRef.Name).Should(Equal("admin")) + Expect(rb.Subjects).Should(ConsistOf([]rbacv1.Subject{ + { + Kind: "Group", + APIGroup: "rbac.authorization.k8s.io", + Name: "z-team", + }, + })) + + proj := argocd.AppProject() + Eventually(func() error { + if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: "z-team"}, proj); err != nil { + return err + } + return nil + }).Should(Succeed()) + Expect(proj.UnstructuredContent()["spec"]).Should(MatchAllKeys(Keys{ + "destinations": ConsistOf( + MatchAllKeys(Keys{ + "namespace": Equal("app-z"), + "server": Equal("*"), + }), + ), + "namespaceResourceBlacklist": ConsistOf( + MatchAllKeys(Keys{ + "group": Equal(""), + "kind": Equal("ResourceQuota"), + }), + MatchAllKeys(Keys{ + "group": Equal(""), + "kind": Equal("LimitRange"), + }), + ), + "orphanedResources": MatchAllKeys(Keys{ + "warn": Equal(false), + }), + "roles": ConsistOf( + MatchAllKeys(Keys{ + "groups": ConsistOf("cybozu-go:z-team"), + "name": Equal("admin"), + "policies": ConsistOf("p, proj:z-team:admin, applications, *, z-team/*, allow"), + }), + ), + "sourceRepos": ConsistOf("*"), + })) + + By("removing tenant") + err = k8sClient.Delete(ctx, tenant) + Expect(err).ToNot(HaveOccurred()) + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-z"}, ns) + if err != nil { + return err + } + if ns.Labels[constants.OwnerTenant] != "" { + return errors.New("owner label still exists") + } + return nil + }).Should(Succeed()) + Expect(ns.Labels).Should(MatchAllKeys(Keys{ + "kubernetes.io/metadata.name": Equal("app-z"), + "accurate.cybozu.com/type": Equal("root"), + })) + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-z", Name: "z-team-admin"}, rb) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("rolebinding still exists") + }).Should(Succeed()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: "z-team"}, proj) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("appproject still exists") + }).Should(Succeed()) + + Eventually(func() error { + err := k8sClient.Get(ctx, client.ObjectKey{Name: "z-team"}, tenant) + if apierrors.IsNotFound(err) { + return nil + } + if err != nil { + return err + } + return errors.New("tenant still exists") + }).Should(Succeed()) }) }) diff --git a/controllers/testdata/appprojecttemplate.yaml b/controllers/testdata/appprojecttemplate.yaml new file mode 100644 index 0000000..3549362 --- /dev/null +++ b/controllers/testdata/appprojecttemplate.yaml @@ -0,0 +1,26 @@ +apiVersion: argoproj.io/v1alpha1 +kind: AppProject +spec: + destinations: + {{ range .Namespaces }} + - namespace: {{ . }} + server: '*' + {{ end }} + namespaceResourceBlacklist: + - group: "" + kind: ResourceQuota + - group: "" + kind: LimitRange + orphanedResources: + warn: false + roles: + - groups: + - cybozu-go:{{ .Name }} + {{ range .ExtraAdmins }} + - cybozu-go:{{ . }} + {{ end }} + name: admin + policies: + - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow + sourceRepos: + - '*' diff --git a/controllers/testdata/rolebindingtemplate.yaml b/controllers/testdata/rolebindingtemplate.yaml new file mode 100644 index 0000000..1829b28 --- /dev/null +++ b/controllers/testdata/rolebindingtemplate.yaml @@ -0,0 +1,15 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin +subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ .Name }} + {{ range .ExtraAdmins }} + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ . }} + {{ end }} diff --git a/docs/config.md b/docs/config.md index b0ce8d1..2f42dfd 100644 --- a/docs/config.md +++ b/docs/config.md @@ -30,8 +30,6 @@ argocd: kind: ResourceQuota - group: "" kind: LimitRange - - group: networking.k8s.io - kind: NetworkPolicy orphanedResources: warn: false sourceRepos: diff --git a/go.mod b/go.mod index b867e6a..ed224f3 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/cybozu-go/neco-tenant-controller go 1.16 require ( + github.com/google/go-cmp v0.5.5 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.15.0 github.com/spf13/cobra v1.1.3 diff --git a/hooks/tenant.go b/hooks/tenant.go index 1630bb8..49535cf 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -21,7 +21,7 @@ import ( "encoding/json" "net/http" - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "github.com/cybozu-go/neco-tenant-controller/pkg/constants" admissionv1 "k8s.io/api/admission/v1" "sigs.k8s.io/controller-runtime/pkg/client" @@ -43,7 +43,7 @@ func (m *tenantMutator) Handle(ctx context.Context, req admission.Request) admis return admission.Allowed("") } - sn := &multitenancyv1beta1.Tenant{} + sn := &tenantv1beta1.Tenant{} if err := m.dec.Decode(req, sn); err != nil { return admission.Errored(http.StatusBadRequest, err) } @@ -71,7 +71,7 @@ func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) adm return admission.Allowed("") } - sn := &multitenancyv1beta1.Tenant{} + sn := &tenantv1beta1.Tenant{} if err := v.dec.Decode(req, sn); err != nil { return admission.Errored(http.StatusBadRequest, err) } diff --git a/pkg/client/extract.go b/pkg/client/extract.go new file mode 100644 index 0000000..d3625da --- /dev/null +++ b/pkg/client/extract.go @@ -0,0 +1,48 @@ +package client + +import ( + "bytes" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" + "sigs.k8s.io/structured-merge-diff/v4/typed" +) + +func ExtractManagedFields(u *unstructured.Unstructured, manager string) (map[string]interface{}, error) { + fieldset := &fieldpath.Set{} + objManagedFields := u.GetManagedFields() + for _, mf := range objManagedFields { + if mf.Manager != manager || mf.Operation != metav1.ManagedFieldsOperationApply { + continue + } + fs := &fieldpath.Set{} + err := fs.FromJSON(bytes.NewReader(mf.FieldsV1.Raw)) + if err != nil { + return nil, err + } + fieldset = fieldset.Union(fs) + } + + d, err := typed.DeducedParseableType.FromUnstructured(u.Object) + if err != nil { + return nil, err + } + + x := d.ExtractItems(fieldset.Leaves()).AsValue().Unstructured() + managed, ok := x.(map[string]interface{}) + if !ok { + managed = make(map[string]interface{}) + } + + managed["apiVersion"] = u.GetAPIVersion() + managed["kind"] = u.GetKind() + metadata, ok := managed["metadata"].(map[string]interface{}) + if !ok { + metadata = make(map[string]interface{}) + } + metadata["name"] = u.GetName() + metadata["namespace"] = u.GetNamespace() + managed["metadata"] = metadata + return managed, nil +} From e5c7afe532ff7c95a0f3f2512927ededef7630e1 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sun, 26 Dec 2021 12:36:49 +0900 Subject: [PATCH 24/89] Support commonAnnotations Signed-off-by: zoetrope --- controllers/application_controller_test.go | 1 + controllers/tenant_controller.go | 6 +++ controllers/tenant_controller_test.go | 6 ++- pkg/config/testdata/config.yaml | 3 ++ pkg/config/testdata/invalid.yaml | 3 ++ pkg/config/teyps_test.go | 56 +++++++++++++++++++++- pkg/config/types.go | 5 ++ 7 files changed, 78 insertions(+), 2 deletions(-) diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index 300a757..7d143aa 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -55,6 +55,7 @@ var _ = Describe("Application controller", func() { config = &tenantconfig.Config{ Namespace: tenantconfig.NamespaceConfig{ CommonLabels: nil, + CommonAnnotations: nil, GroupKey: "team", RoleBindingTemplate: "", }, diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index ff06a11..f1af3bc 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -126,6 +126,9 @@ func (r *TenantReconciler) banishNamespace(ctx context.Context, ns *corev1.Names for k := range r.config.Namespace.CommonLabels { delete(managed.Labels, k) } + for k := range r.config.Namespace.CommonAnnotations { + delete(managed.Annotations, k) + } err = r.patchNamespace(ctx, managed) if err != nil { return err @@ -277,6 +280,9 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *tena labels[constants.OwnerTenant] = tenant.Name namespace.WithLabels(labels) annotations := make(map[string]string) + for k, v := range r.config.Namespace.CommonAnnotations { + annotations[k] = v + } for k, v := range ns.Annotations { annotations[k] = v } diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index ed3125a..6e1ac0b 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -48,6 +48,9 @@ var _ = Describe("Tenant controller", func() { CommonLabels: map[string]string{ "accurate.cybozu.com/template": "init-template", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + }, GroupKey: "team", RoleBindingTemplate: rolebindingTemplate, }, @@ -125,7 +128,8 @@ var _ = Describe("Tenant controller", func() { "accurate.cybozu.com/template": Equal("init-template"), })) Expect(ns.Annotations).Should(MatchAllKeys(Keys{ - "abc": Equal("def"), + "abc": Equal("def"), + "hoge": Equal("fuga"), })) rb := &rbacv1.RoleBinding{} diff --git a/pkg/config/testdata/config.yaml b/pkg/config/testdata/config.yaml index 9c8d7d1..598d0e6 100644 --- a/pkg/config/testdata/config.yaml +++ b/pkg/config/testdata/config.yaml @@ -2,6 +2,9 @@ namespace: commonLabels: foo: bar a: b + commonAnnotations: + hoge: fuga + c: d groupKey: abc rolebindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 diff --git a/pkg/config/testdata/invalid.yaml b/pkg/config/testdata/invalid.yaml index 03e0860..9c5d4b8 100644 --- a/pkg/config/testdata/invalid.yaml +++ b/pkg/config/testdata/invalid.yaml @@ -2,6 +2,9 @@ namespace: commonLabels: foo: bar a: b + commonAnnotations: + hoge: fuga + c: d groupKey: abc rolebindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 diff --git a/pkg/config/teyps_test.go b/pkg/config/teyps_test.go index 055183f..79908eb 100644 --- a/pkg/config/teyps_test.go +++ b/pkg/config/teyps_test.go @@ -2,8 +2,9 @@ package config import ( _ "embed" - "github.com/google/go-cmp/cmp" "testing" + + "github.com/google/go-cmp/cmp" ) //go:embed testdata/config.yaml @@ -22,6 +23,9 @@ func TestLoad(t *testing.T) { if !cmp.Equal(c.Namespace.CommonLabels, map[string]string{"foo": "bar", "a": "b"}) { t.Error("wrong common labels:", cmp.Diff(c.Namespace.CommonLabels, map[string]string{"foo": "bar", "a": "b"})) } + if !cmp.Equal(c.Namespace.CommonAnnotations, map[string]string{"hoge": "fuga", "c": "d"}) { + t.Error("wrong common annotations:", cmp.Diff(c.Namespace.CommonAnnotations, map[string]string{"hoge": "fuga", "c": "d"})) + } if c.Namespace.GroupKey != "abc" { t.Error("wrong group key:", cmp.Diff(c.Namespace.GroupKey, "abc")) } @@ -66,6 +70,10 @@ func TestValidate(t *testing.T) { "foo": "bar", "a": "b", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + "c": "d", + }, GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, @@ -84,6 +92,32 @@ func TestValidate(t *testing.T) { "foo!": "bar", "a": "b/c", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + "c": "d", + }, + GroupKey: "abc", + RoleBindingTemplate: "kind: RoleBinding", + }, + ArgoCD: ArgoCDConfig{ + Namespace: "argo", + AppProjectTemplate: "kind: AppProject", + }, + }, + isValid: false, + }, + { + name: "invalid common annotations", + config: &Config{ + Namespace: NamespaceConfig{ + CommonLabels: map[string]string{ + "foo": "bar", + "a": "b", + }, + CommonAnnotations: map[string]string{ + "!-hoge": "fuga", + "c": "d", + }, GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, @@ -102,6 +136,10 @@ func TestValidate(t *testing.T) { "foo": "bar", "a": "b", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + "c": "d", + }, GroupKey: "abc@", RoleBindingTemplate: "kind: RoleBinding", }, @@ -120,6 +158,10 @@ func TestValidate(t *testing.T) { "foo": "bar", "a": "b", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + "c": "d", + }, GroupKey: "", RoleBindingTemplate: "kind: RoleBinding", }, @@ -138,6 +180,10 @@ func TestValidate(t *testing.T) { "foo": "bar", "a": "b", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + "c": "d", + }, GroupKey: "abc", RoleBindingTemplate: "", }, @@ -156,6 +202,10 @@ func TestValidate(t *testing.T) { "foo": "bar", "a": "b", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + "c": "d", + }, GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, @@ -174,6 +224,10 @@ func TestValidate(t *testing.T) { "foo": "bar", "a": "b", }, + CommonAnnotations: map[string]string{ + "hoge": "fuga", + "c": "d", + }, GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, diff --git a/pkg/config/types.go b/pkg/config/types.go index 1c03bf5..093a2ab 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -2,6 +2,8 @@ package config import ( "errors" + + v1annotationvalidation "k8s.io/apimachinery/pkg/api/validation" v1labelvalidation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" "k8s.io/apimachinery/pkg/util/validation" "k8s.io/apimachinery/pkg/util/validation/field" @@ -19,6 +21,8 @@ type NamespaceConfig struct { // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller CommonLabels map[string]string `json:"commonLabels,omitempty"` + CommonAnnotations map[string]string `json:"commonAnnotations,omitempty"` + GroupKey string `json:"groupKey"` RoleBindingTemplate string `json:"rolebindingTemplate"` @@ -37,6 +41,7 @@ func (c *Config) Validate() error { var allErrs field.ErrorList allErrs = append(allErrs, v1labelvalidation.ValidateLabels(c.Namespace.CommonLabels, field.NewPath("namespace", "commonLabels"))...) + allErrs = append(allErrs, v1annotationvalidation.ValidateAnnotations(c.Namespace.CommonAnnotations, field.NewPath("namespace", "commonAnnotations"))...) allErrs = append(allErrs, v1labelvalidation.ValidateLabelName(c.Namespace.GroupKey, field.NewPath("namespace", "groupKey"))...) From 2eadf8c9e0ecf10a1ec5b0d8aeacb01031c57c63 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sun, 26 Dec 2021 14:20:52 +0900 Subject: [PATCH 25/89] Implement tenant webhook Signed-off-by: zoetrope --- cmd/neco-tenant-controller/sub/run.go | 2 +- hooks/suite_test.go | 50 +++++++++++- hooks/tenant.go | 55 ++++++++++--- hooks/tenant_test.go | 109 ++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 14 deletions(-) diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index e2170a1..c687941 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -83,7 +83,7 @@ func subMain(ns, addr string, port int) error { if err != nil { return fmt.Errorf("unable to create admission decoder: %w", err) } - hooks.SetupTenantWebhook(mgr, dec) + hooks.SetupTenantWebhook(mgr, dec, cfg) hooks.SetupApplicationWebhook(mgr, dec, cfg) //+kubebuilder:scaffold:builder diff --git a/hooks/suite_test.go b/hooks/suite_test.go index 104d60c..fb21d40 100644 --- a/hooks/suite_test.go +++ b/hooks/suite_test.go @@ -28,6 +28,7 @@ import ( //+kubebuilder:scaffold:imports tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" "github.com/cybozu-go/neco-tenant-controller/pkg/config" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" admissionv1beta1 "k8s.io/api/admission/v1beta1" @@ -115,7 +116,7 @@ var _ = BeforeSuite(func() { Namespace: "argocd", }, } - SetupTenantWebhook(mgr, dec) + SetupTenantWebhook(mgr, dec, config) SetupApplicationWebhook(mgr, dec, config) //+kubebuilder:scaffold:webhook @@ -134,7 +135,52 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-1" ns.Labels = map[string]string{ - "team": "a-team", + "team": "a-team", + "accurate.cybozu.com/parent": "app-a-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "sub-2" + ns.Labels = map[string]string{ + "team": "e-team", + "accurate.cybozu.com/parent": "app-e-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "app-a-team" + ns.Labels = map[string]string{ + constants.OwnerTenant: "a-team", + "team": "a-team", + "accurate.cybozu.com/type": "root", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "app-y-team" + ns.Labels = map[string]string{ + constants.OwnerTenant: "y-team", + "accurate.cybozu.com/type": "root", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "app-z-team" + ns.Labels = map[string]string{ + "team": "z-team", + } + err = k8sClient.Create(ctx, ns) + Expect(err).NotTo(HaveOccurred()) + + ns = &corev1.Namespace{} + ns.Name = "template" + ns.Labels = map[string]string{ + "accurate.cybozu.com/type": "template", } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) diff --git a/hooks/tenant.go b/hooks/tenant.go index 49535cf..d1380a2 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -22,8 +22,11 @@ import ( "net/http" tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/pkg/config" "github.com/cybozu-go/neco-tenant-controller/pkg/constants" admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/webhook" @@ -43,13 +46,13 @@ func (m *tenantMutator) Handle(ctx context.Context, req admission.Request) admis return admission.Allowed("") } - sn := &tenantv1beta1.Tenant{} - if err := m.dec.Decode(req, sn); err != nil { + tenant := &tenantv1beta1.Tenant{} + if err := m.dec.Decode(req, tenant); err != nil { return admission.Errored(http.StatusBadRequest, err) } - sn.Finalizers = []string{constants.Finalizer} - data, err := json.Marshal(sn) + tenant.Finalizers = []string{constants.Finalizer} + data, err := json.Marshal(tenant) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } @@ -60,26 +63,55 @@ func (m *tenantMutator) Handle(ctx context.Context, req admission.Request) admis //+kubebuilder:webhook:path=/validate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions={v1} type tenantValidator struct { - client.Client - dec *admission.Decoder + client client.Client + dec *admission.Decoder + config *config.Config } var _ admission.Handler = &tenantValidator{} func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) admission.Response { - if req.Operation != admissionv1.Create { + if req.Operation != admissionv1.Create && req.Operation != admissionv1.Update { return admission.Allowed("") } - sn := &tenantv1beta1.Tenant{} - if err := v.dec.Decode(req, sn); err != nil { + tenant := &tenantv1beta1.Tenant{} + if err := v.dec.Decode(req, tenant); err != nil { return admission.Errored(http.StatusBadRequest, err) } + + for _, ns := range tenant.Spec.Namespaces { + namespace := &corev1.Namespace{} + err := v.client.Get(ctx, client.ObjectKey{Name: ns.Name}, namespace) + if apierrors.IsNotFound(err) { + continue + } + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + owner := namespace.Labels[constants.OwnerTenant] + if owner != "" && owner != tenant.Name { + return admission.Denied("deny to specify other owner's namespace") + } + group := namespace.Labels[v.config.Namespace.GroupKey] + if group != "" && group != tenant.Name { + return admission.Denied("deny to specify other group's namespace") + } + nsType := namespace.Labels["accurate.cybozu.com/type"] + if nsType != "" && nsType != "root" { + return admission.Denied("deny to specify a namespace other than root") + } + parent := namespace.Labels["accurate.cybozu.com/parent"] + if parent != "" { + return admission.Denied("deny to specify a sub namespace") + } + } + return admission.Allowed("") } // SetupTenantWebhook registers the webhooks for Tenant -func SetupTenantWebhook(mgr manager.Manager, dec *admission.Decoder) { +func SetupTenantWebhook(mgr manager.Manager, dec *admission.Decoder, config *config.Config) { serv := mgr.GetWebhookServer() m := &tenantMutator{ @@ -88,8 +120,9 @@ func SetupTenantWebhook(mgr manager.Manager, dec *admission.Decoder) { serv.Register("/mutate-multi-tenancy-cybozu-com-v1beta1-tenant", &webhook.Admission{Handler: m}) v := &tenantValidator{ - Client: mgr.GetClient(), + client: mgr.GetClient(), dec: dec, + config: config, } serv.Register("/validate-multi-tenancy-cybozu-com-v1beta1-tenant", &webhook.Admission{Handler: v}) } diff --git a/hooks/tenant_test.go b/hooks/tenant_test.go index 6de678e..11a0dca 100644 --- a/hooks/tenant_test.go +++ b/hooks/tenant_test.go @@ -1 +1,110 @@ package hooks + +import ( + "context" + + tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +var _ = Describe("Tenant webhook", func() { + ctx := context.Background() + + It("should allow creating a tenant", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "a-team", + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + { + Name: "app-new", + }, + { + Name: "app-a-team", + }, + }, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).NotTo(HaveOccurred()) + + Expect(controllerutil.ContainsFinalizer(tenant, constants.Finalizer)).To(BeTrue()) + }) + + It("should deny creating a tenant with other owner's namespace", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "b-team", + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + { + Name: "app-y-team", + }, + }, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("deny to specify other owner's namespace")) + }) + + It("should deny creating a tenant with other group's namespace", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "c-team", + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + { + Name: "app-z-team", + }, + }, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("deny to specify other group's namespace")) + }) + + It("should deny creating a tenant with template namespace", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "d-team", + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + { + Name: "template", + }, + }, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("deny to specify a namespace other than root")) + }) + + It("should deny creating a tenant with other group's namespace", func() { + tenant := &tenantv1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "e-team", + }, + Spec: tenantv1beta1.TenantSpec{ + Namespaces: []tenantv1beta1.NamespaceSpec{ + { + Name: "sub-2", + }, + }, + }, + } + err := k8sClient.Create(ctx, tenant) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("deny to specify a sub namespace")) + }) +}) From 882001c8167ff7093bee62bc0e7ba3b17ca7a11d Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sun, 26 Dec 2021 15:10:25 +0900 Subject: [PATCH 26/89] Record events Signed-off-by: zoetrope --- cmd/neco-tenant-controller/sub/run.go | 1 + config/rbac/role.yaml | 8 +++++ controllers/application_controller.go | 40 ++++++++++++++++++---- controllers/application_controller_test.go | 39 +++++++++++++++++++-- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index c687941..8fffe13 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -74,6 +74,7 @@ func subMain(ns, addr string, port int) error { if err := controllers.NewApplicationReconciler( mgr.GetClient(), + mgr.GetEventRecorderFor("neco-tenant-controller"), cfg, ).SetupWithManager(ctx, mgr); err != nil { return fmt.Errorf("unable to create Namespace controller: %w", err) diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 26ac440..f6b3f0c 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -6,6 +6,14 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update - apiGroups: - argoproj.io resources: diff --git a/controllers/application_controller.go b/controllers/application_controller.go index e2a346c..fb099f8 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -15,6 +15,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" "k8s.io/client-go/util/workqueue" "k8s.io/utils/pointer" ctrl "sigs.k8s.io/controller-runtime" @@ -27,20 +28,23 @@ import ( "sigs.k8s.io/controller-runtime/pkg/source" ) -func NewApplicationReconciler(client client.Client, config *config.Config) *ApplicationReconciler { +func NewApplicationReconciler(client client.Client, recorder record.EventRecorder, config *config.Config) *ApplicationReconciler { return &ApplicationReconciler{ - client: client, - config: config, + client: client, + recorder: recorder, + config: config, } } // ApplicationReconciler reconciles an Application object type ApplicationReconciler struct { - client client.Client - config *config.Config + client client.Client + recorder record.EventRecorder + config *config.Config } //+kubebuilder:rbac:groups=argoproj.io,resources=applications,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups="",resources=events,verbs=create;update;patch func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { app := argocd.Application() @@ -158,6 +162,11 @@ func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstr if argocdApp != nil && argocdApp.GetDeletionTimestamp() == nil { logger.Info("Remove unmanaged application") err = r.client.Delete(ctx, argocdApp) + if err != nil { + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "RemoveApplicationFailed", "Failed to remove unmanaged application", err) + return + } + r.recorder.Eventf(tenantApp, corev1.EventTypeNormal, "ApplicationRemoved", "Remove unmanaged application succeeded") } removed = true return @@ -183,6 +192,11 @@ func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstr Force: pointer.BoolPtr(true), FieldManager: constants.ProjectFieldManager, }) + if err != nil { + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "FixProjectFailed", "Failed to fix application project", err) + return + } + r.recorder.Eventf(tenantApp, corev1.EventTypeNormal, "ProjectFixed", "Fix application project succeeded") return } return @@ -239,10 +253,16 @@ func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdA } } - return r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ + err := r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), FieldManager: constants.SpecFieldManager, }) + if err != nil { + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "SyncSpecFailed", "Failed to sync application spec", err) + return err + } + r.recorder.Eventf(tenantApp, corev1.EventTypeNormal, "ApplicationSynced", "Sync application spec succeeded") + return nil } func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { @@ -265,10 +285,16 @@ func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argoc } // MEMO: Use `r.Patch` instead of `r.Status().Patch()`, because the status of application is not a sub-resource. - return r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ + err = r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), FieldManager: constants.StatusFieldManager, }) + if err != nil { + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "SyncStatusFailed", "Failed to sync application status", err) + return err + } + r.recorder.Eventf(tenantApp, corev1.EventTypeNormal, "StatusSynced", "Sync application status succeeded") + return nil } // SetupWithManager sets up the controller with the Manager. diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index 7d143aa..cb830c7 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -64,7 +64,7 @@ var _ = Describe("Application controller", func() { AppProjectTemplate: "", }, } - ar := NewApplicationReconciler(mgr.GetClient(), config) + ar := NewApplicationReconciler(mgr.GetClient(), mgr.GetEventRecorderFor("neco-tenant-controller"), config) err = ar.SetupWithManager(ctx, mgr) Expect(err).ToNot(HaveOccurred()) @@ -149,6 +149,18 @@ var _ = Describe("Application controller", func() { return nil }).Should(Succeed()) Expect(tenantApp.UnstructuredContent()["status"]).Should(Equal(argocdApp.UnstructuredContent()["status"])) + + events := &corev1.EventList{} + err = k8sClient.List(ctx, events, client.InNamespace("sub-1")) + Expect(err).NotTo(HaveOccurred()) + Expect(events.Items).Should(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Reason": Equal("ApplicationSynced"), + }), + MatchFields(IgnoreExtras, Fields{ + "Reason": Equal("StatusSynced"), + }), + )) }) It("should remove an application on unmanaged namespace", func() { @@ -182,6 +194,18 @@ var _ = Describe("Application controller", func() { } return errors.New("application still exists") }).Should(Succeed()) + + events := &corev1.EventList{} + err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) + Expect(err).NotTo(HaveOccurred()) + Expect(events.Items).Should(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Reason": Equal("ApplicationSynced"), + }), + MatchFields(IgnoreExtras, Fields{ + "Reason": Equal("ApplicationRemoved"), + }), + )) }) It("should fix project", func() { @@ -240,6 +264,18 @@ var _ = Describe("Application controller", func() { } return nil }).Should(Succeed()) + + events := &corev1.EventList{} + err = k8sClient.List(ctx, events, client.InNamespace("sub-3")) + Expect(err).NotTo(HaveOccurred()) + Expect(events.Items).Should(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Reason": Equal("ApplicationSynced"), + }), + MatchFields(IgnoreExtras, Fields{ + "Reason": Equal("ProjectFixed"), + }), + )) }) It("should remove application", func() { @@ -285,5 +321,4 @@ var _ = Describe("Application controller", func() { return errors.New("application still exists") }).Should(Succeed()) }) - }) From 32bd1baaaa3cacdd57c33899a77eb036a10ebdbe Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sun, 26 Dec 2021 16:13:55 +0900 Subject: [PATCH 27/89] e2e test Signed-off-by: zoetrope --- Tiltfile | 14 +-- config/default/manager_auth_proxy_patch.yaml | 1 - config/dev/manager.yaml | 2 +- config/manager/manager.yaml | 7 +- .../{02_namespace.yaml => 00_template.yaml} | 7 -- config/samples/02_subnamespace.yaml | 6 ++ controllers/tenant_controller.go | 2 +- e2e/Makefile | 2 +- e2e/e2e_test.go | 99 ++++++++++++++++++- 9 files changed, 117 insertions(+), 23 deletions(-) rename config/samples/{02_namespace.yaml => 00_template.yaml} (51%) create mode 100644 config/samples/02_subnamespace.yaml diff --git a/Tiltfile b/Tiltfile index e6bad74..6f4adad 100644 --- a/Tiltfile +++ b/Tiltfile @@ -5,8 +5,8 @@ def kubebuilder(): DOCKERFILE = '''FROM golang:alpine WORKDIR / - COPY ./bin/manager / - CMD ["/manager"] + COPY ./bin/neco-tenant-controller / + CMD ["/neco-tenant-controller"] ''' def manifests(): @@ -16,7 +16,7 @@ def kubebuilder(): return 'controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' def binary(): - return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/manager cmd/neco-tenant-controller/main.go' + return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/neco-tenant-controller cmd/neco-tenant-controller/main.go' installed = local("which kubebuilder") print("kubebuilder is present:", installed) @@ -37,12 +37,12 @@ def kubebuilder(): local_resource('Sample YAML', 'kubectl apply -f ./config/samples', deps=["./config/samples"], resource_deps=[DIRNAME + "-controller-manager"]) - docker_build_with_restart('controller:latest', '.', + docker_build_with_restart('neco-tenant-controller:dev', '.', dockerfile_contents=DOCKERFILE, - entrypoint=['/manager', '--zap-devel=true'], - only=['./bin/manager'], + entrypoint=['/neco-tenant-controller', '--zap-devel=true'], + only=['./bin/neco-tenant-controller'], live_update=[ - sync('./bin/manager', '/manager'), + sync('./bin/neco-tenant-controller', '/neco-tenant-controller'), ] ) diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml index 4e2232f..bb88cff 100644 --- a/config/default/manager_auth_proxy_patch.yaml +++ b/config/default/manager_auth_proxy_patch.yaml @@ -24,4 +24,3 @@ spec: args: - "--health-probe-bind-address=:8081" - "--metrics-bind-address=127.0.0.1:8080" - - "--leader-elect" diff --git a/config/dev/manager.yaml b/config/dev/manager.yaml index d3c7d0c..3f890a2 100644 --- a/config/dev/manager.yaml +++ b/config/dev/manager.yaml @@ -9,6 +9,6 @@ spec: securityContext: null containers: - command: - - /manager + - /mneco-tenant-controller args: null name: manager diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index a1a68cd..10d3e5f 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -28,10 +28,9 @@ spec: runAsNonRoot: true containers: - command: - - /manager - args: - - --leader-elect - image: controller:latest + - /neco-tenant-controller + image: neco-tenant-controller:dev + imagePullPolicy: IfNotPresent env: - name: POD_NAMESPACE valueFrom: diff --git a/config/samples/02_namespace.yaml b/config/samples/00_template.yaml similarity index 51% rename from config/samples/02_namespace.yaml rename to config/samples/00_template.yaml index 08ece06..cf98f96 100644 --- a/config/samples/02_namespace.yaml +++ b/config/samples/00_template.yaml @@ -4,10 +4,3 @@ metadata: name: init-template labels: accurate.cybozu.com/type: template ---- -apiVersion: v1 -kind: Namespace -metadata: - name: sub-1 - labels: - accurate.cybozu.com/parent: app-a diff --git a/config/samples/02_subnamespace.yaml b/config/samples/02_subnamespace.yaml new file mode 100644 index 0000000..f826772 --- /dev/null +++ b/config/samples/02_subnamespace.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: sub-1 + labels: + accurate.cybozu.com/parent: app-a diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index f1af3bc..cc534fe 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -397,7 +397,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *tenantv1 proj := argocd.AppProject() dec := yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme) - _, _, err = dec.Decode([]byte(buf.String()), nil, proj) + _, _, err = dec.Decode(buf.Bytes(), nil, proj) if err != nil { return err } diff --git a/e2e/Makefile b/e2e/Makefile index 7659989..a32f7ab 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -27,7 +27,7 @@ start: $(KIND) $(KUBECTL) $(KIND) load docker-image neco-tenant-controller:dev --name=neco-tenant-controller $(MAKE) prepare $(KUBECTL) apply -k ../config/default - $(KUBECTL) -n tenant-system wait --for=condition=available --timeout=180s --all deployments + $(KUBECTL) -n neco-tenant-controller-system wait --for=condition=available --timeout=180s --all deployments .PHONY: prepare prepare: $(KUBECTL) $(HELM) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 44c219f..ccc3379 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1,10 +1,107 @@ package e2e import ( + _ "embed" + "encoding/json" + "errors" + + "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) var _ = Describe("neco-tenant-controller", func() { - It("should work", func() { + It("should prepare", func() { + kubectlSafe(nil, "apply", "-f", "../config/samples/00_template.yaml") + kubectlSafe(nil, "apply", "-f", "../config/samples/01_tenant.yaml") + kubectlSafe(nil, "apply", "-f", "../config/samples/02_subnamespace.yaml") + kubectlSafe(nil, "apply", "-f", "../config/samples/03_application.yaml") + }) + + It("should sync application", func() { + Eventually(func() error { + out, err := kubectl(nil, "get", "app", "-n", "sub-1", "sample", "-o", "json") + if err != nil { + return err + } + app := argocd.Application() + if err := json.Unmarshal(out, app); err != nil { + return err + } + healthStatus, found, err := unstructured.NestedString(app.UnstructuredContent(), "status", "health", "status") + if err != nil { + return err + } + if !found { + return errors.New("status not found") + } + if healthStatus != "Healthy" { + return errors.New("status is not healthy") + } + + syncStatus, found, err := unstructured.NestedString(app.UnstructuredContent(), "status", "sync", "status") + if err != nil { + return err + } + if !found { + return errors.New("status not found") + } + if syncStatus != "Synced" { + return errors.New("status is not synced") + } + + return nil + }).Should(Succeed()) + }) + + It("should change ownership", func() { + kubectlSafe(nil, "label", "ns", "sub-1", "accurate.cybozu.com/parent=app-b", "--overwrite") + + Eventually(func() error { + out, err := kubectl(nil, "get", "app", "-n", "sub-1", "sample", "-o", "json") + if err != nil { + return err + } + app := argocd.Application() + if err := json.Unmarshal(out, app); err != nil { + return err + } + + project, found, err := unstructured.NestedString(app.UnstructuredContent(), "spec", "project") + if err != nil { + return err + } + if !found { + return errors.New("project not found") + } + if project != "b-team" { + return errors.New("project is not fixed") + } + + healthStatus, found, err := unstructured.NestedString(app.UnstructuredContent(), "status", "health", "status") + if err != nil { + return err + } + if !found { + return errors.New("status not found") + } + if healthStatus != "Healthy" { + return errors.New("status is not healthy") + } + + syncStatus, found, err := unstructured.NestedString(app.UnstructuredContent(), "status", "sync", "status") + if err != nil { + return err + } + if !found { + return errors.New("status not found") + } + if syncStatus != "Synced" { + return errors.New("status is not synced") + } + + return nil + }).Should(Succeed()) }) }) From 0b9587fb2a38015fbe7d15a55984d16af947ff1a Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sun, 26 Dec 2021 18:21:31 +0900 Subject: [PATCH 28/89] Update tenant status Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 9 +++ api/v1beta1/zz_generated.deepcopy.go | 10 ++- .../multi-tenancy.cybozu.com_tenants.yaml | 77 ++++++++++++++++++- controllers/tenant_controller.go | 37 ++++++++- 4 files changed, 129 insertions(+), 4 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 9566815..ce0b8ee 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -61,11 +61,20 @@ type ArgoCDSpec struct { type TenantStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file + + // Conditions is an array of conditions. + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` } +const ( + ConditionReady string = "Ready" +) + //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:resource:scope=Cluster +//+kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" // Tenant is the Schema for the tenants API type Tenant struct { diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 70b7f01..ded9756 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -6,6 +6,7 @@ package v1beta1 import ( + "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -69,7 +70,7 @@ func (in *Tenant) DeepCopyInto(out *Tenant) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Tenant. @@ -148,6 +149,13 @@ func (in *TenantSpec) DeepCopy() *TenantSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantStatus) DeepCopyInto(out *TenantStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantStatus. diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index 1468aca..ad72ef9 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -16,7 +16,11 @@ spec: singular: tenant scope: Cluster versions: - - name: v1beta1 + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + name: v1beta1 schema: openAPIV3Schema: description: Tenant is the Schema for the tenants API @@ -79,6 +83,77 @@ spec: type: object status: description: TenantStatus defines the observed state of Tenant + properties: + conditions: + description: Conditions is an array of conditions. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array type: object type: object served: true diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index cc534fe..6c93687 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -31,6 +31,8 @@ import ( rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/serializer/yaml" @@ -81,7 +83,9 @@ type TenantReconciler struct { // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.10.0/pkg/reconcile -func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { +func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { + logger := log.FromContext(ctx) + tenant := &tenantv1beta1.Tenant{} if err := r.client.Get(ctx, req.NamespacedName, tenant); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) @@ -94,16 +98,45 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr return ctrl.Result{}, nil } - err := r.reconcileNamespaces(ctx, tenant) + defer func(before []metav1.Condition) { + if !equality.Semantic.DeepEqual(tenant.Status.Conditions, before) { + logger.Info("update conditions", "conditions", tenant.Status.Conditions, "before", before) + if err2 := r.client.Status().Update(ctx, tenant); err2 != nil { + logger.Error(err2, "failed to update status") + err = err2 + } + } + }(tenant.Status.Conditions) + + err = r.reconcileNamespaces(ctx, tenant) if err != nil { + meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ + Type: tenantv1beta1.ConditionReady, + Status: metav1.ConditionFalse, + Reason: "Failed", + Message: err.Error(), + }) return ctrl.Result{}, err } err = r.reconcileArgoCD(ctx, tenant) if err != nil { + meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ + Type: tenantv1beta1.ConditionReady, + Status: metav1.ConditionFalse, + Reason: "Failed", + Message: err.Error(), + }) return ctrl.Result{}, err } + meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ + Type: tenantv1beta1.ConditionReady, + Status: metav1.ConditionTrue, + Reason: "OK", + }) + logger.Info("Tenant successfully reconciled") + return ctrl.Result{}, nil } From ea6ff81f6ba26765ac9c7b64703456ffbe38fa0a Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sun, 26 Dec 2021 18:58:09 +0900 Subject: [PATCH 29/89] Remove GroupKey Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 6 -- cmd/neco-tenant-controller/sub/run.go | 2 +- config/manager/configmap.yaml | 1 - controllers/application_controller.go | 10 ++-- controllers/application_controller_test.go | 3 +- controllers/suite_test.go | 18 +++--- controllers/tenant_controller.go | 29 ++++++---- controllers/tenant_controller_test.go | 11 ++-- docs/config.md | 13 ----- docs/crd_tenant.md | 67 ++++------------------ e2e/accurate-values.yaml | 2 +- hooks/application.go | 4 +- hooks/suite_test.go | 17 +----- hooks/tenant.go | 4 -- hooks/tenant_test.go | 18 ------ pkg/config/testdata/config.yaml | 1 - pkg/config/testdata/invalid.yaml | 1 - pkg/config/teyps_test.go | 53 ----------------- pkg/config/types.go | 5 +- pkg/constants/indexer.go | 3 +- pkg/constants/meta.go | 4 +- 21 files changed, 62 insertions(+), 210 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index ce0b8ee..a803224 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -20,9 +20,6 @@ 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. - // TenantSpec defines the desired state of Tenant type TenantSpec struct { Namespaces []NamespaceSpec `json:"namespaces,omitempty"` @@ -59,9 +56,6 @@ type ArgoCDSpec struct { // TenantStatus defines the observed state of Tenant type TenantStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file - // Conditions is an array of conditions. // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/neco-tenant-controller/sub/run.go index 8fffe13..d850706 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/neco-tenant-controller/sub/run.go @@ -62,7 +62,7 @@ func subMain(ns, addr string, port int) error { return fmt.Errorf("invalid configurations: %w", err) } ctx := ctrl.SetupSignalHandler() - if err := controllers.SetupIndexForNamespace(ctx, mgr, cfg.Namespace.GroupKey); err != nil { + if err := controllers.SetupIndexForNamespace(ctx, mgr); err != nil { return fmt.Errorf("failed to setup indexer for namespaces: %w", err) } if err := controllers.NewTenantReconciler( diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index 42d3a53..649fa5e 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -8,7 +8,6 @@ data: namespace: commonLabels: accurate.cybozu.com/template: init-template - groupKey: team rolebindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/controllers/application_controller.go b/controllers/application_controller.go index fb099f8..6f13cf4 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -157,8 +157,8 @@ func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstr if err != nil { return } - group := ns.Labels[r.config.Namespace.GroupKey] - if group == "" { + tenantName := ns.Labels[constants.OwnerTenant] + if tenantName == "" { if argocdApp != nil && argocdApp.GetDeletionTimestamp() == nil { logger.Info("Remove unmanaged application") err = r.client.Delete(ctx, argocdApp) @@ -179,12 +179,12 @@ func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstr err = errors.New("spec.project not found") return } - if project != group { - logger.Info("Overwrite project", "before", project, "after", group) + if project != tenantName { + logger.Info("Overwrite project", "before", project, "after", tenantName) newApp := argocd.Application() newApp.SetNamespace(tenantApp.GetNamespace()) newApp.SetName(tenantApp.GetName()) - err = unstructured.SetNestedField(newApp.UnstructuredContent(), group, "spec", "project") + err = unstructured.SetNestedField(newApp.UnstructuredContent(), tenantName, "spec", "project") if err != nil { return } diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index cb830c7..00bb23c 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -56,7 +56,6 @@ var _ = Describe("Application controller", func() { Namespace: tenantconfig.NamespaceConfig{ CommonLabels: nil, CommonAnnotations: nil, - GroupKey: "team", RoleBindingTemplate: "", }, ArgoCD: tenantconfig.ArgoCDConfig{ @@ -226,7 +225,7 @@ var _ = Describe("Application controller", func() { ns := &corev1.Namespace{} ns.Name = "sub-3" ns.Labels = map[string]string{ - "team": "b-team", + constants.OwnerTenant: "b-team", } err = k8sClient.Update(ctx, ns) Expect(err).NotTo(HaveOccurred()) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index cb6176f..a52b7de 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -22,15 +22,13 @@ import ( "testing" "time" - corev1 "k8s.io/api/core/v1" - - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - - "k8s.io/apimachinery/pkg/runtime" - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" + "github.com/cybozu-go/neco-tenant-controller/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/envtest" @@ -96,7 +94,7 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-1" ns.Labels = map[string]string{ - "team": "a-team", + constants.OwnerTenant: "a-team", } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -104,7 +102,7 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-2" ns.Labels = map[string]string{ - "team": "a-team", + constants.OwnerTenant: "a-team", } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -112,7 +110,7 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-3" ns.Labels = map[string]string{ - "team": "a-team", + constants.OwnerTenant: "a-team", } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -120,7 +118,7 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-4" ns.Labels = map[string]string{ - "team": "x-team", + constants.OwnerTenant: "x-team", } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 6c93687..42c0c79 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -155,7 +155,6 @@ func (r *TenantReconciler) banishNamespace(ctx context.Context, ns *corev1.Names return err } delete(managed.Labels, constants.OwnerTenant) - delete(managed.Labels, r.config.Namespace.GroupKey) for k := range r.config.Namespace.CommonLabels { delete(managed.Labels, k) } @@ -210,7 +209,7 @@ func (r *TenantReconciler) finalize(ctx context.Context, tenant *tenantv1beta1.T } logger.Info("starting finalization") nss := &corev1.NamespaceList{} - if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + if err := r.client.List(ctx, nss, client.MatchingFields{constants.RootNamespaces: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } for _, ns := range nss.Items { @@ -309,7 +308,6 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *tena labels[k] = v } labels["accurate.cybozu.com/type"] = "root" - labels[r.config.Namespace.GroupKey] = tenant.Name labels[constants.OwnerTenant] = tenant.Name namespace.WithLabels(labels) annotations := make(map[string]string) @@ -359,7 +357,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *tena } } nss := &corev1.NamespaceList{} - if err := r.client.List(ctx, nss, client.MatchingFields{constants.NamespaceGroupKey: tenant.Name}); err != nil { + if err := r.client.List(ctx, nss, client.MatchingFields{constants.RootNamespaces: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } for _, ns := range nss.Items { @@ -401,7 +399,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *tenantv1 } nss := &corev1.NamespaceList{} - if err := r.client.List(ctx, nss, client.MatchingLabels{r.config.Namespace.GroupKey: tenant.Name}); err != nil { + if err := r.client.List(ctx, nss, client.MatchingFields{constants.TenantNamespaces: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) } namespaces := make([]string, len(nss.Items)) @@ -497,17 +495,28 @@ func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { Complete(r) } -func SetupIndexForNamespace(ctx context.Context, mgr manager.Manager, groupKey string) error { +func SetupIndexForNamespace(ctx context.Context, mgr manager.Manager) error { ns := &corev1.Namespace{} - return mgr.GetFieldIndexer().IndexField(ctx, ns, constants.NamespaceGroupKey, func(rawObj client.Object) []string { + err := mgr.GetFieldIndexer().IndexField(ctx, ns, constants.RootNamespaces, func(rawObj client.Object) []string { nsType := rawObj.GetLabels()["accurate.cybozu.com/type"] if nsType != "root" { return nil } - group := rawObj.GetLabels()[groupKey] - if group == "" { + tenantName := rawObj.GetLabels()[constants.OwnerTenant] + if tenantName == "" { return nil } - return []string{group} + return []string{tenantName} + }) + if err != nil { + return err + } + + return mgr.GetFieldIndexer().IndexField(ctx, ns, constants.TenantNamespaces, func(rawObj client.Object) []string { + tenantName := rawObj.GetLabels()[constants.OwnerTenant] + if tenantName == "" { + return nil + } + return []string{tenantName} }) } diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 6e1ac0b..f37f2e8 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -51,7 +51,6 @@ var _ = Describe("Tenant controller", func() { CommonAnnotations: map[string]string{ "hoge": "fuga", }, - GroupKey: "team", RoleBindingTemplate: rolebindingTemplate, }, ArgoCD: tenantconfig.ArgoCDConfig{ @@ -62,7 +61,7 @@ var _ = Describe("Tenant controller", func() { tr := NewTenantReconciler(mgr.GetClient(), config) err = tr.SetupWithManager(mgr) Expect(err).ToNot(HaveOccurred()) - err = SetupIndexForNamespace(ctx, mgr, config.Namespace.GroupKey) + err = SetupIndexForNamespace(ctx, mgr) Expect(err).ToNot(HaveOccurred()) ctx, cancel := context.WithCancel(ctx) @@ -122,7 +121,6 @@ var _ = Describe("Tenant controller", func() { Expect(ns.Labels).Should(MatchAllKeys(Keys{ "kubernetes.io/metadata.name": Equal("app-x"), "accurate.cybozu.com/type": Equal("root"), - config.Namespace.GroupKey: Equal("x-team"), constants.OwnerTenant: Equal("x-team"), "foo": Equal("bar"), "accurate.cybozu.com/template": Equal("init-template"), @@ -221,7 +219,6 @@ var _ = Describe("Tenant controller", func() { Expect(nsy1.Labels).Should(MatchAllKeys(Keys{ "kubernetes.io/metadata.name": Equal("app-y1"), "accurate.cybozu.com/type": Equal("root"), - config.Namespace.GroupKey: Equal("y-team"), constants.OwnerTenant: Equal("y-team"), "accurate.cybozu.com/template": Equal("init-template"), })) @@ -236,7 +233,6 @@ var _ = Describe("Tenant controller", func() { Expect(nsy2.Labels).Should(MatchAllKeys(Keys{ "kubernetes.io/metadata.name": Equal("app-y2"), "accurate.cybozu.com/type": Equal("root"), - config.Namespace.GroupKey: Equal("y-team"), constants.OwnerTenant: Equal("y-team"), "accurate.cybozu.com/template": Equal("init-template"), })) @@ -314,6 +310,8 @@ var _ = Describe("Tenant controller", func() { })) By("removing app-y2") + err = k8sClient.Get(ctx, client.ObjectKey{Name: tenant.Name}, tenant) + Expect(err).ToNot(HaveOccurred()) tenant.Spec.Namespaces = []tenantv1beta1.NamespaceSpec{ {Name: "app-y1"}, } @@ -366,6 +364,8 @@ var _ = Describe("Tenant controller", func() { }).Should(Succeed()) By("removing app-y1") + err = k8sClient.Get(ctx, client.ObjectKey{Name: tenant.Name}, tenant) + Expect(err).ToNot(HaveOccurred()) tenant.Spec.Namespaces = []tenantv1beta1.NamespaceSpec{} err = k8sClient.Update(ctx, tenant) Expect(err).ToNot(HaveOccurred()) @@ -433,7 +433,6 @@ var _ = Describe("Tenant controller", func() { Expect(ns.Labels).Should(MatchAllKeys(Keys{ "kubernetes.io/metadata.name": Equal("app-z"), "accurate.cybozu.com/type": Equal("root"), - config.Namespace.GroupKey: Equal("z-team"), constants.OwnerTenant: Equal("z-team"), "accurate.cybozu.com/template": Equal("init-template"), })) diff --git a/docs/config.md b/docs/config.md index 2f42dfd..7ebed81 100644 --- a/docs/config.md +++ b/docs/config.md @@ -15,11 +15,6 @@ namespace: argocd: # The name of namespace where Argo CD is deployed namespace: argocd - # The mode of validation for Application resources. - # If true is set, this does not deny Application resources but issues a warning. - permissiveValidation: true - - organization: "cybozu-go" appProjectTemplate: | apiVersion: argoproj.io/v1alpha1 @@ -34,12 +29,4 @@ argocd: warn: false sourceRepos: - '*' - -teleport: - # The name of namespace where Teleport Nodes are deployed - namespace: teleport - # The name of Teleport container image - image: quay.io/cybozu/teleport-node:latest - # The name of secret resource contains a license key for Teleport Enterprise - licenseSecretName: teleport-general-secret-20210310 ``` diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 546a20a..1b50430 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -5,27 +5,11 @@ ### Sub Resources -* [ArgoCDApplicationSpec](#argocdapplicationspec) * [ArgoCDSpec](#argocdspec) * [NamespaceSpec](#namespacespec) -* [TeleportApplicationSpec](#teleportapplicationspec) -* [TeleportNodeSpec](#teleportnodespec) -* [TeleportSpec](#teleportspec) * [TenantList](#tenantlist) * [TenantSpec](#tenantspec) - -#### ArgoCDApplicationSpec - -ArgoCDApplicationSpec defines the desired state of Application - -| Field | Description | Scheme | Required | -| ----- | ----------- | ------ | -------- | -| name | Name is the name of Application resource. | string | true | -| path | Path is a directory path within the Git repository, and is only valid for applications sourced from Git. | string | true | -| repoURL | RepoURL is the URL to the repository (Git or Helm) that contains the application manifests. | string | true | -| targetRevision | TargetRevision defines the revision of the source to sync the application to. In case of Git, this can be commit, tag, or branch. If omitted, will equal to HEAD. In case of Helm, this is a semver tag for the Chart's version. | string | true | - -[Back to Custom Resources](#custom-resources) +* [TenantStatus](#tenantstatus) #### ArgoCDSpec @@ -33,8 +17,6 @@ ArgoCDSpec defines the desired state of the settings for Argo CD | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | -| applications | Applications are the list of Application resources managed by the tenant team. | [][ArgoCDApplicationSpec](#argocdapplicationspec) | false | -| repositories | Repositories are the list of repositories used by the tenant team. | []string | false | | extraAdmins | ExtraAdmins are the names of the team to add to the AppProject user. Specify this if you want other tenant teams to be able to use your AppProject. | []string | false | [Back to Custom Resources](#custom-resources) @@ -52,40 +34,6 @@ NamespaceSpec defines the desired state of Namespace [Back to Custom Resources](#custom-resources) -#### TeleportApplicationSpec - -TeleportApplicationSpec defines the desired state of Teleport Application. - -| Field | Description | Scheme | Required | -| ----- | ----------- | ------ | -------- | -| name | Name is the name of the application to proxy. | string | true | -| url | URL is the internal address of the application to proxy. | string | true | -| extraArgs | ExtraArgs are the list of additional arguments to be specified for Teleport Application Pod. | []string | false | - -[Back to Custom Resources](#custom-resources) - -#### TeleportNodeSpec - - - -| Field | Description | Scheme | Required | -| ----- | ----------- | ------ | -------- | -| replicas | Replicas is the number of Teleport Node Pods. | int | true | -| extraArgs | ExtraArgs are the list of additional arguments to be specified for Teleport Node Pod. | []string | false | - -[Back to Custom Resources](#custom-resources) - -#### TeleportSpec - -TeleportSpec defines the desired state of the settings for Teleport - -| Field | Description | Scheme | Required | -| ----- | ----------- | ------ | -------- | -| node | Node is the settings of Teleport Node for the tenant team. | *[TeleportNodeSpec](#teleportnodespec) | false | -| applications | Applications are the list of applications to be used by the tenant team. | [][TeleportApplicationSpec](#teleportapplicationspec) | false | - -[Back to Custom Resources](#custom-resources) - #### Tenant Tenant is the Schema for the tenants API @@ -116,7 +64,16 @@ TenantSpec defines the desired state of Tenant | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | | namespaces | | [][NamespaceSpec](#namespacespec) | false | -| argocd | | *[ArgoCDSpec](#argocdspec) | false | -| teleport | | *[TeleportSpec](#teleportspec) | false | +| argocd | | [ArgoCDSpec](#argocdspec) | false | + +[Back to Custom Resources](#custom-resources) + +#### TenantStatus + +TenantStatus defines the observed state of Tenant + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| conditions | Conditions is an array of conditions. | []metav1.Condition | false | [Back to Custom Resources](#custom-resources) diff --git a/e2e/accurate-values.yaml b/e2e/accurate-values.yaml index 3d456c5..8f2a9ac 100644 --- a/e2e/accurate-values.yaml +++ b/e2e/accurate-values.yaml @@ -1,7 +1,7 @@ controller: config: labelKeys: - - team + - multi-tenancy.cybozu.com/tenant watches: - group: rbac.authorization.k8s.io version: v1 diff --git a/hooks/application.go b/hooks/application.go index ee137e7..cf436ec 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -84,7 +84,7 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request return admission.Errored(http.StatusInternalServerError, err) } - group, ok := ns.Labels[v.config.Namespace.GroupKey] + tenantName, ok := ns.Labels[constants.OwnerTenant] if !ok { return admission.Denied("an application cannot be created on unmanaged namespaces") } @@ -115,7 +115,7 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request return admission.Errored(http.StatusBadRequest, errors.New("spec.project not found")) } - if group != project { + if tenantName != project { return admission.Denied("cannot specify a project for other tenants") } diff --git a/hooks/suite_test.go b/hooks/suite_test.go index fb21d40..c627c0d 100644 --- a/hooks/suite_test.go +++ b/hooks/suite_test.go @@ -109,9 +109,7 @@ var _ = BeforeSuite(func() { Expect(err).NotTo(HaveOccurred()) config := &config.Config{ - Namespace: config.NamespaceConfig{ - GroupKey: "team", - }, + Namespace: config.NamespaceConfig{}, ArgoCD: config.ArgoCDConfig{ Namespace: "argocd", }, @@ -135,7 +133,7 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-1" ns.Labels = map[string]string{ - "team": "a-team", + constants.OwnerTenant: "a-team", "accurate.cybozu.com/parent": "app-a-team", } err = k8sClient.Create(ctx, ns) @@ -144,7 +142,7 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-2" ns.Labels = map[string]string{ - "team": "e-team", + constants.OwnerTenant: "e-team", "accurate.cybozu.com/parent": "app-e-team", } err = k8sClient.Create(ctx, ns) @@ -154,7 +152,6 @@ var _ = BeforeSuite(func() { ns.Name = "app-a-team" ns.Labels = map[string]string{ constants.OwnerTenant: "a-team", - "team": "a-team", "accurate.cybozu.com/type": "root", } err = k8sClient.Create(ctx, ns) @@ -169,14 +166,6 @@ var _ = BeforeSuite(func() { err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) - ns = &corev1.Namespace{} - ns.Name = "app-z-team" - ns.Labels = map[string]string{ - "team": "z-team", - } - err = k8sClient.Create(ctx, ns) - Expect(err).NotTo(HaveOccurred()) - ns = &corev1.Namespace{} ns.Name = "template" ns.Labels = map[string]string{ diff --git a/hooks/tenant.go b/hooks/tenant.go index d1380a2..b4c31bb 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -93,10 +93,6 @@ func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) adm if owner != "" && owner != tenant.Name { return admission.Denied("deny to specify other owner's namespace") } - group := namespace.Labels[v.config.Namespace.GroupKey] - if group != "" && group != tenant.Name { - return admission.Denied("deny to specify other group's namespace") - } nsType := namespace.Labels["accurate.cybozu.com/type"] if nsType != "" && nsType != "root" { return admission.Denied("deny to specify a namespace other than root") diff --git a/hooks/tenant_test.go b/hooks/tenant_test.go index 11a0dca..19460ca 100644 --- a/hooks/tenant_test.go +++ b/hooks/tenant_test.go @@ -54,24 +54,6 @@ var _ = Describe("Tenant webhook", func() { Expect(err.Error()).Should(ContainSubstring("deny to specify other owner's namespace")) }) - It("should deny creating a tenant with other group's namespace", func() { - tenant := &tenantv1beta1.Tenant{ - ObjectMeta: metav1.ObjectMeta{ - Name: "c-team", - }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ - { - Name: "app-z-team", - }, - }, - }, - } - err := k8sClient.Create(ctx, tenant) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("deny to specify other group's namespace")) - }) - It("should deny creating a tenant with template namespace", func() { tenant := &tenantv1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ diff --git a/pkg/config/testdata/config.yaml b/pkg/config/testdata/config.yaml index 598d0e6..55b92a2 100644 --- a/pkg/config/testdata/config.yaml +++ b/pkg/config/testdata/config.yaml @@ -5,7 +5,6 @@ namespace: commonAnnotations: hoge: fuga c: d - groupKey: abc rolebindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/pkg/config/testdata/invalid.yaml b/pkg/config/testdata/invalid.yaml index 9c5d4b8..51ecf47 100644 --- a/pkg/config/testdata/invalid.yaml +++ b/pkg/config/testdata/invalid.yaml @@ -5,7 +5,6 @@ namespace: commonAnnotations: hoge: fuga c: d - groupKey: abc rolebindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding diff --git a/pkg/config/teyps_test.go b/pkg/config/teyps_test.go index 79908eb..3e4325f 100644 --- a/pkg/config/teyps_test.go +++ b/pkg/config/teyps_test.go @@ -26,9 +26,6 @@ func TestLoad(t *testing.T) { if !cmp.Equal(c.Namespace.CommonAnnotations, map[string]string{"hoge": "fuga", "c": "d"}) { t.Error("wrong common annotations:", cmp.Diff(c.Namespace.CommonAnnotations, map[string]string{"hoge": "fuga", "c": "d"})) } - if c.Namespace.GroupKey != "abc" { - t.Error("wrong group key:", cmp.Diff(c.Namespace.GroupKey, "abc")) - } if c.Namespace.RoleBindingTemplate != `apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding ` { @@ -74,7 +71,6 @@ func TestValidate(t *testing.T) { "hoge": "fuga", "c": "d", }, - GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, ArgoCD: ArgoCDConfig{ @@ -96,7 +92,6 @@ func TestValidate(t *testing.T) { "hoge": "fuga", "c": "d", }, - GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, ArgoCD: ArgoCDConfig{ @@ -118,51 +113,6 @@ func TestValidate(t *testing.T) { "!-hoge": "fuga", "c": "d", }, - GroupKey: "abc", - RoleBindingTemplate: "kind: RoleBinding", - }, - ArgoCD: ArgoCDConfig{ - Namespace: "argo", - AppProjectTemplate: "kind: AppProject", - }, - }, - isValid: false, - }, - { - name: "invalid group key", - config: &Config{ - Namespace: NamespaceConfig{ - CommonLabels: map[string]string{ - "foo": "bar", - "a": "b", - }, - CommonAnnotations: map[string]string{ - "hoge": "fuga", - "c": "d", - }, - GroupKey: "abc@", - RoleBindingTemplate: "kind: RoleBinding", - }, - ArgoCD: ArgoCDConfig{ - Namespace: "argo", - AppProjectTemplate: "kind: AppProject", - }, - }, - isValid: false, - }, - { - name: "empty group key", - config: &Config{ - Namespace: NamespaceConfig{ - CommonLabels: map[string]string{ - "foo": "bar", - "a": "b", - }, - CommonAnnotations: map[string]string{ - "hoge": "fuga", - "c": "d", - }, - GroupKey: "", RoleBindingTemplate: "kind: RoleBinding", }, ArgoCD: ArgoCDConfig{ @@ -184,7 +134,6 @@ func TestValidate(t *testing.T) { "hoge": "fuga", "c": "d", }, - GroupKey: "abc", RoleBindingTemplate: "", }, ArgoCD: ArgoCDConfig{ @@ -206,7 +155,6 @@ func TestValidate(t *testing.T) { "hoge": "fuga", "c": "d", }, - GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, ArgoCD: ArgoCDConfig{ @@ -228,7 +176,6 @@ func TestValidate(t *testing.T) { "hoge": "fuga", "c": "d", }, - GroupKey: "abc", RoleBindingTemplate: "kind: RoleBinding", }, ArgoCD: ArgoCDConfig{ diff --git a/pkg/config/types.go b/pkg/config/types.go index 093a2ab..89c79d8 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -21,10 +21,9 @@ type NamespaceConfig struct { // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller CommonLabels map[string]string `json:"commonLabels,omitempty"` + // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller CommonAnnotations map[string]string `json:"commonAnnotations,omitempty"` - GroupKey string `json:"groupKey"` - RoleBindingTemplate string `json:"rolebindingTemplate"` } @@ -43,8 +42,6 @@ func (c *Config) Validate() error { allErrs = append(allErrs, v1labelvalidation.ValidateLabels(c.Namespace.CommonLabels, field.NewPath("namespace", "commonLabels"))...) allErrs = append(allErrs, v1annotationvalidation.ValidateAnnotations(c.Namespace.CommonAnnotations, field.NewPath("namespace", "commonAnnotations"))...) - allErrs = append(allErrs, v1labelvalidation.ValidateLabelName(c.Namespace.GroupKey, field.NewPath("namespace", "groupKey"))...) - if len(c.Namespace.RoleBindingTemplate) == 0 { allErrs = append(allErrs, field.Invalid(field.NewPath("namespace", "rolebindingTemplate"), c.Namespace.RoleBindingTemplate, "should not be empty")) } diff --git a/pkg/constants/indexer.go b/pkg/constants/indexer.go index 26766b8..b04f6a0 100644 --- a/pkg/constants/indexer.go +++ b/pkg/constants/indexer.go @@ -1,3 +1,4 @@ package constants -const NamespaceGroupKey = "namespace.group" +const RootNamespaces = "neco-tenant-controller.namespaces.root" +const TenantNamespaces = "neco-tenant-controller.namespaces.tenant" diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go index 53b4288..e9fe1a8 100644 --- a/pkg/constants/meta.go +++ b/pkg/constants/meta.go @@ -6,9 +6,9 @@ const MetaPrefix = "multi-tenancy.cybozu.com/" // Finalizer is the finalizer ID of Accurate. const Finalizer = MetaPrefix + "finalizer" -const OwnerTenant = MetaPrefix + "owner-tenant" +const OwnerTenant = MetaPrefix + "tenant" -const OwnerAppNamespace = MetaPrefix + "owner-app-namespace" +const OwnerAppNamespace = MetaPrefix + "owner-namespace" const FieldManager = MetaPrefix + "neco-tenant-controller" const StatusFieldManager = FieldManager + "/status" From 346dcc07195d8b0148a83a3ec8474c51b3662805 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 14:43:09 +0900 Subject: [PATCH 30/89] Add helm chart Signed-off-by: zoetrope --- Makefile | 13 + charts/neco-tenant-controller/.helmignore | 23 + charts/neco-tenant-controller/Chart.yaml | 24 ++ .../neco-tenant-controller/crds/tenant.yaml | 161 +++++++ .../templates/NOTES.txt | 0 .../templates/_helpers.tpl | 62 +++ .../templates/certificate.yaml | 15 + .../templates/configmap.yaml | 20 + .../templates/deployment.yaml | 85 ++++ .../templates/generated.yaml | 398 ++++++++++++++++++ .../templates/issuer.yaml | 9 + charts/neco-tenant-controller/values.yaml | 77 ++++ config/crd/kustomization.yaml | 5 +- config/crd/patches/fix-crd.yaml | 6 + config/helm/crds/kustomization.yaml | 5 + config/helm/templates/kustomization.yaml | 16 + config/helm/templates/label-transformer.yaml | 12 + .../templates/webhookcainjection_patch.yaml | 13 + config/manager/manager.yaml | 93 ++-- e2e/Makefile | 4 +- 20 files changed, 995 insertions(+), 46 deletions(-) create mode 100644 charts/neco-tenant-controller/.helmignore create mode 100644 charts/neco-tenant-controller/Chart.yaml create mode 100644 charts/neco-tenant-controller/crds/tenant.yaml create mode 100644 charts/neco-tenant-controller/templates/NOTES.txt create mode 100644 charts/neco-tenant-controller/templates/_helpers.tpl create mode 100644 charts/neco-tenant-controller/templates/certificate.yaml create mode 100644 charts/neco-tenant-controller/templates/configmap.yaml create mode 100644 charts/neco-tenant-controller/templates/deployment.yaml create mode 100644 charts/neco-tenant-controller/templates/generated.yaml create mode 100644 charts/neco-tenant-controller/templates/issuer.yaml create mode 100644 charts/neco-tenant-controller/values.yaml create mode 100644 config/crd/patches/fix-crd.yaml create mode 100644 config/helm/crds/kustomization.yaml create mode 100644 config/helm/templates/kustomization.yaml create mode 100644 config/helm/templates/label-transformer.yaml create mode 100644 config/helm/templates/webhookcainjection_patch.yaml diff --git a/Makefile b/Makefile index 8c7a8af..0050e4a 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ CTRL_TOOLS_VERSION=0.7.0 CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) KUSTOMIZE_VERSION = 4.4.1 +HELM_VERSION = 3.7.1 CRD_TO_MARKDOWN_VERSION = 0.0.3 TILT_VERSION = 0.23.4 CTLPTL_VERSION = 0.6.2 @@ -49,6 +50,9 @@ help: ## Display this help. .PHONY: manifests manifests: kustomize controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(KUSTOMIZE) build config/helm/crds > charts/neco-tenant-controller/crds/tenant.yaml + $(KUSTOMIZE) build config/helm/templates > charts/neco-tenant-controller/templates/generated.yaml + .PHONY: generate generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. @@ -145,6 +149,15 @@ $(KUSTOMIZE): curl -fsL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv$(KUSTOMIZE_VERSION)/kustomize_v$(KUSTOMIZE_VERSION)_linux_amd64.tar.gz | \ tar -C bin -xzf - +HELM := $(shell pwd)/bin/helm +.PHONY: helm +helm: $(HELM) ## Download helm locally if necessary. + +$(HELM): + mkdir -p $(BIN_DIR) + curl -L -sS https://get.helm.sh/helm-v$(HELM_VERSION)-linux-amd64.tar.gz \ + | tar xz -C $(BIN_DIR) --strip-components 1 linux-amd64/helm + CRD_TO_MARKDOWN := $(shell pwd)/bin/crd-to-markdown .PHONY: crd-to-markdown crd-to-markdown: ## Download crd-to-markdown locally if necessary. diff --git a/charts/neco-tenant-controller/.helmignore b/charts/neco-tenant-controller/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/charts/neco-tenant-controller/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/charts/neco-tenant-controller/Chart.yaml b/charts/neco-tenant-controller/Chart.yaml new file mode 100644 index 0000000..fc0b06f --- /dev/null +++ b/charts/neco-tenant-controller/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: neco-tenant-controller +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: 0.1.0 diff --git a/charts/neco-tenant-controller/crds/tenant.yaml b/charts/neco-tenant-controller/crds/tenant.yaml new file mode 100644 index 0000000..cfa5d40 --- /dev/null +++ b/charts/neco-tenant-controller/crds/tenant.yaml @@ -0,0 +1,161 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.7.0 + labels: + app.kubernetes.io/name: neco-tenant-controller + name: tenants.multi-tenancy.cybozu.com +spec: + group: multi-tenancy.cybozu.com + names: + kind: Tenant + listKind: TenantList + plural: tenants + singular: tenant + scope: Cluster + versions: + - additionalPrinterColumns: + - jsonPath: .status.conditions[?(@.type=='Ready')].status + name: READY + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants 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: TenantSpec defines the desired state of Tenant + properties: + argocd: + description: ArgoCDSpec defines the desired state of the settings + for Argo CD + properties: + extraAdmins: + description: ExtraAdmins are the names of the team to add to the + AppProject user. Specify this if you want other tenant teams + to be able to use your AppProject. + items: + type: string + type: array + type: object + namespaces: + items: + description: NamespaceSpec defines the desired state of Namespace + properties: + annotations: + additionalProperties: + type: string + description: Annotations are the annotations to add to the namespace + type: object + extraAdmins: + description: ExtraAdmins are the names of the team to add to + the namespace administrator. Specify this if you want other + tenant teams to be able to use your namespace. + items: + type: string + type: array + labels: + additionalProperties: + type: string + description: Labels are the labels to add to the namespace + type: object + name: + description: Name is the name of namespace to be generated + type: string + required: + - name + type: object + type: array + type: object + status: + description: TenantStatus defines the observed state of Tenant + properties: + conditions: + description: Conditions is an array of conditions. + items: + description: "Condition contains details for one aspect of the current + state of this API Resource. --- This struct is intended for direct + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition + transitioned from one status to another. This should be when + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating + details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation + that the condition was set based upon. For instance, if .metadata.generation + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + --- Many .condition.type values are consistent across resources + like Available, but because arbitrary conditions can be useful + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/neco-tenant-controller/templates/NOTES.txt b/charts/neco-tenant-controller/templates/NOTES.txt new file mode 100644 index 0000000..e69de29 diff --git a/charts/neco-tenant-controller/templates/_helpers.tpl b/charts/neco-tenant-controller/templates/_helpers.tpl new file mode 100644 index 0000000..2ff18eb --- /dev/null +++ b/charts/neco-tenant-controller/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "neco-tenant-controller.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "neco-tenant-controller.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "neco-tenant-controller.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "neco-tenant-controller.labels" -}} +helm.sh/chart: {{ include "neco-tenant-controller.chart" . }} +{{ include "neco-tenant-controller.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "neco-tenant-controller.selectorLabels" -}} +app.kubernetes.io/name: {{ include "neco-tenant-controller.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "neco-tenant-controller.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "neco-tenant-controller.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/charts/neco-tenant-controller/templates/certificate.yaml b/charts/neco-tenant-controller/templates/certificate.yaml new file mode 100644 index 0000000..84ee3e7 --- /dev/null +++ b/charts/neco-tenant-controller/templates/certificate.yaml @@ -0,0 +1,15 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ template "neco-tenant-controller.fullname" . }}-serving-cert + namespace: {{ .Release.Namespace }} + labels: + {{- include "neco-tenant-controller.labels" . | nindent 4 }} +spec: + dnsNames: + - {{ template "neco-tenant-controller.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc + - {{ template "neco-tenant-controller.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc.cluster.local + issuerRef: + kind: Issuer + name: {{ template "neco-tenant-controller.fullname" . }}-selfsigned-issuer + secretName: webhook-server-cert diff --git a/charts/neco-tenant-controller/templates/configmap.yaml b/charts/neco-tenant-controller/templates/configmap.yaml new file mode 100644 index 0000000..95daa32 --- /dev/null +++ b/charts/neco-tenant-controller/templates/configmap.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "neco-tenant-controller.fullname" . }}-config + namespace: {{ .Release.Namespace }} + labels: + {{- include "neco-tenant-controller.labels" . | nindent 4 }} +data: + config.yaml: | + namespace: + {{- with .Values.controller.config.namespace.commonLabels }} + commonLabels: {{ toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.controller.config.namespace.commonAnntations }} + commonAnnotations: {{ toYaml . | nindent 8 }} + {{- end }} + rolebindingTemplate: {{ required ".Values.controller.config.namespace.rolebindingTemplate required!" .Values.controller.config.namespace.rolebindingTemplate | toYaml | nindent 8 }} + argocd: + namespace: {{ required ".Values.controller.config.argocd.namespace required!" .Values.controller.config.argocd.namespace }} + appProjectTemplate: {{ required ".Values.controller.config.argocd.appProjectTemplate required!" .Values.controller.config.argocd.appProjectTemplate | toYaml | nindent 8 }} diff --git a/charts/neco-tenant-controller/templates/deployment.yaml b/charts/neco-tenant-controller/templates/deployment.yaml new file mode 100644 index 0000000..3f0a991 --- /dev/null +++ b/charts/neco-tenant-controller/templates/deployment.yaml @@ -0,0 +1,85 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "neco-tenant-controller.fullname" . }}-controller-manager + namespace: {{ .Release.Namespace }} + labels: + app.kubernetes.io/component: controller + {{- include "neco-tenant-controller.labels" . | nindent 4 }} +spec: + replicas: {{ .Values.controller.replicas }} + selector: + matchLabels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: {{ include "neco-tenant-controller.name" . }} + template: + metadata: + annotations: + kubectl.kubernetes.io/default-container: manager + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + labels: + app.kubernetes.io/component: controller + app.kubernetes.io/name: {{ include "neco-tenant-controller.name" . }} + spec: + containers: + - name: manager + image: "{{ .Values.image.repository }}:{{ default .Chart.AppVersion .Values.image.tag }}" + {{- with .Values.image.pullPolicy }} + imagePullPolicy: {{ . }} + {{- end }} + command: + - /neco-tenant-controller + {{- with .Values.controller.extraArgs }} + args: {{ . }} + {{- end }} + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + - containerPort: 8081 + name: health + protocol: TCP + - containerPort: 8080 + name: metrics + protocol: TCP + {{- with .Values.controller.resources }} + resources: {{ toYaml . | nindent 12 }} + {{- end }} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + livenessProbe: + httpGet: + path: /healthz + port: health + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: health + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - mountPath: /tmp/k8s-webhook-server/serving-certs + name: cert + readOnly: true + - mountPath: /etc/neco-tenant-controller + name: config + securityContext: + runAsNonRoot: true + serviceAccountName: {{ template "neco-tenant-controller.fullname" . }}-controller-manager + terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} + volumes: + - name: cert + secret: + defaultMode: 420 + secretName: webhook-server-cert + - configMap: + name: {{ template "neco-tenant-controller.fullname" . }}-config + name: config diff --git a/charts/neco-tenant-controller/templates/generated.yaml b/charts/neco-tenant-controller/templates/generated.yaml new file mode 100644 index 0000000..30c2571 --- /dev/null +++ b/charts/neco-tenant-controller/templates/generated.yaml @@ -0,0 +1,398 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-leader-election-role' + namespace: '{{ .Release.Namespace }}' +rules: +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + creationTimestamp: null + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-manager-role' +rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update +- apiGroups: + - argoproj.io + resources: + - applications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - argoproj.io + resources: + - appprojects + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants/finalizers + verbs: + - update +- apiGroups: + - multi-tenancy.cybozu.com + resources: + - tenants/status + verbs: + - get + - patch + - update +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - bind + - escalate + - get + - list + - watch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-metrics-reader' +rules: +- nonResourceURLs: + - /metrics + verbs: + - get +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-proxy-role' +rules: +- apiGroups: + - authentication.k8s.io + resources: + - tokenreviews + verbs: + - create +- apiGroups: + - authorization.k8s.io + resources: + - subjectaccessreviews + verbs: + - create +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-leader-election-rolebinding' + namespace: '{{ .Release.Namespace }}' +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: '{{ template "neco-tenant-controller.fullname" . }}-leader-election-role' +subjects: +- kind: ServiceAccount + name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-manager-rolebinding' +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ template "neco-tenant-controller.fullname" . }}-manager-role' +subjects: +- kind: ServiceAccount + name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-proxy-rolebinding' +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: '{{ template "neco-tenant-controller.fullname" . }}-proxy-role' +subjects: +- kind: ServiceAccount + name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + control-plane: controller-manager + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager-metrics-service' + namespace: '{{ .Release.Namespace }}' +spec: + ports: + - name: https + port: 8443 + protocol: TCP + targetPort: https + selector: + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + control-plane: controller-manager +--- +apiVersion: v1 +kind: Service +metadata: + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' +spec: + ports: + - port: 443 + protocol: TCP + targetPort: 9443 + selector: + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + control-plane: controller-manager +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" + . }}-serving-cert' + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-mutating-webhook-configuration' +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /mutate-argoproj-io-application + failurePolicy: Fail + name: mapplication.kb.io + rules: + - apiGroups: + - argoproj.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - applications + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /mutate-multi-tenancy-cybozu-com-v1beta1-tenant + failurePolicy: Fail + name: mtenant.kb.io + rules: + - apiGroups: + - multi-tenancy.cybozu.com + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - tenants + sideEffects: None +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" + . }}-serving-cert' + labels: + app.kubernetes.io/managed-by: '{{ .Release.Service }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + name: '{{ template "neco-tenant-controller.fullname" . }}-validating-webhook-configuration' +webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /validate-argoproj-io-application + failurePolicy: Fail + name: vapplication.kb.io + rules: + - apiGroups: + - argoproj.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - applications + sideEffects: None +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /validate-multi-tenancy-cybozu-com-v1beta1-tenant + failurePolicy: Fail + name: vtenant.kb.io + rules: + - apiGroups: + - multi-tenancy.cybozu.com + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - tenants + sideEffects: None diff --git a/charts/neco-tenant-controller/templates/issuer.yaml b/charts/neco-tenant-controller/templates/issuer.yaml new file mode 100644 index 0000000..f608c62 --- /dev/null +++ b/charts/neco-tenant-controller/templates/issuer.yaml @@ -0,0 +1,9 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ template "neco-tenant-controller.fullname" . }}-selfsigned-issuer + namespace: {{ .Release.Namespace }} + labels: + {{- include "neco-tenant-controller.labels" . | nindent 4 }} +spec: + selfSigned: {} diff --git a/charts/neco-tenant-controller/values.yaml b/charts/neco-tenant-controller/values.yaml new file mode 100644 index 0000000..6fa4ec1 --- /dev/null +++ b/charts/neco-tenant-controller/values.yaml @@ -0,0 +1,77 @@ +image: + # image.repository -- neco-tenant-controller image repository to use. + repository: ghcr.io/cybozu-go/neco-tenant-controller + + # image.tag -- neco-tenant-controller image tag to use. + # @default -- `{{ .Chart.AppVersion }}` + tag: # 0.1.0 + + # image.pullPolicy -- neco-tenant-controller image pullPolicy. + pullPolicy: # Always + +controller: + # controller.replicas -- Specify the number of replicas of the controller Pod. + replicas: 1 + + # controller.resources -- Specify resources. + resources: + requests: + cpu: 100m + memory: 64Mi + + # controller.terminationGracePeriodSeconds -- Specify terminationGracePeriodSeconds. + terminationGracePeriodSeconds: 10 + + # controller.extraArgs -- Optional additional arguments. + extraArgs: [] + + config: + # controller.config.namespace -- + namespace: + commonLabels: + accurate.cybozu.com/template: init-template + rolebindingTemplate: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ .Name }} + {{ range .ExtraAdmins }} + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ . }} + {{ end }} + argocd: + namespace: argocd + appProjectTemplate: | + apiVersion: argoproj.io/v1alpha1 + kind: AppProject + spec: + destinations: + {{ range .Namespaces }} + - namespace: {{ . }} + server: '*' + {{ end }} + namespaceResourceBlacklist: + - group: "" + kind: ResourceQuota + - group: "" + kind: LimitRange + orphanedResources: + warn: false + roles: + - groups: + - cybozu-go:{{ .Name }} + {{ range .ExtraAdmins }} + - cybozu-go:{{ . }} + {{ end }} + name: admin + policies: + - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow + sourceRepos: + - '*' diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 432fc33..70bdaa4 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -2,10 +2,11 @@ # since it depends on service name and namespace that are out of this kustomize package. # It should be run by config/default resources: -- bases/multi-tenancy.cybozu.com_tenants.yaml + - bases/multi-tenancy.cybozu.com_tenants.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: + - patches/fix-crd.yaml # [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_tenants.yaml @@ -18,4 +19,4 @@ patchesStrategicMerge: # the following config is for teaching kustomize how to do kustomization for CRDs. configurations: -- kustomizeconfig.yaml + - kustomizeconfig.yaml diff --git a/config/crd/patches/fix-crd.yaml b/config/crd/patches/fix-crd.yaml new file mode 100644 index 0000000..8a4cf6a --- /dev/null +++ b/config/crd/patches/fix-crd.yaml @@ -0,0 +1,6 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: tenants.multi-tenancy.cybozu.com + creationTimestamp: null +status: null diff --git a/config/helm/crds/kustomization.yaml b/config/helm/crds/kustomization.yaml new file mode 100644 index 0000000..9bf0b86 --- /dev/null +++ b/config/helm/crds/kustomization.yaml @@ -0,0 +1,5 @@ +resources: + - ../../crd + +commonLabels: + app.kubernetes.io/name: neco-tenant-controller diff --git a/config/helm/templates/kustomization.yaml b/config/helm/templates/kustomization.yaml new file mode 100644 index 0000000..38821f6 --- /dev/null +++ b/config/helm/templates/kustomization.yaml @@ -0,0 +1,16 @@ +resources: + - ../../rbac + - ../../webhook + +namespace: '{{ .Release.Namespace }}' + +namePrefix: '{{ template "neco-tenant-controller.fullname" . }}-' + +commonLabels: + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + +patchesStrategicMerge: + - webhookcainjection_patch.yaml + +transformers: + - label-transformer.yaml diff --git a/config/helm/templates/label-transformer.yaml b/config/helm/templates/label-transformer.yaml new file mode 100644 index 0000000..fc38f63 --- /dev/null +++ b/config/helm/templates/label-transformer.yaml @@ -0,0 +1,12 @@ +apiVersion: builtin +kind: LabelTransformer +metadata: + name: helm-metadata-labels +labels: + helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' + app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/version: '{{ .Chart.AppVersion }}' + app.kubernetes.io/managed-by: '{{ .Release.Service }}' +fieldSpecs: + - path: metadata/labels + create: true diff --git a/config/helm/templates/webhookcainjection_patch.yaml b/config/helm/templates/webhookcainjection_patch.yaml new file mode 100644 index 0000000..3aa40c3 --- /dev/null +++ b/config/helm/templates/webhookcainjection_patch.yaml @@ -0,0 +1,13 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: mutating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" . }}-serving-cert' +--- +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: validating-webhook-configuration + annotations: + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" . }}-serving-cert' diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 10d3e5f..0fd5e1b 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -11,58 +11,69 @@ metadata: name: controller-manager namespace: system labels: - control-plane: controller-manager + app.kubernetes.io/component: controller spec: + replicas: 1 selector: matchLabels: - control-plane: controller-manager - replicas: 1 + app.kubernetes.io/component: controller template: metadata: annotations: kubectl.kubernetes.io/default-container: manager labels: - control-plane: controller-manager + app.kubernetes.io/component: controller spec: + containers: + - name: manager + image: neco-tenant-controller:dev + imagePullPolicy: IfNotPresent + command: + - /neco-tenant-controller + env: + - name: POD_NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + ports: + - containerPort: 9443 + name: webhook-server + protocol: TCP + - containerPort: 8081 + name: health + protocol: TCP + - containerPort: 8080 + name: metrics + protocol: TCP + # TODO(user): Configure the resources accordingly based on the project requirements. + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ + resources: + limits: + cpu: 500m + memory: 128Mi + requests: + cpu: 10m + memory: 64Mi + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + livenessProbe: + httpGet: + path: /healthz + port: health + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: health + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - mountPath: /etc/neco-tenant-controller + name: config securityContext: runAsNonRoot: true - containers: - - command: - - /neco-tenant-controller - image: neco-tenant-controller:dev - imagePullPolicy: IfNotPresent - env: - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace - 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 - # TODO(user): Configure the resources accordingly based on the project requirements. - # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ - resources: - limits: - cpu: 500m - memory: 128Mi - requests: - cpu: 10m - memory: 64Mi - volumeMounts: - - mountPath: /etc/neco-tenant-controller - name: config serviceAccountName: controller-manager terminationGracePeriodSeconds: 10 volumes: diff --git a/e2e/Makefile b/e2e/Makefile index a32f7ab..b7680f3 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -72,9 +72,7 @@ $(KUBECTL): chmod a+x $@ $(HELM): - mkdir -p $(BIN_DIR) - curl -L -sS https://get.helm.sh/helm-v$(HELM_VERSION)-linux-amd64.tar.gz \ - | tar xz -C $(BIN_DIR) --strip-components 1 linux-amd64/helm + $(MAKE) -C .. helm .PHONY: argocd argocd: $(ARGOCD) From 592e70649757dc3c7f5c5e670177971dfb25ce49 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 16:39:45 +0900 Subject: [PATCH 31/89] Update docs Signed-off-by: zoetrope --- README.md | 17 +++++++++++++---- docs/SUMMARY.md | 13 ++++++++++++- docs/book.toml | 8 ++++---- docs/design.md | 34 +++++++++++++++++++++++++--------- 4 files changed, 54 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 7769091..2690833 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,24 @@ -[![GitHub release](https://img.shields.io/github/release/cybozu-go/neco-tenant-controller.svg?maxAge=60)][releases] +[![GitHub release](https://img.shields.io/github/release/cybozu-go/neco-tenant-controller.svg?maxAge=60)](https://github.com/cybozu-go/neco-tenant-controller/releases) [![CI](https://github.com/cybozu-go/neco-tenant-controller/actions/workflows/ci.yaml/badge.svg)](https://github.com/cybozu-go/neco-tenant-controller/actions/workflows/ci.yaml) [![PkgGoDev](https://pkg.go.dev/badge/github.com/cybozu-go/neco-tenant-controller?tab=overview)](https://pkg.go.dev/github.com/cybozu-go/neco-tenant-controller?tab=overview) [![Go Report Card](https://goreportcard.com/badge/github.com/cybozu-go/neco-tenant-controller)](https://goreportcard.com/report/github.com/cybozu-go/neco-tenant-controller) -Tenant Controller for Neco -========================== +# Tenant Controller for Neco + +neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. **Project Status**: Initial development +## Features + +- Management of root-namespaces for tenants. Tenant users will be able to create sub-namespaces in those root-namespaces. +- When a tenant user creates a sub-namespace, the AppProject will be automatically updated accordingly. Tenant users will be able to deploy applications with Argo CD to the namespaces. +- The ownership of sub-namespaces can be changed between tenants. +- Tenant users can create Application resources in their sub-namespaces without `argocd` command. It allows for [App Of Apps Pattern](https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/#app-of-apps-pattern) in multi-tenancy environments. + ## Documentation [docs](docs/) directory contains documents about designs and specifications. -[releases]: https://github.com/cybozu-go/neco-tenant-controller/releases +[Accurate]: https://github.com/cybozu-go/accurate +[Argo CD]: https://argo-cd.readthedocs.io/en/stable/ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index fa24aff..5def7e0 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,9 +1,20 @@ # Summary -[Template](README.md) +[neco-tenant-controller](README.md) # User manual +- [Overview](overview.md) +- [Getting started](getting_started.md) +- [Usage](usage.md) + +# References + +- [Tenant custom resource](crd_tenant.md) +- [Configurations](config.md) + # Developer documents +- [Design notes](design.md) +- [Development](development.md) - [Release procedure](release.md) diff --git a/docs/book.toml b/docs/book.toml index 81e19c4..a34d3bf 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -2,9 +2,9 @@ language = "en" multilingual = false src = "." -title = "Template Documentation" +title = "neco-tenant-controller Documentation" [output.html] -git-repository-url = "https://github.com/cybozu-go/neco-template" -edit-url-template = "https://github.com/cybozu-go/neco-template/edit/main/docs/{path}" -site-url = "/neco-template/" +git-repository-url = "https://github.com/cybozu-go/neco-tenant-controller" +edit-url-template = "https://github.com/cybozu-go/neco-tenant-controller/edit/main/docs/{path}" +site-url = "/neco-tenant-controller/" diff --git a/docs/design.md b/docs/design.md index 75eba7b..2139a91 100644 --- a/docs/design.md +++ b/docs/design.md @@ -2,42 +2,58 @@ ## Overview -`neco-tenant-controller` is a custom controller that uses [Accurate][] and [Argo CD][] to provide multi-tenant environment for Kubernetes cluster. +neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. ## Motivation -We have developed the following mechanism for multi-tenant [Argo CD][]. +Argo CD has a problem that it is difficult to implement app-of-apps pattern in a multi-tenancy environment. + +https://github.com/argoproj/argo-cd/issues/2785 + +We have developed the following mechanism to resolve the problem. https://blog.kintone.io/entry/production-grade-delivery-workflow-using-argocd#Multi-tenancy -However, the above mechanism has the following problems: +However, the mechanism has the following problems: - Tenant users cannot create app-of-apps Application resources. They need to ask an administrator for that. - Application resources are not strictly validated. Tenant users can specify Project for other tenants, and can also specify duplicate names. -- When a SubNamespace is created in [HNC][] or [Accurate][], an administrator needs to add it to the destinations of Application resources. +- When a SubNamespace is created in [HNC][] or [Accurate][], an administrator needs to add it to the destinations of the Application resource. (Argo CD supports specifying wildcards in destinations, but that is not enough for us.) +We are hoping for a better solution. + ## Goals -- Develop a custom controller to automate the configuration for multi-tenancy. -- Automates the creation of root-namespaces and AppProject for administrators. +- Develop a Kubernetes custom controller to automate the configuration for multi-tenancy of Argo CD with Accurate. +- Automates the creation of root-namespaces and AppProject for each tenant. - Allow tenant users to create Application resources in any namespace. - Perform strict validation of Application resources for security. ## User stories -### Adding a team +### Adding a tenant -Administrators only need to create one custom resource to add a team to a Kubernetes cluster. +An administrator only need to create one custom resource to add a tenant to a Kubernetes cluster. No more manual operations to add Namespaces and Applications. +Tenant users can create sub-namespaces within their tenant. + ### Adding an app-of-apps Application +Tenant users can create Application resources within their sub-namespaces. +Application resources are strictly validated. +No more deploying to another tenant's namespace by mistake. + ### Changing ownership +There are cases where you want to move ownership of an application between tenants. +Accurate supports `kubectl accurate sub move` command to change the parent of a sub-namespace. -## Upgrade Strategy +https://cybozu-go.github.io/accurate/subnamespaces.html#changing-the-parent-of-a-sub-namespace +An administrators can use this command to move the sub-namespace to another tenant. +The permission of AppProjects, Applications and Namespaces will be updated automatically. ## Alternatives From 0dc79c238a47d80d72e4a4a07a4410b233f28447 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 17:00:55 +0900 Subject: [PATCH 32/89] Fix CRD Signed-off-by: zoetrope --- Makefile | 2 +- charts/neco-tenant-controller/Chart.yaml | 2 +- .../neco-tenant-controller/crds/tenant.yaml | 31 +++------- .../multi-tenancy.cybozu.com_tenants.yaml | 31 +++------- config/multi-tenancy.cybozu.com_tenants.yaml | 57 ------------------- e2e/e2e_test.go | 20 +++++-- 6 files changed, 34 insertions(+), 109 deletions(-) delete mode 100644 config/multi-tenancy.cybozu.com_tenants.yaml diff --git a/Makefile b/Makefile index 0050e4a..a2d9703 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ help: ## Display this help. .PHONY: manifests manifests: kustomize controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases + $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases $(KUSTOMIZE) build config/helm/crds > charts/neco-tenant-controller/crds/tenant.yaml $(KUSTOMIZE) build config/helm/templates > charts/neco-tenant-controller/templates/generated.yaml diff --git a/charts/neco-tenant-controller/Chart.yaml b/charts/neco-tenant-controller/Chart.yaml index fc0b06f..f68207f 100644 --- a/charts/neco-tenant-controller/Chart.yaml +++ b/charts/neco-tenant-controller/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 name: neco-tenant-controller -description: A Helm chart for Kubernetes +description: A Helm chart for neco-tenant-controller # A chart can be either an 'application' or a 'library' chart. # diff --git a/charts/neco-tenant-controller/crds/tenant.yaml b/charts/neco-tenant-controller/crds/tenant.yaml index cfa5d40..49dd090 100644 --- a/charts/neco-tenant-controller/crds/tenant.yaml +++ b/charts/neco-tenant-controller/crds/tenant.yaml @@ -27,12 +27,12 @@ spec: 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' + internal value, and may reject unrecognized values. More info: https://git.k8s.' 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' + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.' type: string metadata: type: object @@ -86,22 +86,14 @@ spec: conditions: description: Conditions is an array of conditions. items: - description: "Condition contains details for one aspect of the current + description: Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + use as an array at the field path .status.conditions. properties: lastTransitionTime: description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. + the underlying condition changed. format: date-time type: string message: @@ -112,19 +104,13 @@ spec: observedGeneration: description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. + is currently 12, but the .status.conditions[x]. format: int64 minimum: 0 type: integer reason: description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. + the reason for the condition's last transition. maxLength: 1024 minLength: 1 pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ @@ -140,8 +126,7 @@ spec: description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + (see .node.status. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index ad72ef9..fcc4c49 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -28,12 +28,12 @@ spec: 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' + internal value, and may reject unrecognized values. More info: https://git.k8s.' 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' + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.' type: string metadata: type: object @@ -87,22 +87,14 @@ spec: conditions: description: Conditions is an array of conditions. items: - description: "Condition contains details for one aspect of the current + description: Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + use as an array at the field path .status.conditions. properties: lastTransitionTime: description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. + the underlying condition changed. format: date-time type: string message: @@ -113,19 +105,13 @@ spec: observedGeneration: description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. + is currently 12, but the .status.conditions[x]. format: int64 minimum: 0 type: integer reason: description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. + the reason for the condition's last transition. maxLength: 1024 minLength: 1 pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ @@ -141,8 +127,7 @@ spec: description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + (see .node.status. maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/multi-tenancy.cybozu.com_tenants.yaml b/config/multi-tenancy.cybozu.com_tenants.yaml deleted file mode 100644 index f688586..0000000 --- a/config/multi-tenancy.cybozu.com_tenants.yaml +++ /dev/null @@ -1,57 +0,0 @@ - ---- -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition -metadata: - annotations: - controller-gen.kubebuilder.io/version: v0.7.0 - creationTimestamp: null - name: tenants.multi-tenancy.cybozu.com -spec: - group: multi-tenancy.cybozu.com - names: - kind: Tenant - listKind: TenantList - plural: tenants - singular: tenant - scope: Namespaced - versions: - - name: v1beta1 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants 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.' - 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.' - type: string - metadata: - type: object - spec: - description: TenantSpec defines the desired state of Tenant - properties: - foo: - description: Foo is an example field of Tenant. Edit tenant_types.go - to remove/update - type: string - type: object - status: - description: TenantStatus defines the observed state of Tenant - type: object - type: object - served: true - storage: true - subresources: - status: {} -status: - acceptedNames: - kind: "" - plural: "" - conditions: [] - storedVersions: [] diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index ccc3379..5fd8b9f 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -13,10 +13,22 @@ import ( var _ = Describe("neco-tenant-controller", func() { It("should prepare", func() { - kubectlSafe(nil, "apply", "-f", "../config/samples/00_template.yaml") - kubectlSafe(nil, "apply", "-f", "../config/samples/01_tenant.yaml") - kubectlSafe(nil, "apply", "-f", "../config/samples/02_subnamespace.yaml") - kubectlSafe(nil, "apply", "-f", "../config/samples/03_application.yaml") + Eventually(func() error { + _, err := kubectl(nil, "apply", "-f", "../config/samples/00_template.yaml") + return err + }).Should(Succeed()) + Eventually(func() error { + _, err := kubectl(nil, "apply", "-f", "../config/samples/01_tenant.yaml") + return err + }).Should(Succeed()) + Eventually(func() error { + _, err := kubectl(nil, "apply", "-f", "../config/samples/02_subnamespace.yaml") + return err + }).Should(Succeed()) + Eventually(func() error { + _, err := kubectl(nil, "apply", "-f", "../config/samples/03_application.yaml") + return err + }).Should(Succeed()) }) It("should sync application", func() { From d08828ef32b32234619f205f691fbc4583b452d7 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 23:55:01 +0900 Subject: [PATCH 33/89] Fix rolebindingTemplate Signed-off-by: zoetrope --- charts/neco-tenant-controller/templates/configmap.yaml | 2 +- charts/neco-tenant-controller/values.yaml | 2 +- config/manager/configmap.yaml | 2 +- controllers/tenant_controller_test.go | 8 ++++---- pkg/config/testdata/config.yaml | 2 +- pkg/config/testdata/invalid.yaml | 2 +- pkg/config/types.go | 6 +++--- 7 files changed, 12 insertions(+), 12 deletions(-) diff --git a/charts/neco-tenant-controller/templates/configmap.yaml b/charts/neco-tenant-controller/templates/configmap.yaml index 95daa32..0d826fc 100644 --- a/charts/neco-tenant-controller/templates/configmap.yaml +++ b/charts/neco-tenant-controller/templates/configmap.yaml @@ -14,7 +14,7 @@ data: {{- with .Values.controller.config.namespace.commonAnntations }} commonAnnotations: {{ toYaml . | nindent 8 }} {{- end }} - rolebindingTemplate: {{ required ".Values.controller.config.namespace.rolebindingTemplate required!" .Values.controller.config.namespace.rolebindingTemplate | toYaml | nindent 8 }} + roleBindingTemplate: {{ required ".Values.controller.config.namespace.roleBindingTemplate required!" .Values.controller.config.namespace.roleBindingTemplate | toYaml | nindent 8 }} argocd: namespace: {{ required ".Values.controller.config.argocd.namespace required!" .Values.controller.config.argocd.namespace }} appProjectTemplate: {{ required ".Values.controller.config.argocd.appProjectTemplate required!" .Values.controller.config.argocd.appProjectTemplate | toYaml | nindent 8 }} diff --git a/charts/neco-tenant-controller/values.yaml b/charts/neco-tenant-controller/values.yaml index 6fa4ec1..b6e63a8 100644 --- a/charts/neco-tenant-controller/values.yaml +++ b/charts/neco-tenant-controller/values.yaml @@ -30,7 +30,7 @@ controller: namespace: commonLabels: accurate.cybozu.com/template: init-template - rolebindingTemplate: | + roleBindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding roleRef: diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index 649fa5e..9684b44 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -8,7 +8,7 @@ data: namespace: commonLabels: accurate.cybozu.com/template: init-template - rolebindingTemplate: | + roleBindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding roleRef: diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index f37f2e8..0815b78 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -24,10 +24,10 @@ import ( ) //go:embed testdata/appprojecttemplate.yaml -var appprojectTemplate string +var appProjectTemplate string //go:embed testdata/rolebindingtemplate.yaml -var rolebindingTemplate string +var roleBindingTemplate string var _ = Describe("Tenant controller", func() { ctx := context.Background() @@ -51,11 +51,11 @@ var _ = Describe("Tenant controller", func() { CommonAnnotations: map[string]string{ "hoge": "fuga", }, - RoleBindingTemplate: rolebindingTemplate, + RoleBindingTemplate: roleBindingTemplate, }, ArgoCD: tenantconfig.ArgoCDConfig{ Namespace: "argocd", - AppProjectTemplate: appprojectTemplate, + AppProjectTemplate: appProjectTemplate, }, } tr := NewTenantReconciler(mgr.GetClient(), config) diff --git a/pkg/config/testdata/config.yaml b/pkg/config/testdata/config.yaml index 55b92a2..dbd1802 100644 --- a/pkg/config/testdata/config.yaml +++ b/pkg/config/testdata/config.yaml @@ -5,7 +5,7 @@ namespace: commonAnnotations: hoge: fuga c: d - rolebindingTemplate: | + roleBindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding argocd: diff --git a/pkg/config/testdata/invalid.yaml b/pkg/config/testdata/invalid.yaml index 51ecf47..374b0d8 100644 --- a/pkg/config/testdata/invalid.yaml +++ b/pkg/config/testdata/invalid.yaml @@ -5,7 +5,7 @@ namespace: commonAnnotations: hoge: fuga c: d - rolebindingTemplate: | + roleBindingTemplate: | apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding argocd: diff --git a/pkg/config/types.go b/pkg/config/types.go index 89c79d8..5370356 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -21,10 +21,10 @@ type NamespaceConfig struct { // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller CommonLabels map[string]string `json:"commonLabels,omitempty"` - // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller + // CommonAnnotations are annotations to add to all namespaces to be deployed by neco-tenant-controller CommonAnnotations map[string]string `json:"commonAnnotations,omitempty"` - RoleBindingTemplate string `json:"rolebindingTemplate"` + RoleBindingTemplate string `json:"roleBindingTemplate"` } // ArgoCDConfig represents the configuration about Argo CD @@ -43,7 +43,7 @@ func (c *Config) Validate() error { allErrs = append(allErrs, v1annotationvalidation.ValidateAnnotations(c.Namespace.CommonAnnotations, field.NewPath("namespace", "commonAnnotations"))...) if len(c.Namespace.RoleBindingTemplate) == 0 { - allErrs = append(allErrs, field.Invalid(field.NewPath("namespace", "rolebindingTemplate"), c.Namespace.RoleBindingTemplate, "should not be empty")) + allErrs = append(allErrs, field.Invalid(field.NewPath("namespace", "roleBindingTemplate"), c.Namespace.RoleBindingTemplate, "should not be empty")) } for _, msg := range validation.IsDNS1123Subdomain(c.ArgoCD.Namespace) { From 5333f11bc87f94bceb3dffe768a6fb87566df6d0 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 28 Dec 2021 00:19:24 +0900 Subject: [PATCH 34/89] Update docs Signed-off-by: zoetrope --- docs/config.md | 109 +++++++++++++++++++++++++++++++++++++++----- pkg/config/types.go | 8 ++-- 2 files changed, 102 insertions(+), 15 deletions(-) diff --git a/docs/config.md b/docs/config.md index 7ebed81..36dd4b3 100644 --- a/docs/config.md +++ b/docs/config.md @@ -2,31 +2,116 @@ ## Configuration file -`neco-tenant-controller` reads its configurations from a configuration file. +`neco-tenant-controller` reads a configuration file on startup. The default location is `/etc/neco-tenant-controller/config.yaml`. +The location can be changed with `--config-file` flag. + +The configuration file should be a JSON or YAML file having the following keys: + +| Key | Type | Description | +|---------------------------------|---------------------|--------------------------------------------------------------------------------------------| +| `namespace.commonLabels` | `map[string]string` | Labels to be added to all namespaces belonging to a tenant. | +| `namespace.commonAnnotations` | `map[string]string` | Annotations to be added to all namespaces belonging to a tenant. | +| `namespace.roleBindingTemplate` | `string` | Template for RoleBinding resource that is created on all namespaces belonging to a tenant. | +| `argocd.namepsace` | `string` | The name of namespace where Argo CD is running. | +| `argocd.appProjectTemplate` | `string` | Template for AppProject resources that is created for each tenant. | The repository includes an example as follows: ```yaml namespace: - # Labels to add to all namespaces to be deployed by neco-tenant-controller commonLabels: accurate.cybozu.com/template: init-template - + commonAnnotations: + foo: bar + roleBindingTemplate: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ .Name }} + {{ range .ExtraAdmins }} + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ . }} + {{ end }} argocd: - # The name of namespace where Argo CD is deployed namespace: argocd - appProjectTemplate: | apiVersion: argoproj.io/v1alpha1 kind: AppProject spec: - namespaceResourceBlacklist: - - group: "" - kind: ResourceQuota - - group: "" - kind: LimitRange - orphanedResources: - warn: false + destinations: + {{ range .Namespaces }} + - namespace: {{ . }} + server: '*' + {{ end }} + roles: + - groups: + - {{ .Name }} + {{ range .ExtraAdmins }} + - {{ . }} + {{ end }} + name: admin + policies: + - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow sourceRepos: - '*' ``` + +`roleBindingTemplate` and `appProjectTemplate` can be written in go-template format. + +`roleBindingTemplate` can use the following variables: + +| Key | Type | Description | +|---------------|------------|-------------------------------| +| `Name` | `string` | The name of a tenant. | +| `ExtraAdmins` | `[]string` | List of extra administrators. | + +`appProjectTemplate` can use the following variables: + +| Key | Type | Description | +|---------------|------------|----------------------------------------------------------------------| +| `Name` | `string` | The name of a tenant. | +| `ExtraAdmins` | `[]string` | List of extra administrators. | +| `Namespaces` | `[]string` | List of namespaces belonging to a tenant (including sub-namespaces). | + +## Environment variables + +| Name | Required | Description | +|-----------------|----------|---------------------------------------------------------------| +| `POD_NAMESPACE` | Yes | The namespace name where `neco-tenant-controller` is running. | + +## Command-line flags + +``` +Flags: + --add_dir_header If true, adds the file directory to the header + --alsologtostderr log to standard error as well as files + --cert-dir string webhook certificate directory + --config-file string Configuration file path (default "/etc/neco-tenant-controller/config.yaml") + --health-probe-addr string Listen address for health probes (default ":8081") + -h, --help help for neco-tenant-controller + --leader-election-id string ID for leader election by controller-runtime (default "neco-tenant-controller") + --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) + --log_dir string If non-empty, write log files in this directory + --log_file string If non-empty, use this log file + --log_file_max_size uint Defines the maximum size a log file can grow to. Unit is megabytes. If the value is 0, the maximum file size is unlimited. (default 1800) + --logtostderr log to standard error instead of files (default true) + --metrics-addr string The address the metric endpoint binds to (default ":8080") + --skip_headers If true, avoid header prefixes in the log messages + --skip_log_headers If true, avoid headers when opening log files + --stderrthreshold severity logs at or above this threshold go to stderr (default 2) + -v, --v Level number for the log level verbosity + --version version for neco-tenant-controller + --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging + --webhook-addr string Listen address for the webhook endpoint (default ":9443") + --zap-devel Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) + --zap-encoder encoder Zap log encoding (one of 'json' or 'console') + --zap-log-level level Zap Level to configure the verbosity of logging. Can be one of 'debug', 'info', 'error', or any integer value > 0 which corresponds to custom debug levels of increasing verbosity + --zap-stacktrace-level level Zap Level at and above which stacktraces are captured (one of 'info', 'error', 'panic'). +``` diff --git a/pkg/config/types.go b/pkg/config/types.go index 5370356..2d90fdc 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -18,20 +18,22 @@ type Config struct { // NamespaceConfig represents the configuration about Namespaces type NamespaceConfig struct { - // CommonLabels are labels to add to all namespaces to be deployed by neco-tenant-controller + // CommonLabels are labels to be added to all namespaces belonging to a tenant CommonLabels map[string]string `json:"commonLabels,omitempty"` - // CommonAnnotations are annotations to add to all namespaces to be deployed by neco-tenant-controller + // CommonAnnotations are annotations to be added to all namespaces belonging to a tenant CommonAnnotations map[string]string `json:"commonAnnotations,omitempty"` + // RoleBindingTemplate is a template for RoleBinding resource that is created on all namespaces belonging to a tenant RoleBindingTemplate string `json:"roleBindingTemplate"` } // ArgoCDConfig represents the configuration about Argo CD type ArgoCDConfig struct { - // Namespace is the name of namespace where Argo CD is deployed + // Namespace is the name of namespace where Argo CD is running Namespace string `json:"namespace"` + // AppProjectTemplate is a template for AppProject resources that is created for each tenant AppProjectTemplate string `json:"appProjectTemplate"` } From 1b33bb32673d828dc8ca64b364de9e6f7a3b0173 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 28 Dec 2021 00:28:03 +0900 Subject: [PATCH 35/89] Fix labels Signed-off-by: zoetrope --- charts/neco-tenant-controller/templates/generated.yaml | 5 ++--- config/manager/manager.yaml | 2 -- config/prometheus/monitor.yaml | 4 +--- config/rbac/auth_proxy_service.yaml | 4 +--- config/webhook/service.yaml | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/charts/neco-tenant-controller/templates/generated.yaml b/charts/neco-tenant-controller/templates/generated.yaml index 30c2571..ee34db5 100644 --- a/charts/neco-tenant-controller/templates/generated.yaml +++ b/charts/neco-tenant-controller/templates/generated.yaml @@ -256,7 +256,6 @@ metadata: app.kubernetes.io/managed-by: '{{ .Release.Service }}' app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - control-plane: controller-manager helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager-metrics-service' namespace: '{{ .Release.Namespace }}' @@ -267,8 +266,8 @@ spec: protocol: TCP targetPort: https selector: + app.kubernetes.io/component: controller app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' - control-plane: controller-manager --- apiVersion: v1 kind: Service @@ -286,8 +285,8 @@ spec: protocol: TCP targetPort: 9443 selector: + app.kubernetes.io/component: controller app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' - control-plane: controller-manager --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index 0fd5e1b..a6892a6 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -1,8 +1,6 @@ apiVersion: v1 kind: Namespace metadata: - labels: - control-plane: controller-manager name: system --- apiVersion: apps/v1 diff --git a/config/prometheus/monitor.yaml b/config/prometheus/monitor.yaml index d19136a..8b0df7f 100644 --- a/config/prometheus/monitor.yaml +++ b/config/prometheus/monitor.yaml @@ -3,8 +3,6 @@ apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: - labels: - control-plane: controller-manager name: controller-manager-metrics-monitor namespace: system spec: @@ -17,4 +15,4 @@ spec: insecureSkipVerify: true selector: matchLabels: - control-plane: controller-manager + app.kubernetes.io/component: controller diff --git a/config/rbac/auth_proxy_service.yaml b/config/rbac/auth_proxy_service.yaml index 71f1797..cef39f5 100644 --- a/config/rbac/auth_proxy_service.yaml +++ b/config/rbac/auth_proxy_service.yaml @@ -1,8 +1,6 @@ apiVersion: v1 kind: Service metadata: - labels: - control-plane: controller-manager name: controller-manager-metrics-service namespace: system spec: @@ -12,4 +10,4 @@ spec: protocol: TCP targetPort: https selector: - control-plane: controller-manager + app.kubernetes.io/component: controller diff --git a/config/webhook/service.yaml b/config/webhook/service.yaml index 3f638bd..436358d 100644 --- a/config/webhook/service.yaml +++ b/config/webhook/service.yaml @@ -10,4 +10,4 @@ spec: protocol: TCP targetPort: 9443 selector: - control-plane: controller-manager + app.kubernetes.io/component: controller From c598de625125860fc65a1563b7701ca14fdb3646 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 28 Dec 2021 19:21:35 +0900 Subject: [PATCH 36/89] Update docs Signed-off-by: zoetrope --- docs/README.md | 10 ++- docs/SUMMARY.md | 2 +- docs/development.md | 40 +++++++++--- docs/setup.md | 151 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 192 insertions(+), 11 deletions(-) create mode 100644 docs/setup.md diff --git a/docs/README.md b/docs/README.md index 04953d3..4f918cc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1 +1,9 @@ -# Template documentation +# neco-tenant-controller documentation + +neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. +It is currently developed and maintained by [Cybozu](https://cybozu-global.com/). + +The repository is at https://github.com/cybozu-go/neco-tenant-controller . + +[Accurate]: https://github.com/cybozu-go/accurate +[Argo CD]: https://argo-cd.readthedocs.io/en/stable/ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 5def7e0..6930e9f 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -5,7 +5,7 @@ # User manual - [Overview](overview.md) -- [Getting started](getting_started.md) +- [Setup](setup.md) - [Usage](usage.md) # References diff --git a/docs/development.md b/docs/development.md index dee1d7f..b50fb74 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,11 +1,33 @@ # Development -``` -$ make dev -$ make tilt -$ ./bin/tilt up -``` - -``` -$ make stop-dev -``` +neco-tenant-controller can be developed in Tilt. + +1. Prepare a Linux box running Docker. +2. Checkout this repository. + + ```console + $ git clone https://github.com/cybozu-go/neco-tenant-controller + ``` + +3. Launch local Kubernetes cluster. + + ```console + $ cd neco-tenant-controller + $ make dev + ``` + +4. Start Tilt. + + ```console + $ make tilt + $ ./bin/tilt up + ``` + +5. Access: http://localhost:10350/ +6. Stop the Kubernetes cluster. + + ```console + $ make stop-dev + ``` + +[Tilt]: https://tilt.dev diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 0000000..e5a6d80 --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,151 @@ +# Setup + +## Kubernetes cluster + +neco-tenant-controller is a controller that runs in a soft multi-tenancy Kubernetes cluster. +Namespaces must be isolated for each tenant. + +There are many ways to achieve Namespace isolation. +In EKS and GKE, you can integrate RBAC with IAM. +For on-premises, [Telepot](https://goteleport.com) and [Loft](https://loft.sh) may help you. + +## Argo CD + +Install Argo CD as shown in the following page: + +https://argo-cd.readthedocs.io/en/stable/getting_started/ + +neco-tenant-controller isolates AppProject resource for each tenant. + +So, please refer to the following page to enable user management. +Argo CD supports a lot of authentication methods. + +https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/ + +neco-tenant-controller expects tenant users to be able to create Application resources. +Apply the following manifest: + +```yaml +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: application-admin + labels: + rbac.authorization.k8s.io/aggregate-to-admin: "true" +rules: +- apiGroups: + - argoproj.io + resources: + - applications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - deletecollection +``` + +## cert-manager + +neco-tenant-controller and Accurate depend on [cert-manager][] to issue TLS certificate for admission webhooks. +If cert-manager is not installed on your cluster, install it as follows: + +```console +$ curl -fsLO https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml +$ kubectl apply -f cert-manager.yaml +``` + +## Accurate + +neco-tenant-controller depends on Accurate. +It expects `multi-tenancy.cybozu.com/tenant` labels and RoleBinding resources to be propagated. + +Include the following settings in your values.yaml: + +```yaml +controller: + config: + labelKeys: + - multi-tenancy.cybozu.com/tenant + watches: + - group: rbac.authorization.k8s.io + version: v1 + kind: RoleBinding +``` + +Install Accurate with the values.yaml as follows: + +```console +$ helm install --create-namespace --namespace accurate accurate -f values.yaml accurate/accurate +``` + +For more information, see the following page: + +https://cybozu-go.github.io/accurate/helm.html + +## neco-tenant-controller + +Prepare values.yaml as follows: + +```yaml +controller: + config: + namespace: + roleBindingTemplate: | + apiVersion: rbac.authorization.k8s.io/v1 + kind: RoleBinding + roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: admin + subjects: + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ .Name }} + {{ range .ExtraAdmins }} + - apiGroup: rbac.authorization.k8s.io + kind: Group + name: {{ . }} + {{ end }} + argocd: + namespace: argocd + appProjectTemplate: | + apiVersion: argoproj.io/v1alpha1 + kind: AppProject + spec: + destinations: + {{ range .Namespaces }} + - namespace: {{ . }} + server: '*' + {{ end }} + roles: + - groups: + - {{ .Name }} + {{ range .ExtraAdmins }} + - {{ . }} + {{ end }} + name: admin + policies: + - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow + sourceRepos: + - '*' +``` + +`appProjectTemplate` and `roleBindingTemplate` should be configured appropriately for your multi-tenancy environment. +Read [Configurations](config.md) for details. + +Setup Helm repository: + + ```console + $ helm repo add neco-tenant-controller https://cybozu-go.github.io/neco-tenant-controller/ + $ helm repo update + ``` + +Install the Helm chart with your values.yaml: + +```console +$ helm install --create-namespace --namespace neco-tenant-controller neco-tenant-controller neco-tenant-controller/neco-tenant-controller -f values.yaml +``` From 345301465acd3d602f3b606fdf86d481a10d9d73 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 28 Dec 2021 22:57:12 +0900 Subject: [PATCH 37/89] Workaround Signed-off-by: zoetrope --- controllers/application_controller.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 6f13cf4..0a00441 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -273,6 +273,24 @@ func (r *ApplicationReconciler) syncApplicationStatus(ctx context.Context, argoc newApp.SetName(tenantApp.GetName()) if argocdApp != nil && argocdApp.UnstructuredContent()["status"] != nil { newApp.UnstructuredContent()["status"] = argocdApp.DeepCopy().UnstructuredContent()["status"] + + // This is a workaround. + // When `status.summary` is empty, controller will fail to sync status. + // So, controller sets an empty slice as dummy data. + summary, found, err := unstructured.NestedFieldCopy(newApp.UnstructuredContent(), "status", "summary") + if err != nil { + return err + } + if found { + summaryMap, ok := summary.(map[string]interface{}) + if ok && len(summaryMap) == 0 { + logger.Info("status.summary is empty") + err = unstructured.SetNestedStringSlice(newApp.UnstructuredContent(), []string{""}, "status", "summary", "images") + if err != nil { + return err + } + } + } } managed, err := extract.ExtractManagedFields(tenantApp, constants.StatusFieldManager) From 6d6ab32cf67fa4007836c73ec2ebcb5fe767d5a0 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 29 Dec 2021 00:05:57 +0900 Subject: [PATCH 38/89] Update docs Signed-off-by: zoetrope --- docs/overview.md | 41 +++++++------- docs/usage.md | 142 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 20 deletions(-) create mode 100644 docs/usage.md diff --git a/docs/overview.md b/docs/overview.md index de537e7..665ecee 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,39 +1,40 @@ # Overview -neco-tenant-controller is a Kubernetes controller to manage tenant team resources. +neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. ## Features -### For Administrator +### For Administrators -Cluster administrator can creates a [Tenant custom resource](./crd_tenant.md) for each tenant team. -neco-tenant-controller will automatically apply the following resources needed by the tenant team based on the Tenant resource. +- Management of root-namespaces for tenants -- Manage root namespaces + When an administrator creates a [tenant resource](crd_tenant.md), root-namespaces for the tenant will be created. + A RoleBinding resource will be created in the namespace so that the namespace can only be accessed by the tenant's users. + Tenant users can create sub-namespaces in those root-namespaces. - Tenant users can use [Accurate][] to create a SubNamespace. - Because of that, users need a root namespace. - neco-tenant-controller creates multiple root namespaces for each tenant team. - Those namespaces are permission-controlled so that only the tenant users can access them. +- Automatic update of Argo CD AppProject resources -- Manage Argo CD AppProject resources + An AppProject resource can control namespaces where users can deploy manifests. + When a tenant user creates a sub-namespace, the AppProject will be automatically updated accordingly. + Tenant users will be able to deploy applications with Argo CD to the namespaces. - Tenant users need an AppProject resource to deploy their manifests with [Argo CD][Argo CD]. - The AppProject can control namespaces where tenant users can deploy manifests. - neco-tenant-controller dynamically rewrites the AppProject resource each time tenant users creates a SubNamespace. +- The ownership of sub-namespaces can be changed between tenants + Sometimes users may want to move the ownership of an application to another tenant. + When the parent of a sub-namespace is changed, neco-tenant-controller will automatically update the permissions. ### For Tenant Users -- Manage Argo CD Application resources +- Sync Argo CD Application resources - Tenant users need Application resources to deploy their manifests with Argo CD. - neco-tenant-controller creates Application resources for each tenant team. + Tenant users can create Application resources in their sub-namespaces without `argocd` command. + neco-tenant-controller will synchronize Application resource between the tenant namespace and argocd namespace. + It allows for [App Of Apps Pattern][] in multi-tenancy environments. - Validate Argo CD Application resources - Tenant users can specify any repository for an Application resource. - However, that's not appropriate from a security perspective. - Admission webhook will deny the creation of Application that contains an unauthorized repository. + Tenant users can only specify their own AppProject when creating an application resource. -[Teleport]: https://goteleport.com +[Accurate]: https://github.com/cybozu-go/accurate +[Argo CD]: https://argo-cd.readthedocs.io/en/stable/ +[App Of Apps Pattern]: https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/#app-of-apps-pattern diff --git a/docs/usage.md b/docs/usage.md new file mode 100644 index 0000000..2041882 --- /dev/null +++ b/docs/usage.md @@ -0,0 +1,142 @@ +# Usage + +## Create a Tenant resource + +Administrators can create the following tenant resource for a tenant team. + +```yaml +apiVersion: multi-tenancy.cybozu.com/v1beta1 +kind: Tenant +metadata: + name: your-team +spec: + namespaces: + - name: your-root +``` + +The name of the tenant resource must match the name of the group in Kubernetes and Argo CD. +The namespaces specified in `spec.namespaces` will be created automatically. + +```console +$ kubectl get ns your-root +NAME STATUS AGE +your-root Active 1m +``` + +RoleBinding and AppProject resource are also automatically created. + +```console +$ kubeclt get rolebinding -n your-root +NAME ROLE AGE +your-team-admin ClusterRole/admin 2m +``` + +```console +$ kubectl get appproject -n argocd your-team +NAME AGE +your-team 2m +``` + +## Create an Application resource + +Tenant users can create a SubNamespace on their namespaces. + +```console +$ kubectl accurate sub create your-sub your-root +``` + +Tenant users can create an Application resource in the sub-namespace. + +Prepare an Application resource as follows: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Application +metadata: + name: testhttpd + namespace: your-sub + finalizers: + - resources-finalizer.argocd.argoproj.io +spec: + project: your-team + source: + repoURL: https://github.com/cybozu-go/neco-tenant-controller.git + targetRevision: main + path: samples/testhttpd + destination: + server: https://kubernetes.default.svc + namespace: your-sub + syncPolicy: + automated: + prune: true + selfHeal: true +``` + +Apply the resource: + +```console +$ kubectl apply -f application.yaml +``` + +Make sure that the Application resource are synchronized. + +```console +$ kubectl get application -n your-sub +NAME SYNC STATUS HEALTH STATUS +testhttpd Synced Healthy +``` + +```console +$ kubectl get application -n argocd +NAME SYNC STATUS HEALTH STATUS +testhttpd Synced Healthy +``` + +Get the result of synchronization as events. + +```console +$ kubectl get events -n your-sub +LAST SEEN TYPE REASON OBJECT MESSAGE +45s Normal ApplicationSynced application/testhttpd Sync application spec succeeded +34s Normal StatusSynced application/testhttpd Sync application status succeeded +``` + +## Changing ownership + +The ownership of sub-namespace can be transferred to other tenant. + +Prepare a new tenant: + +```yaml +apiVersion: multi-tenancy.cybozu.com/v1beta1 +kind: Tenant +metadata: + name: new-team +spec: + namespaces: + - name: new-root +``` + +Use `kubectl accurate sub move` command to change the parent of your-sub namespace to new-root. + +```console +$ kubectl accurate sub move your-sub new-root +``` + +`spec.project` field will be updated. + +```console +$ kubectl get app -n your-sub testhttpd -o jsonpath="{.spec.project}" +new-team +``` + +## Remove resources + +When a tenant user delete an Application resource on the tenant's namespace, an Application resource on argocd namespace will be deleted as well. +If `resources-finalizer.argocd.argoproj.io` is annotated, resources deployed by the Application will be deleted. + +When an administrator deleted a tenant resource: +- Namespaces for the tenant will remain +- RoleBinding on the namespaces will be deleted +- Applications on the namespaces will be deleted +- AppProject for the tenant will be deleted From 0af8f14ea89e25abe780dee31a5da69f8b104b15 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 29 Dec 2021 00:06:10 +0900 Subject: [PATCH 39/89] Fix Signed-off-by: zoetrope --- Makefile | 2 +- .../neco-tenant-controller/crds/tenant.yaml | 31 ++++++++++++++----- cluster.yaml | 1 + .../multi-tenancy.cybozu.com_tenants.yaml | 31 ++++++++++++++----- config/dev/manager.yaml | 1 + config/samples/03_application.yaml | 6 ++-- 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index a2d9703..e73e194 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ SUDO = sudo SHELL = /bin/bash .SHELLFLAGS = -e -o pipefail -c -CRD_OPTIONS = "crd:crdVersions=v1,maxDescLen=220" +CRD_OPTIONS = "crd:crdVersions=v1" # for Go GOOS = $(shell go env GOOS) diff --git a/charts/neco-tenant-controller/crds/tenant.yaml b/charts/neco-tenant-controller/crds/tenant.yaml index 49dd090..cfa5d40 100644 --- a/charts/neco-tenant-controller/crds/tenant.yaml +++ b/charts/neco-tenant-controller/crds/tenant.yaml @@ -27,12 +27,12 @@ spec: 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.' + 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.' + 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 @@ -86,14 +86,22 @@ spec: conditions: description: Conditions is an array of conditions. items: - description: Condition contains details for one aspect of the current + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when - the underlying condition changed. + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. format: date-time type: string message: @@ -104,13 +112,19 @@ spec: observedGeneration: description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x]. + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. format: int64 minimum: 0 type: integer reason: description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. maxLength: 1024 minLength: 1 pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ @@ -126,7 +140,8 @@ spec: description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful - (see .node.status. + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/cluster.yaml b/cluster.yaml index 092400b..bf7cf70 100644 --- a/cluster.yaml +++ b/cluster.yaml @@ -7,4 +7,5 @@ apiVersion: ctlptl.dev/v1alpha1 kind: Cluster name: kind-dev product: kind +kubernetesVersion: v1.21.4 registry: ctlptl-registry diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index fcc4c49..ad72ef9 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -28,12 +28,12 @@ spec: 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.' + 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.' + 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 @@ -87,14 +87,22 @@ spec: conditions: description: Conditions is an array of conditions. items: - description: Condition contains details for one aspect of the current + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. + use as an array at the field path .status.conditions. For example, + type FooStatus struct{ // Represents the observations of a + foo's current state. // Known .status.conditions.type are: + \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type + \ // +patchStrategy=merge // +listType=map // +listMapKey=type + \ Conditions []metav1.Condition `json:\"conditions,omitempty\" + patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` + \n // other fields }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when - the underlying condition changed. + the underlying condition changed. If that is not known, then + using the time when the API field changed is acceptable. format: date-time type: string message: @@ -105,13 +113,19 @@ spec: observedGeneration: description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x]. + is currently 12, but the .status.conditions[x].observedGeneration + is 9, the condition is out of date with respect to the current + state of the instance. format: int64 minimum: 0 type: integer reason: description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. + the reason for the condition's last transition. Producers + of specific condition types may define expected values and + meanings for this field, and whether the values are considered + a guaranteed API. The value should be a CamelCase string. + This field may not be empty. maxLength: 1024 minLength: 1 pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ @@ -127,7 +141,8 @@ spec: description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful - (see .node.status. + (see .node.status.conditions), the ability to deconflict is + important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) maxLength: 316 pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ type: string diff --git a/config/dev/manager.yaml b/config/dev/manager.yaml index 3f890a2..cfd4a9d 100644 --- a/config/dev/manager.yaml +++ b/config/dev/manager.yaml @@ -12,3 +12,4 @@ spec: - /mneco-tenant-controller args: null name: manager + securityContext: null diff --git a/config/samples/03_application.yaml b/config/samples/03_application.yaml index b734913..c0c1234 100644 --- a/config/samples/03_application.yaml +++ b/config/samples/03_application.yaml @@ -10,9 +10,9 @@ metadata: spec: project: a-team source: - repoURL: https://github.com/neco-test/apps-sandbox.git - targetRevision: master - path: testhttpd + repoURL: https://github.com/cybozu-go/neco-tenant-controller.git + targetRevision: main + path: samples/argocd-config destination: server: https://kubernetes.default.svc namespace: sub-1 From 13f514304e8d05187d9afdf1e1a5972388ba32d2 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Sun, 26 Dec 2021 23:55:37 +0900 Subject: [PATCH 40/89] Reflect review comments. Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 10 ++++- api/v1beta1/zz_generated.deepcopy.go | 1 - .../neco-tenant-controller/crds/tenant.yaml | 8 +++- .../multi-tenancy.cybozu.com_tenants.yaml | 8 +++- controllers/application_controller.go | 2 + controllers/tenant_controller.go | 17 ++------ controllers/tenant_controller_test.go | 41 ++----------------- docs/design.md | 2 +- 8 files changed, 29 insertions(+), 60 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index a803224..80a4153 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -22,8 +22,14 @@ import ( // TenantSpec defines the desired state of Tenant type TenantSpec struct { - Namespaces []NamespaceSpec `json:"namespaces,omitempty"` - ArgoCD ArgoCDSpec `json:"argocd,omitempty"` + // Namespaces are the list of root namespaces that belong to this tenant + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinItems=1 + Namespaces []NamespaceSpec `json:"namespaces"` + + // ArgoCD is the settings of Argo CD for this tenant + // +optional + ArgoCD ArgoCDSpec `json:"argocd,omitempty"` } // NamespaceSpec defines the desired state of Namespace diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index ded9756..556e595 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,4 +1,3 @@ -//go:build !ignore_autogenerated // +build !ignore_autogenerated // Code generated by controller-gen. DO NOT EDIT. diff --git a/charts/neco-tenant-controller/crds/tenant.yaml b/charts/neco-tenant-controller/crds/tenant.yaml index cfa5d40..4a57cd6 100644 --- a/charts/neco-tenant-controller/crds/tenant.yaml +++ b/charts/neco-tenant-controller/crds/tenant.yaml @@ -40,8 +40,7 @@ spec: description: TenantSpec defines the desired state of Tenant properties: argocd: - description: ArgoCDSpec defines the desired state of the settings - for Argo CD + description: ArgoCD is the settings of Argo CD for this tenant properties: extraAdmins: description: ExtraAdmins are the names of the team to add to the @@ -52,6 +51,8 @@ spec: type: array type: object namespaces: + description: Namespaces are the list of root namespaces that belong + to this tenant items: description: NamespaceSpec defines the desired state of Namespace properties: @@ -78,7 +79,10 @@ spec: required: - name type: object + minItems: 1 type: array + required: + - namespaces type: object status: description: TenantStatus defines the observed state of Tenant diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index ad72ef9..f14f94d 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -41,8 +41,7 @@ spec: description: TenantSpec defines the desired state of Tenant properties: argocd: - description: ArgoCDSpec defines the desired state of the settings - for Argo CD + description: ArgoCD is the settings of Argo CD for this tenant properties: extraAdmins: description: ExtraAdmins are the names of the team to add to the @@ -53,6 +52,8 @@ spec: type: array type: object namespaces: + description: Namespaces are the list of root namespaces that belong + to this tenant items: description: NamespaceSpec defines the desired state of Namespace properties: @@ -79,7 +80,10 @@ spec: required: - name type: object + minItems: 1 type: array + required: + - namespaces type: object status: description: TenantStatus defines the observed state of Tenant diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 0a00441..65b955a 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -136,11 +136,13 @@ func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocd return nil } + // Sync application spec from tenant to argocd. err = r.syncApplicationSpec(ctx, argocdApp, tenantApp) if err != nil { logger.Error(err, "failed to sync application spec") return err } + // Sync application status from argocd to tenant. err = r.syncApplicationStatus(ctx, argocdApp, tenantApp) if err != nil { logger.Error(err, "failed to sync application status") diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 42c0c79..094333d 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -149,7 +149,7 @@ func containNamespace(roots []tenantv1beta1.NamespaceSpec, ns corev1.Namespace) return false } -func (r *TenantReconciler) banishNamespace(ctx context.Context, ns *corev1.Namespace) error { +func (r *TenantReconciler) disownNamespace(ctx context.Context, ns *corev1.Namespace) error { managed, err := accorev1.ExtractNamespace(ns, constants.FieldManager) if err != nil { return err @@ -213,7 +213,7 @@ func (r *TenantReconciler) finalize(ctx context.Context, tenant *tenantv1beta1.T return fmt.Errorf("failed to list namespaces: %w", err) } for _, ns := range nss.Items { - err := r.banishNamespace(ctx, &ns) + err := r.disownNamespace(ctx, &ns) if err != nil { return err } @@ -364,7 +364,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *tena if containNamespace(tenant.Spec.Namespaces, ns) { continue } - err := r.banishNamespace(ctx, &ns) + err := r.disownNamespace(ctx, &ns) if err != nil { return err } @@ -387,17 +387,6 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *tenantv1 return err } - if len(tenant.Spec.Namespaces) == 0 { - if apierrors.IsNotFound(err) { - return nil - } - if orig.GetDeletionTimestamp() != nil { - return nil - } - logger.Info("remove AppProject", "proj", orig, "deletion", orig.GetDeletionTimestamp()) - return r.client.Delete(ctx, orig) - } - nss := &corev1.NamespaceList{} if err := r.client.List(ctx, nss, client.MatchingFields{constants.TenantNamespaces: tenant.Name}); err != nil { return fmt.Errorf("failed to list namespaces: %w", err) diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 0815b78..1ade34a 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -193,7 +193,7 @@ var _ = Describe("Tenant controller", func() { })) }) - It("should banish root namespace", func() { + It("should disown root namespace", func() { tenant := &tenantv1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "y-team", @@ -368,43 +368,8 @@ var _ = Describe("Tenant controller", func() { Expect(err).ToNot(HaveOccurred()) tenant.Spec.Namespaces = []tenantv1beta1.NamespaceSpec{} err = k8sClient.Update(ctx, tenant) - Expect(err).ToNot(HaveOccurred()) - - Eventually(func() error { - err := k8sClient.Get(ctx, client.ObjectKey{Name: "app-y1"}, nsy1) - if err != nil { - return err - } - if nsy1.Labels[constants.OwnerTenant] != "" { - return errors.New("owner label still exists") - } - return nil - }).Should(Succeed()) - Expect(nsy1.Labels).Should(MatchAllKeys(Keys{ - "kubernetes.io/metadata.name": Equal("app-y1"), - "accurate.cybozu.com/type": Equal("root"), - })) - Eventually(func() error { - err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-y1", Name: "y-team-admin"}, rby1) - if apierrors.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - return errors.New("rolebinding still exists") - }).Should(Succeed()) - - Eventually(func() error { - err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: "y-team"}, proj) - if apierrors.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - return errors.New("appproject still exists") - }).Should(Succeed()) + Expect(err).To(HaveOccurred()) + Expect(err.Error()).Should(ContainSubstring("\"y-team\" is invalid: spec.namespaces: Invalid value: 0: spec.namespaces in body should have at least 1 items")) }) It("should remove tenant", func() { diff --git a/docs/design.md b/docs/design.md index 2139a91..df7ec53 100644 --- a/docs/design.md +++ b/docs/design.md @@ -35,7 +35,7 @@ We are hoping for a better solution. ### Adding a tenant An administrator only need to create one custom resource to add a tenant to a Kubernetes cluster. -No more manual operations to add Namespaces and Applications. +No more manual operations to add Applications AppProjects and Namespaces. Tenant users can create sub-namespaces within their tenant. From f583ac8a219078ee0a4d06ee911855ae8bfb0591 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 00:09:38 +0900 Subject: [PATCH 41/89] Add status.health Signed-off-by: zoetrope --- Makefile | 2 +- api/v1beta1/tenant_types.go | 15 ++++++++++++++- charts/neco-tenant-controller/crds/tenant.yaml | 10 ++++++++-- .../bases/multi-tenancy.cybozu.com_tenants.yaml | 10 ++++++++-- controllers/tenant_controller.go | 11 +++++++---- e2e/Makefile | 2 +- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/Makefile b/Makefile index e73e194..7d09e79 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ HELM_VERSION = 3.7.1 CRD_TO_MARKDOWN_VERSION = 0.0.3 TILT_VERSION = 0.23.4 CTLPTL_VERSION = 0.6.2 -ARGOCD_VERSION = 2.1.8 +ARGOCD_VERSION = 2.2.2 # Test tools BIN_DIR := $(shell pwd)/bin diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 80a4153..4c63332 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -60,8 +60,21 @@ type ArgoCDSpec struct { ExtraAdmins []string `json:"extraAdmins,omitempty"` } +// TenantHealth defines the observed state of Tenant +// +kubebuilder:validation:Enum=Healthy;Unhealthy +type TenantHealth string + +const ( + TenantHealthy = TenantHealth("Healthy") + TenantUnhealthy = TenantHealth("Unhealthy") +) + // TenantStatus defines the observed state of Tenant type TenantStatus struct { + // Health is the health of Tenant. + // +optional + Health TenantHealth `json:"health,omitempty"` + // Conditions is an array of conditions. // +optional Conditions []metav1.Condition `json:"conditions,omitempty"` @@ -74,7 +87,7 @@ const ( //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:resource:scope=Cluster -//+kubebuilder:printcolumn:name="READY",type="string",JSONPath=".status.conditions[?(@.type=='Ready')].status" +//+kubebuilder:printcolumn:name="STATUS",type="string",JSONPath=".status.health" // Tenant is the Schema for the tenants API type Tenant struct { diff --git a/charts/neco-tenant-controller/crds/tenant.yaml b/charts/neco-tenant-controller/crds/tenant.yaml index 4a57cd6..6513121 100644 --- a/charts/neco-tenant-controller/crds/tenant.yaml +++ b/charts/neco-tenant-controller/crds/tenant.yaml @@ -16,8 +16,8 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=='Ready')].status - name: READY + - jsonPath: .status.health + name: STATUS type: string name: v1beta1 schema: @@ -157,6 +157,12 @@ spec: - type type: object type: array + health: + description: Health is the health of Tenant. + enum: + - healthy + - unhealthy + type: string type: object type: object served: true diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml index f14f94d..15a3d25 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml @@ -17,8 +17,8 @@ spec: scope: Cluster versions: - additionalPrinterColumns: - - jsonPath: .status.conditions[?(@.type=='Ready')].status - name: READY + - jsonPath: .status.health + name: STATUS type: string name: v1beta1 schema: @@ -158,6 +158,12 @@ spec: - type type: object type: array + health: + description: Health is the health of Tenant. + enum: + - Healthy + - Unhealthy + type: string type: object type: object served: true diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 094333d..983c992 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -98,18 +98,19 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return ctrl.Result{}, nil } - defer func(before []metav1.Condition) { - if !equality.Semantic.DeepEqual(tenant.Status.Conditions, before) { - logger.Info("update conditions", "conditions", tenant.Status.Conditions, "before", before) + defer func(before tenantv1beta1.TenantStatus) { + if !equality.Semantic.DeepEqual(tenant.Status, before) { + logger.Info("update status", "status", tenant.Status, "before", before) if err2 := r.client.Status().Update(ctx, tenant); err2 != nil { logger.Error(err2, "failed to update status") err = err2 } } - }(tenant.Status.Conditions) + }(tenant.Status) err = r.reconcileNamespaces(ctx, tenant) if err != nil { + tenant.Status.Health = tenantv1beta1.TenantUnhealthy meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ Type: tenantv1beta1.ConditionReady, Status: metav1.ConditionFalse, @@ -121,6 +122,7 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res err = r.reconcileArgoCD(ctx, tenant) if err != nil { + tenant.Status.Health = tenantv1beta1.TenantUnhealthy meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ Type: tenantv1beta1.ConditionReady, Status: metav1.ConditionFalse, @@ -130,6 +132,7 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return ctrl.Result{}, err } + tenant.Status.Health = tenantv1beta1.TenantHealthy meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ Type: tenantv1beta1.ConditionReady, Status: metav1.ConditionTrue, diff --git a/e2e/Makefile b/e2e/Makefile index b7680f3..5de54a2 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -1,6 +1,6 @@ KIND_VERSION = 0.11.1 KUBERNETES_VERSION = 1.22.2 -ARGOCD_VERSION = 2.1.8 +ARGOCD_VERSION = 2.2.2 HELM_VERSION = 3.7.1 BIN_DIR := $(dir $(shell pwd))bin From f42a5f52c86099c3dbd28a9609468c6a9c7854b0 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 00:17:39 +0900 Subject: [PATCH 42/89] Add workflows for helm and mdbook Signed-off-by: zoetrope --- .github/workflows/helm.yaml | 67 +++++++++++++++++++++++++++++++++++ .github/workflows/mdbook.yaml | 49 +++++++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 .github/workflows/helm.yaml create mode 100644 .github/workflows/mdbook.yaml diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml new file mode 100644 index 0000000..c0260df --- /dev/null +++ b/.github/workflows/helm.yaml @@ -0,0 +1,67 @@ +name: Release Charts +on: + push: + tags: + - 'chart-v*' +jobs: + build: + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Helm + uses: azure/setup-helm@v1 + with: + version: v3.7.1 + - name: Check chart version + run: | + tag_version=${GITHUB_REF##*/chart-v} + chart_version=$(cat charts/neco-tenant-controller/Chart.yaml | grep 'version:' | sed 's/version:\s//') + if [ "$tag_version" != "$chart_version" ]; then + echo "Different versions: tag \"$tag_version\", chart \"$chart_version\"" + exit 1 + fi + - name: Packaging the chart + run: helm package ./charts/neco-tenant-controller/ + - uses: actions/upload-artifact@v2 + with: + name: helm-charts + path: ./neco-tenant-controller-*.tgz + retention-days: 1 + publish: + name: Publish charts on GitHub Pages + runs-on: ubuntu-20.04 + needs: build + steps: + - uses: actions/checkout@v2 + with: + ref: gh-pages + - name: Set up Helm + uses: azure/setup-helm@v1 + with: + version: v3.7.1 + - uses: actions/download-artifact@v2 + with: + name: helm-charts + - name: Update charts index + run: helm repo index --url https://cybozu-go.github.io/neco-tenant-controller/ --merge index.yaml . + - run: git add . + - name: Check diffs + run: | + diffs=$(git status -s) + if [ "$diffs" = "" ]; then + echo "NO_DIFF=1" >> $GITHUB_ENV + else + printf "%s\n" "$diffs" + fi + - name: Commit changes + if: env.NO_DIFF != '1' + run: | + git config --global user.name 'Cybozu Neco' + git config --global user.email 'cybozu-neco@users.noreply.github.com' + git commit -m 'update' + - name: Push to gh-pages + if: env.NO_DIFF != '1' + run: git push origin gh-pages diff --git a/.github/workflows/mdbook.yaml b/.github/workflows/mdbook.yaml new file mode 100644 index 0000000..bf305cf --- /dev/null +++ b/.github/workflows/mdbook.yaml @@ -0,0 +1,49 @@ +name: Book +on: + pull_request: + push: + branches: + - 'main' +jobs: + build: + name: Build book + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - run: make book + - uses: actions/upload-artifact@v2 + with: + name: book + path: docs/book + retention-days: 1 + publish: + name: Publish book on GitHub Pages + runs-on: ubuntu-20.04 + needs: build + steps: + - uses: actions/checkout@v2 + with: + ref: gh-pages + # ignore helm chart index file and chart archive file. + - run: ls | grep -v -E 'index.yaml|.*\.tgz' | xargs rm -rf + - uses: actions/download-artifact@v2 + with: + name: book + - run: git add . + - name: Check diff + run: | + diffs=$(git status -s) + if [ "$diffs" = "" ]; then + echo "NO_DIFF=1" >> $GITHUB_ENV + else + printf "%s\n" "$diffs" + fi + - name: Commit changes + if: env.NO_DIFF != '1' + run: | + git config --global user.name 'Cybozu Neco' + git config --global user.email 'cybozu-neco@users.noreply.github.com' + git commit -m 'update' + - name: Push to gh-pages + if: github.ref == 'refs/heads/main' && env.NO_DIFF != '1' + run: git push origin gh-pages From bd1c887f35422ac4e56fdb827a087475bb640717 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 00:21:56 +0900 Subject: [PATCH 43/89] Add make book Signed-off-by: zoetrope --- Makefile | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Makefile b/Makefile index 7d09e79..a7f258b 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print su KUSTOMIZE_VERSION = 4.4.1 HELM_VERSION = 3.7.1 CRD_TO_MARKDOWN_VERSION = 0.0.3 +MDBOOK_VERSION = 0.4.15 TILT_VERSION = 0.23.4 CTLPTL_VERSION = 0.6.2 ARGOCD_VERSION = 2.2.2 @@ -62,6 +63,11 @@ generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and apidoc: crd-to-markdown $(wildcard api/*/*_types.go) $(CRD_TO_MARKDOWN) --links docs/links.csv -f api/v1beta1/tenant_types.go -n Tenant > docs/crd_tenant.md +.PHONY: book +book: mdbook + rm -rf docs/book + cd docs; $(MDBOOK) build + .PHONY: check-generate check-generate: $(MAKE) manifests generate apidoc @@ -163,6 +169,13 @@ CRD_TO_MARKDOWN := $(shell pwd)/bin/crd-to-markdown crd-to-markdown: ## Download crd-to-markdown locally if necessary. $(call go-get-tool,$(CRD_TO_MARKDOWN),github.com/clamoriniere/crd-to-markdown@v$(CRD_TO_MARKDOWN_VERSION)) +MDBOOK := $(shell pwd)/bin/mdbook +.PHONY: mdbook +mdbook: $(MDBOOK) ## Download mdbook locally if necessary +$(MDBOOK): + mkdir -p bin + curl -fsL https://github.com/rust-lang/mdBook/releases/download/v$(MDBOOK_VERSION)/mdbook-v$(MDBOOK_VERSION)-x86_64-unknown-linux-gnu.tar.gz | tar -C bin -xzf - + # 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 From 1a534c95f07ee87e5fcbab4ec9bc66f1ed5cf125 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 00:59:41 +0900 Subject: [PATCH 44/89] Rename! Signed-off-by: zoetrope --- .github/workflows/helm.yaml | 8 +- .github/workflows/release.yaml | 6 +- Dockerfile | 8 +- Makefile | 6 +- PROJECT | 10 +- README.md | 12 +- Tiltfile | 14 +- api/v1beta1/groupversion_info.go | 4 +- .../.helmignore | 0 .../Chart.yaml | 4 +- .../crds/tenant.yaml | 10 +- .../templates/NOTES.txt | 0 .../templates/_helpers.tpl | 20 +-- charts/cattage/templates/certificate.yaml | 15 ++ .../templates/configmap.yaml | 4 +- .../templates/deployment.yaml | 16 +-- .../templates/generated.yaml | 132 +++++++++--------- charts/cattage/templates/issuer.yaml | 9 ++ .../values.yaml | 8 +- .../templates/certificate.yaml | 15 -- .../templates/issuer.yaml | 9 -- cmd/cattage-controller/main.go | 9 ++ .../sub/root.go | 14 +- .../sub/run.go | 16 +-- cmd/neco-tenant-controller/main.go | 9 -- ...ts.yaml => cattage.cybozu.io_tenants.yaml} | 4 +- config/crd/kustomization.yaml | 2 +- .../crd/patches/cainjection_in_tenants.yaml | 2 +- config/crd/patches/fix-crd.yaml | 2 +- config/crd/patches/webhook_in_tenants.yaml | 2 +- config/default/kustomization.yaml | 4 +- config/dev/manager.yaml | 2 +- config/helm/crds/kustomization.yaml | 2 +- config/helm/templates/kustomization.yaml | 4 +- config/helm/templates/label-transformer.yaml | 4 +- .../templates/webhookcainjection_patch.yaml | 4 +- config/manager/controller_manager_config.yaml | 2 +- config/manager/manager.yaml | 6 +- config/rbac/role.yaml | 28 ++-- config/rbac/tenant_editor_role.yaml | 4 +- config/rbac/tenant_viewer_role.yaml | 4 +- config/samples/01_tenant.yaml | 4 +- config/samples/03_application.yaml | 2 +- config/webhook/manifests.yaml | 4 +- controllers/application_controller.go | 8 +- controllers/application_controller_test.go | 10 +- controllers/suite_test.go | 6 +- controllers/tenant_controller.go | 46 +++--- controllers/tenant_controller_test.go | 38 ++--- docs/README.md | 6 +- docs/SUMMARY.md | 2 +- docs/book.toml | 8 +- docs/config.md | 16 +-- docs/crd_tenant.md | 5 +- docs/design.md | 2 +- docs/development.md | 6 +- docs/overview.md | 6 +- docs/setup.md | 20 +-- docs/usage.md | 6 +- e2e/Makefile | 14 +- e2e/accurate-values.yaml | 2 +- e2e/e2e_test.go | 4 +- go.mod | 2 +- hooks/application.go | 6 +- hooks/application_test.go | 4 +- hooks/suite_test.go | 8 +- hooks/tenant.go | 14 +- hooks/tenant_test.go | 28 ++-- pkg/config/types.go | 2 +- pkg/constants/indexer.go | 4 +- pkg/constants/meta.go | 4 +- version.go | 4 +- 72 files changed, 363 insertions(+), 362 deletions(-) rename charts/{neco-tenant-controller => cattage}/.helmignore (100%) rename charts/{neco-tenant-controller => cattage}/Chart.yaml (92%) rename charts/{neco-tenant-controller => cattage}/crds/tenant.yaml (97%) rename charts/{neco-tenant-controller => cattage}/templates/NOTES.txt (100%) rename charts/{neco-tenant-controller => cattage}/templates/_helpers.tpl (70%) create mode 100644 charts/cattage/templates/certificate.yaml rename charts/{neco-tenant-controller => cattage}/templates/configmap.yaml (87%) rename charts/{neco-tenant-controller => cattage}/templates/deployment.yaml (81%) rename charts/{neco-tenant-controller => cattage}/templates/generated.yaml (62%) create mode 100644 charts/cattage/templates/issuer.yaml rename charts/{neco-tenant-controller => cattage}/values.yaml (88%) delete mode 100644 charts/neco-tenant-controller/templates/certificate.yaml delete mode 100644 charts/neco-tenant-controller/templates/issuer.yaml create mode 100644 cmd/cattage-controller/main.go rename cmd/{neco-tenant-controller => cattage-controller}/sub/root.go (85%) rename cmd/{neco-tenant-controller => cattage-controller}/sub/run.go (85%) delete mode 100644 cmd/neco-tenant-controller/main.go rename config/crd/bases/{multi-tenancy.cybozu.com_tenants.yaml => cattage.cybozu.io_tenants.yaml} (99%) diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml index c0260df..effecaf 100644 --- a/.github/workflows/helm.yaml +++ b/.github/workflows/helm.yaml @@ -18,17 +18,17 @@ jobs: - name: Check chart version run: | tag_version=${GITHUB_REF##*/chart-v} - chart_version=$(cat charts/neco-tenant-controller/Chart.yaml | grep 'version:' | sed 's/version:\s//') + chart_version=$(cat charts/cattage/Chart.yaml | grep 'version:' | sed 's/version:\s//') if [ "$tag_version" != "$chart_version" ]; then echo "Different versions: tag \"$tag_version\", chart \"$chart_version\"" exit 1 fi - name: Packaging the chart - run: helm package ./charts/neco-tenant-controller/ + run: helm package ./charts/cattage/ - uses: actions/upload-artifact@v2 with: name: helm-charts - path: ./neco-tenant-controller-*.tgz + path: ./cattage-*.tgz retention-days: 1 publish: name: Publish charts on GitHub Pages @@ -46,7 +46,7 @@ jobs: with: name: helm-charts - name: Update charts index - run: helm repo index --url https://cybozu-go.github.io/neco-tenant-controller/ --merge index.yaml . + run: helm repo index --url https://cybozu-go.github.io/cattage/ --merge index.yaml . - run: git add . - name: Check diffs run: | diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index b1af93d..f64e9d7 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -13,11 +13,11 @@ jobs: - uses: actions/checkout@v2 - name: Build images run: | - docker build -t neco-tenant-controller:dev . + docker build -t cattage:dev . - name: Login to ghcr.io run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin - name: Push images run: | TAG=${GITHUB_REF#refs/tags/v} - docker tag neco-tenant-controller:dev ghcr.io/cybozu-go/neco-tenant-controller:$TAG - docker push ghcr.io/cybozu-go/neco-tenant-controller:$TAG + docker tag cattage:dev ghcr.io/cybozu-go/cattage:$TAG + docker push ghcr.io/cybozu-go/cattage:$TAG diff --git a/Dockerfile b/Dockerfile index e1b7645..edb2091 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,13 +2,13 @@ FROM quay.io/cybozu/golang:1.17-focal as builder COPY ./ . -RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o neco-tenant-controller ./cmd/neco-tenant-controller +RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o cattage-controller ./cmd/cattage-controller # the controller image FROM scratch -LABEL org.opencontainers.image.source https://github.com/cybozu-go/neco-tenant +LABEL org.opencontainers.image.source https://github.com/cybozu-go/cattage -COPY --from=builder /work/neco-tenant-controller ./ +COPY --from=builder /work/cattage-controller ./ USER 10000:10000 -ENTRYPOINT ["/neco-tenant-controller"] +ENTRYPOINT ["/cattage-controller"] diff --git a/Makefile b/Makefile index a7f258b..3194c20 100644 --- a/Makefile +++ b/Makefile @@ -51,8 +51,8 @@ help: ## Display this help. .PHONY: manifests manifests: kustomize controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - $(KUSTOMIZE) build config/helm/crds > charts/neco-tenant-controller/crds/tenant.yaml - $(KUSTOMIZE) build config/helm/templates > charts/neco-tenant-controller/templates/generated.yaml + $(KUSTOMIZE) build config/helm/crds > charts/cattage/crds/tenant.yaml + $(KUSTOMIZE) build config/helm/templates > charts/cattage/templates/generated.yaml .PHONY: generate @@ -104,7 +104,7 @@ build: .PHONY: docker-build docker-build: - docker build -t neco-tenant-controller:latest . + docker build -t cattage:latest . ##@ Development TILT := $(shell pwd)/bin/tilt diff --git a/PROJECT b/PROJECT index fdcecd2..2d3fb33 100644 --- a/PROJECT +++ b/PROJECT @@ -1,17 +1,17 @@ -domain: cybozu.com +domain: cybozu.io layout: - go.kubebuilder.io/v3 -projectName: neco-tenant-controller -repo: github.com/cybozu-go/neco-tenant-controller +projectName: cattage +repo: github.com/cybozu-go/cattage resources: - api: crdVersion: v1 namespaced: true controller: true - domain: cybozu.com + domain: cybozu.io group: multi-tenancy kind: Tenant - path: github.com/cybozu-go/neco-tenant-controller/api/v1beta1 + path: github.com/cybozu-go/cattage/api/v1beta1 version: v1beta1 webhooks: defaulting: true diff --git a/README.md b/README.md index 2690833..0f46c99 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -[![GitHub release](https://img.shields.io/github/release/cybozu-go/neco-tenant-controller.svg?maxAge=60)](https://github.com/cybozu-go/neco-tenant-controller/releases) -[![CI](https://github.com/cybozu-go/neco-tenant-controller/actions/workflows/ci.yaml/badge.svg)](https://github.com/cybozu-go/neco-tenant-controller/actions/workflows/ci.yaml) -[![PkgGoDev](https://pkg.go.dev/badge/github.com/cybozu-go/neco-tenant-controller?tab=overview)](https://pkg.go.dev/github.com/cybozu-go/neco-tenant-controller?tab=overview) -[![Go Report Card](https://goreportcard.com/badge/github.com/cybozu-go/neco-tenant-controller)](https://goreportcard.com/report/github.com/cybozu-go/neco-tenant-controller) +[![GitHub release](https://img.shields.io/github/release/cybozu-go/cattage.svg?maxAge=60)](https://github.com/cybozu-go/cattage/releases) +[![CI](https://github.com/cybozu-go/cattage/actions/workflows/ci.yaml/badge.svg)](https://github.com/cybozu-go/cattage/actions/workflows/ci.yaml) +[![PkgGoDev](https://pkg.go.dev/badge/github.com/cybozu-go/cattage?tab=overview)](https://pkg.go.dev/github.com/cybozu-go/cattage?tab=overview) +[![Go Report Card](https://goreportcard.com/badge/github.com/cybozu-go/cattage)](https://goreportcard.com/report/github.com/cybozu-go/cattage) -# Tenant Controller for Neco +# Cattage -neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. +Cattage is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. **Project Status**: Initial development diff --git a/Tiltfile b/Tiltfile index 6f4adad..bf3afcc 100644 --- a/Tiltfile +++ b/Tiltfile @@ -5,8 +5,8 @@ def kubebuilder(): DOCKERFILE = '''FROM golang:alpine WORKDIR / - COPY ./bin/neco-tenant-controller / - CMD ["/neco-tenant-controller"] + COPY ./bin/cattage-controller / + CMD ["/cattage-controller"] ''' def manifests(): @@ -16,7 +16,7 @@ def kubebuilder(): return 'controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' def binary(): - return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/neco-tenant-controller cmd/neco-tenant-controller/main.go' + return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/cattage-controller cmd/cattage-controller/main.go' installed = local("which kubebuilder") print("kubebuilder is present:", installed) @@ -37,12 +37,12 @@ def kubebuilder(): local_resource('Sample YAML', 'kubectl apply -f ./config/samples', deps=["./config/samples"], resource_deps=[DIRNAME + "-controller-manager"]) - docker_build_with_restart('neco-tenant-controller:dev', '.', + docker_build_with_restart('cattage:dev', '.', dockerfile_contents=DOCKERFILE, - entrypoint=['/neco-tenant-controller', '--zap-devel=true'], - only=['./bin/neco-tenant-controller'], + entrypoint=['/cattage-controller', '--zap-devel=true'], + only=['./bin/cattage-controller'], live_update=[ - sync('./bin/neco-tenant-controller', '/neco-tenant-controller'), + sync('./bin/cattage-controller', '/cattage-controller'), ] ) diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go index 436f3c2..1c9d0b4 100644 --- a/api/v1beta1/groupversion_info.go +++ b/api/v1beta1/groupversion_info.go @@ -16,7 +16,7 @@ limitations under the License. // Package v1beta1 contains API Schema definitions for the multi-tenancy v1beta1 API group //+kubebuilder:object:generate=true -//+groupName=multi-tenancy.cybozu.com +//+groupName=cattage.cybozu.io package v1beta1 import ( @@ -26,7 +26,7 @@ import ( var ( // GroupVersion is group version used to register these objects - GroupVersion = schema.GroupVersion{Group: "multi-tenancy.cybozu.com", Version: "v1beta1"} + GroupVersion = schema.GroupVersion{Group: "cattage.cybozu.io", Version: "v1beta1"} // SchemeBuilder is used to add go types to the GroupVersionKind scheme SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} diff --git a/charts/neco-tenant-controller/.helmignore b/charts/cattage/.helmignore similarity index 100% rename from charts/neco-tenant-controller/.helmignore rename to charts/cattage/.helmignore diff --git a/charts/neco-tenant-controller/Chart.yaml b/charts/cattage/Chart.yaml similarity index 92% rename from charts/neco-tenant-controller/Chart.yaml rename to charts/cattage/Chart.yaml index f68207f..86700e5 100644 --- a/charts/neco-tenant-controller/Chart.yaml +++ b/charts/cattage/Chart.yaml @@ -1,6 +1,6 @@ apiVersion: v2 -name: neco-tenant-controller -description: A Helm chart for neco-tenant-controller +name: cattage +description: A Helm chart for cattage # A chart can be either an 'application' or a 'library' chart. # diff --git a/charts/neco-tenant-controller/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml similarity index 97% rename from charts/neco-tenant-controller/crds/tenant.yaml rename to charts/cattage/crds/tenant.yaml index 6513121..058564b 100644 --- a/charts/neco-tenant-controller/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -4,10 +4,10 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.7.0 labels: - app.kubernetes.io/name: neco-tenant-controller - name: tenants.multi-tenancy.cybozu.com + app.kubernetes.io/name: cattage + name: tenants.cattage.cybozu.io spec: - group: multi-tenancy.cybozu.com + group: cattage.cybozu.io names: kind: Tenant listKind: TenantList @@ -160,8 +160,8 @@ spec: health: description: Health is the health of Tenant. enum: - - healthy - - unhealthy + - Healthy + - Unhealthy type: string type: object type: object diff --git a/charts/neco-tenant-controller/templates/NOTES.txt b/charts/cattage/templates/NOTES.txt similarity index 100% rename from charts/neco-tenant-controller/templates/NOTES.txt rename to charts/cattage/templates/NOTES.txt diff --git a/charts/neco-tenant-controller/templates/_helpers.tpl b/charts/cattage/templates/_helpers.tpl similarity index 70% rename from charts/neco-tenant-controller/templates/_helpers.tpl rename to charts/cattage/templates/_helpers.tpl index 2ff18eb..33f6690 100644 --- a/charts/neco-tenant-controller/templates/_helpers.tpl +++ b/charts/cattage/templates/_helpers.tpl @@ -1,7 +1,7 @@ {{/* Expand the name of the chart. */}} -{{- define "neco-tenant-controller.name" -}} +{{- define "cattage.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} @@ -10,7 +10,7 @@ Create a default fully qualified app name. We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). If release name contains chart name it will be used as a full name. */}} -{{- define "neco-tenant-controller.fullname" -}} +{{- define "cattage.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} @@ -26,16 +26,16 @@ If release name contains chart name it will be used as a full name. {{/* Create chart name and version as used by the chart label. */}} -{{- define "neco-tenant-controller.chart" -}} +{{- define "cattage.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} -{{- define "neco-tenant-controller.labels" -}} -helm.sh/chart: {{ include "neco-tenant-controller.chart" . }} -{{ include "neco-tenant-controller.selectorLabels" . }} +{{- define "cattage.labels" -}} +helm.sh/chart: {{ include "cattage.chart" . }} +{{ include "cattage.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} @@ -45,17 +45,17 @@ app.kubernetes.io/managed-by: {{ .Release.Service }} {{/* Selector labels */}} -{{- define "neco-tenant-controller.selectorLabels" -}} -app.kubernetes.io/name: {{ include "neco-tenant-controller.name" . }} +{{- define "cattage.selectorLabels" -}} +app.kubernetes.io/name: {{ include "cattage.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} -{{- define "neco-tenant-controller.serviceAccountName" -}} +{{- define "cattage.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} -{{- default (include "neco-tenant-controller.fullname" .) .Values.serviceAccount.name }} +{{- default (include "cattage.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} diff --git a/charts/cattage/templates/certificate.yaml b/charts/cattage/templates/certificate.yaml new file mode 100644 index 0000000..09600e1 --- /dev/null +++ b/charts/cattage/templates/certificate.yaml @@ -0,0 +1,15 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: {{ template "cattage.fullname" . }}-serving-cert + namespace: {{ .Release.Namespace }} + labels: + {{- include "cattage.labels" . | nindent 4 }} +spec: + dnsNames: + - {{ template "cattage.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc + - {{ template "cattage.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc.cluster.local + issuerRef: + kind: Issuer + name: {{ template "cattage.fullname" . }}-selfsigned-issuer + secretName: webhook-server-cert diff --git a/charts/neco-tenant-controller/templates/configmap.yaml b/charts/cattage/templates/configmap.yaml similarity index 87% rename from charts/neco-tenant-controller/templates/configmap.yaml rename to charts/cattage/templates/configmap.yaml index 0d826fc..68306b1 100644 --- a/charts/neco-tenant-controller/templates/configmap.yaml +++ b/charts/cattage/templates/configmap.yaml @@ -1,10 +1,10 @@ apiVersion: v1 kind: ConfigMap metadata: - name: {{ template "neco-tenant-controller.fullname" . }}-config + name: {{ template "cattage.fullname" . }}-config namespace: {{ .Release.Namespace }} labels: - {{- include "neco-tenant-controller.labels" . | nindent 4 }} + {{- include "cattage.labels" . | nindent 4 }} data: config.yaml: | namespace: diff --git a/charts/neco-tenant-controller/templates/deployment.yaml b/charts/cattage/templates/deployment.yaml similarity index 81% rename from charts/neco-tenant-controller/templates/deployment.yaml rename to charts/cattage/templates/deployment.yaml index 3f0a991..0cc3ed6 100644 --- a/charts/neco-tenant-controller/templates/deployment.yaml +++ b/charts/cattage/templates/deployment.yaml @@ -1,17 +1,17 @@ apiVersion: apps/v1 kind: Deployment metadata: - name: {{ template "neco-tenant-controller.fullname" . }}-controller-manager + name: {{ template "cattage.fullname" . }}-controller-manager namespace: {{ .Release.Namespace }} labels: app.kubernetes.io/component: controller - {{- include "neco-tenant-controller.labels" . | nindent 4 }} + {{- include "cattage.labels" . | nindent 4 }} spec: replicas: {{ .Values.controller.replicas }} selector: matchLabels: app.kubernetes.io/component: controller - app.kubernetes.io/name: {{ include "neco-tenant-controller.name" . }} + app.kubernetes.io/name: {{ include "cattage.name" . }} template: metadata: annotations: @@ -19,7 +19,7 @@ spec: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} labels: app.kubernetes.io/component: controller - app.kubernetes.io/name: {{ include "neco-tenant-controller.name" . }} + app.kubernetes.io/name: {{ include "cattage.name" . }} spec: containers: - name: manager @@ -28,7 +28,7 @@ spec: imagePullPolicy: {{ . }} {{- end }} command: - - /neco-tenant-controller + - /cattage-controller {{- with .Values.controller.extraArgs }} args: {{ . }} {{- end }} @@ -69,11 +69,11 @@ spec: - mountPath: /tmp/k8s-webhook-server/serving-certs name: cert readOnly: true - - mountPath: /etc/neco-tenant-controller + - mountPath: /etc/cattage name: config securityContext: runAsNonRoot: true - serviceAccountName: {{ template "neco-tenant-controller.fullname" . }}-controller-manager + serviceAccountName: {{ template "cattage.fullname" . }}-controller-manager terminationGracePeriodSeconds: {{ .Values.controller.terminationGracePeriodSeconds }} volumes: - name: cert @@ -81,5 +81,5 @@ spec: defaultMode: 420 secretName: webhook-server-cert - configMap: - name: {{ template "neco-tenant-controller.fullname" . }}-config + name: {{ template "cattage.fullname" . }}-config name: config diff --git a/charts/neco-tenant-controller/templates/generated.yaml b/charts/cattage/templates/generated.yaml similarity index 62% rename from charts/neco-tenant-controller/templates/generated.yaml rename to charts/cattage/templates/generated.yaml index ee34db5..3c853ec 100644 --- a/charts/neco-tenant-controller/templates/generated.yaml +++ b/charts/cattage/templates/generated.yaml @@ -3,10 +3,10 @@ kind: ServiceAccount metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-controller-manager' namespace: '{{ .Release.Namespace }}' --- apiVersion: rbac.authorization.k8s.io/v1 @@ -14,10 +14,10 @@ kind: Role metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-leader-election-role' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-leader-election-role' namespace: '{{ .Release.Namespace }}' rules: - apiGroups: @@ -58,10 +58,10 @@ metadata: creationTimestamp: null labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-manager-role' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-manager-role' rules: - apiGroups: - "" @@ -96,9 +96,9 @@ rules: - update - watch - apiGroups: - - "" + - cattage.cybozu.io resources: - - namespaces + - tenants verbs: - create - delete @@ -108,31 +108,31 @@ rules: - update - watch - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - - tenants + - tenants/finalizers verbs: - - create - - delete - - get - - list - - patch - update - - watch - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - - tenants/finalizers + - tenants/status verbs: + - get + - patch - update - apiGroups: - - multi-tenancy.cybozu.com + - "" resources: - - tenants/status + - namespaces verbs: + - create + - delete - get + - list - patch - update + - watch - apiGroups: - rbac.authorization.k8s.io resources: @@ -161,10 +161,10 @@ kind: ClusterRole metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-metrics-reader' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-metrics-reader' rules: - nonResourceURLs: - /metrics @@ -176,10 +176,10 @@ kind: ClusterRole metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-proxy-role' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-proxy-role' rules: - apiGroups: - authentication.k8s.io @@ -199,18 +199,18 @@ kind: RoleBinding metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-leader-election-rolebinding' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-leader-election-rolebinding' namespace: '{{ .Release.Namespace }}' roleRef: apiGroup: rbac.authorization.k8s.io kind: Role - name: '{{ template "neco-tenant-controller.fullname" . }}-leader-election-role' + name: '{{ template "cattage.fullname" . }}-leader-election-role' subjects: - kind: ServiceAccount - name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + name: '{{ template "cattage.fullname" . }}-controller-manager' namespace: '{{ .Release.Namespace }}' --- apiVersion: rbac.authorization.k8s.io/v1 @@ -218,17 +218,17 @@ kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-manager-rolebinding' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-manager-rolebinding' roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: '{{ template "neco-tenant-controller.fullname" . }}-manager-role' + name: '{{ template "cattage.fullname" . }}-manager-role' subjects: - kind: ServiceAccount - name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + name: '{{ template "cattage.fullname" . }}-controller-manager' namespace: '{{ .Release.Namespace }}' --- apiVersion: rbac.authorization.k8s.io/v1 @@ -236,17 +236,17 @@ kind: ClusterRoleBinding metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-proxy-rolebinding' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-proxy-rolebinding' roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole - name: '{{ template "neco-tenant-controller.fullname" . }}-proxy-role' + name: '{{ template "cattage.fullname" . }}-proxy-role' subjects: - kind: ServiceAccount - name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager' + name: '{{ template "cattage.fullname" . }}-controller-manager' namespace: '{{ .Release.Namespace }}' --- apiVersion: v1 @@ -254,10 +254,10 @@ kind: Service metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-controller-manager-metrics-service' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-controller-manager-metrics-service' namespace: '{{ .Release.Namespace }}' spec: ports: @@ -267,17 +267,17 @@ spec: targetPort: https selector: app.kubernetes.io/component: controller - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' --- apiVersion: v1 kind: Service metadata: labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-webhook-service' namespace: '{{ .Release.Namespace }}' spec: ports: @@ -286,26 +286,26 @@ spec: targetPort: 9443 selector: app.kubernetes.io/component: controller - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" . }}-serving-cert' labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-mutating-webhook-configuration' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-mutating-webhook-configuration' webhooks: - admissionReviewVersions: - v1 clientConfig: service: - name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + name: '{{ template "cattage.fullname" . }}-webhook-service' namespace: '{{ .Release.Namespace }}' path: /mutate-argoproj-io-application failurePolicy: Fail @@ -325,14 +325,14 @@ webhooks: - v1 clientConfig: service: - name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + name: '{{ template "cattage.fullname" . }}-webhook-service' namespace: '{{ .Release.Namespace }}' path: /mutate-multi-tenancy-cybozu-com-v1beta1-tenant failurePolicy: Fail name: mtenant.kb.io rules: - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io apiVersions: - v1beta1 operations: @@ -346,20 +346,20 @@ apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" . }}-serving-cert' labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - name: '{{ template "neco-tenant-controller.fullname" . }}-validating-webhook-configuration' + helm.sh/chart: '{{ include "cattage.chart" . }}' + name: '{{ template "cattage.fullname" . }}-validating-webhook-configuration' webhooks: - admissionReviewVersions: - v1 clientConfig: service: - name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + name: '{{ template "cattage.fullname" . }}-webhook-service' namespace: '{{ .Release.Namespace }}' path: /validate-argoproj-io-application failurePolicy: Fail @@ -379,14 +379,14 @@ webhooks: - v1 clientConfig: service: - name: '{{ template "neco-tenant-controller.fullname" . }}-webhook-service' + name: '{{ template "cattage.fullname" . }}-webhook-service' namespace: '{{ .Release.Namespace }}' path: /validate-multi-tenancy-cybozu-com-v1beta1-tenant failurePolicy: Fail name: vtenant.kb.io rules: - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io apiVersions: - v1beta1 operations: diff --git a/charts/cattage/templates/issuer.yaml b/charts/cattage/templates/issuer.yaml new file mode 100644 index 0000000..a6bf08d --- /dev/null +++ b/charts/cattage/templates/issuer.yaml @@ -0,0 +1,9 @@ +apiVersion: cert-manager.io/v1 +kind: Issuer +metadata: + name: {{ template "cattage.fullname" . }}-selfsigned-issuer + namespace: {{ .Release.Namespace }} + labels: + {{- include "cattage.labels" . | nindent 4 }} +spec: + selfSigned: {} diff --git a/charts/neco-tenant-controller/values.yaml b/charts/cattage/values.yaml similarity index 88% rename from charts/neco-tenant-controller/values.yaml rename to charts/cattage/values.yaml index b6e63a8..0dbda77 100644 --- a/charts/neco-tenant-controller/values.yaml +++ b/charts/cattage/values.yaml @@ -1,12 +1,12 @@ image: - # image.repository -- neco-tenant-controller image repository to use. - repository: ghcr.io/cybozu-go/neco-tenant-controller + # image.repository -- cattage image repository to use. + repository: ghcr.io/cybozu-go/cattage - # image.tag -- neco-tenant-controller image tag to use. + # image.tag -- cattage image tag to use. # @default -- `{{ .Chart.AppVersion }}` tag: # 0.1.0 - # image.pullPolicy -- neco-tenant-controller image pullPolicy. + # image.pullPolicy -- cattage image pullPolicy. pullPolicy: # Always controller: diff --git a/charts/neco-tenant-controller/templates/certificate.yaml b/charts/neco-tenant-controller/templates/certificate.yaml deleted file mode 100644 index 84ee3e7..0000000 --- a/charts/neco-tenant-controller/templates/certificate.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Certificate -metadata: - name: {{ template "neco-tenant-controller.fullname" . }}-serving-cert - namespace: {{ .Release.Namespace }} - labels: - {{- include "neco-tenant-controller.labels" . | nindent 4 }} -spec: - dnsNames: - - {{ template "neco-tenant-controller.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc - - {{ template "neco-tenant-controller.fullname" . }}-webhook-service.{{ .Release.Namespace }}.svc.cluster.local - issuerRef: - kind: Issuer - name: {{ template "neco-tenant-controller.fullname" . }}-selfsigned-issuer - secretName: webhook-server-cert diff --git a/charts/neco-tenant-controller/templates/issuer.yaml b/charts/neco-tenant-controller/templates/issuer.yaml deleted file mode 100644 index f608c62..0000000 --- a/charts/neco-tenant-controller/templates/issuer.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: cert-manager.io/v1 -kind: Issuer -metadata: - name: {{ template "neco-tenant-controller.fullname" . }}-selfsigned-issuer - namespace: {{ .Release.Namespace }} - labels: - {{- include "neco-tenant-controller.labels" . | nindent 4 }} -spec: - selfSigned: {} diff --git a/cmd/cattage-controller/main.go b/cmd/cattage-controller/main.go new file mode 100644 index 0000000..129683c --- /dev/null +++ b/cmd/cattage-controller/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/cybozu-go/cattage/cmd/cattage-controller/sub" +) + +func main() { + sub.Execute() +} diff --git a/cmd/neco-tenant-controller/sub/root.go b/cmd/cattage-controller/sub/root.go similarity index 85% rename from cmd/neco-tenant-controller/sub/root.go rename to cmd/cattage-controller/sub/root.go index 4b122fd..e642056 100644 --- a/cmd/neco-tenant-controller/sub/root.go +++ b/cmd/cattage-controller/sub/root.go @@ -8,13 +8,13 @@ import ( "os" "strconv" - tenant "github.com/cybozu-go/neco-tenant-controller" + "github.com/cybozu-go/cattage" "github.com/spf13/cobra" "k8s.io/klog" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) -const defaultConfigPath = "/etc/neco-tenant-controller/config.yaml" +const defaultConfigPath = "/etc/cattage/config.yaml" var options struct { configFile string @@ -27,10 +27,10 @@ var options struct { } var rootCmd = &cobra.Command{ - Use: "neco-tenant-controller", - Version: tenant.Version, - Short: "neco-tenant controller", - Long: `neco-tenant controller`, + Use: "cattage-controller", + Version: cattage.Version, + Short: "cattage controller", + Long: `cattage controller`, RunE: func(cmd *cobra.Command, args []string) error { cmd.SilenceUsage = true @@ -64,7 +64,7 @@ func init() { fs.StringVar(&options.configFile, "config-file", defaultConfigPath, "Configuration file path") fs.StringVar(&options.metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to") fs.StringVar(&options.probeAddr, "health-probe-addr", ":8081", "Listen address for health probes") - fs.StringVar(&options.leaderElectionID, "leader-election-id", "neco-tenant-controller", "ID for leader election by controller-runtime") + fs.StringVar(&options.leaderElectionID, "leader-election-id", "cattage", "ID for leader election by controller-runtime") fs.StringVar(&options.webhookAddr, "webhook-addr", ":9443", "Listen address for the webhook endpoint") fs.StringVar(&options.certDir, "cert-dir", "", "webhook certificate directory") diff --git a/cmd/neco-tenant-controller/sub/run.go b/cmd/cattage-controller/sub/run.go similarity index 85% rename from cmd/neco-tenant-controller/sub/run.go rename to cmd/cattage-controller/sub/run.go index d850706..dc2362d 100644 --- a/cmd/neco-tenant-controller/sub/run.go +++ b/cmd/cattage-controller/sub/run.go @@ -8,11 +8,11 @@ import ( // to ensure that exec-entrypoint and run can make use of them. _ "k8s.io/client-go/plugin/pkg/client/auth" - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/controllers" - "github.com/cybozu-go/neco-tenant-controller/hooks" - cacheclient "github.com/cybozu-go/neco-tenant-controller/pkg/client" - "github.com/cybozu-go/neco-tenant-controller/pkg/config" + cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/controllers" + "github.com/cybozu-go/cattage/hooks" + cacheclient "github.com/cybozu-go/cattage/pkg/client" + "github.com/cybozu-go/cattage/pkg/config" "k8s.io/apimachinery/pkg/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" @@ -29,8 +29,8 @@ func subMain(ns, addr string, port int) error { if err := clientgoscheme.AddToScheme(scheme); err != nil { return fmt.Errorf("unable to add client-go objects: %w", err) } - if err := multitenancyv1beta1.AddToScheme(scheme); err != nil { - return fmt.Errorf("unable to add neco-tenant-controller objects: %w", err) + if err := cattagev1beta1.AddToScheme(scheme); err != nil { + return fmt.Errorf("unable to add cattage objects: %w", err) } cfgData, err := os.ReadFile(options.configFile) @@ -74,7 +74,7 @@ func subMain(ns, addr string, port int) error { if err := controllers.NewApplicationReconciler( mgr.GetClient(), - mgr.GetEventRecorderFor("neco-tenant-controller"), + mgr.GetEventRecorderFor("cattage"), cfg, ).SetupWithManager(ctx, mgr); err != nil { return fmt.Errorf("unable to create Namespace controller: %w", err) diff --git a/cmd/neco-tenant-controller/main.go b/cmd/neco-tenant-controller/main.go deleted file mode 100644 index e93354a..0000000 --- a/cmd/neco-tenant-controller/main.go +++ /dev/null @@ -1,9 +0,0 @@ -package main - -import ( - "github.com/cybozu-go/neco-tenant-controller/cmd/neco-tenant-controller/sub" -) - -func main() { - sub.Execute() -} diff --git a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml similarity index 99% rename from config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml rename to config/crd/bases/cattage.cybozu.io_tenants.yaml index 15a3d25..c19d3a8 100644 --- a/config/crd/bases/multi-tenancy.cybozu.com_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -6,9 +6,9 @@ metadata: annotations: controller-gen.kubebuilder.io/version: v0.7.0 creationTimestamp: null - name: tenants.multi-tenancy.cybozu.com + name: tenants.cattage.cybozu.io spec: - group: multi-tenancy.cybozu.com + group: cattage.cybozu.io names: kind: Tenant listKind: TenantList diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 70bdaa4..f47c921 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/multi-tenancy.cybozu.com_tenants.yaml + - bases/cattage.cybozu.io_tenants.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: diff --git a/config/crd/patches/cainjection_in_tenants.yaml b/config/crd/patches/cainjection_in_tenants.yaml index 8ab5ede..79bf7fb 100644 --- a/config/crd/patches/cainjection_in_tenants.yaml +++ b/config/crd/patches/cainjection_in_tenants.yaml @@ -4,4 +4,4 @@ kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: tenants.multi-tenancy.cybozu.com + name: tenants.cattage.cybozu.io diff --git a/config/crd/patches/fix-crd.yaml b/config/crd/patches/fix-crd.yaml index 8a4cf6a..c6a9ba0 100644 --- a/config/crd/patches/fix-crd.yaml +++ b/config/crd/patches/fix-crd.yaml @@ -1,6 +1,6 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: tenants.multi-tenancy.cybozu.com + name: tenants.cattage.cybozu.io creationTimestamp: null status: null diff --git a/config/crd/patches/webhook_in_tenants.yaml b/config/crd/patches/webhook_in_tenants.yaml index fd8f900..5a32ebd 100644 --- a/config/crd/patches/webhook_in_tenants.yaml +++ b/config/crd/patches/webhook_in_tenants.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: - name: tenants.multi-tenancy.cybozu.com + name: tenants.cattage.cybozu.io spec: conversion: strategy: Webhook diff --git a/config/default/kustomization.yaml b/config/default/kustomization.yaml index b4869b2..b62479a 100644 --- a/config/default/kustomization.yaml +++ b/config/default/kustomization.yaml @@ -1,12 +1,12 @@ # Adds namespace to all resources. -namespace: neco-tenant-controller-system +namespace: cattage # 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: neco-tenant-controller- +namePrefix: cattage- # Labels to add to all resources and selectors. #commonLabels: diff --git a/config/dev/manager.yaml b/config/dev/manager.yaml index cfd4a9d..9f35ade 100644 --- a/config/dev/manager.yaml +++ b/config/dev/manager.yaml @@ -9,7 +9,7 @@ spec: securityContext: null containers: - command: - - /mneco-tenant-controller + - /cattage-controller args: null name: manager securityContext: null diff --git a/config/helm/crds/kustomization.yaml b/config/helm/crds/kustomization.yaml index 9bf0b86..8694560 100644 --- a/config/helm/crds/kustomization.yaml +++ b/config/helm/crds/kustomization.yaml @@ -2,4 +2,4 @@ resources: - ../../crd commonLabels: - app.kubernetes.io/name: neco-tenant-controller + app.kubernetes.io/name: cattage diff --git a/config/helm/templates/kustomization.yaml b/config/helm/templates/kustomization.yaml index 38821f6..a3e9fc4 100644 --- a/config/helm/templates/kustomization.yaml +++ b/config/helm/templates/kustomization.yaml @@ -4,10 +4,10 @@ resources: namespace: '{{ .Release.Namespace }}' -namePrefix: '{{ template "neco-tenant-controller.fullname" . }}-' +namePrefix: '{{ template "cattage.fullname" . }}-' commonLabels: - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' patchesStrategicMerge: - webhookcainjection_patch.yaml diff --git a/config/helm/templates/label-transformer.yaml b/config/helm/templates/label-transformer.yaml index fc38f63..e318edd 100644 --- a/config/helm/templates/label-transformer.yaml +++ b/config/helm/templates/label-transformer.yaml @@ -3,8 +3,8 @@ kind: LabelTransformer metadata: name: helm-metadata-labels labels: - helm.sh/chart: '{{ include "neco-tenant-controller.chart" . }}' - app.kubernetes.io/name: '{{ include "neco-tenant-controller.name" . }}' + helm.sh/chart: '{{ include "cattage.chart" . }}' + app.kubernetes.io/name: '{{ include "cattage.name" . }}' app.kubernetes.io/version: '{{ .Chart.AppVersion }}' app.kubernetes.io/managed-by: '{{ .Release.Service }}' fieldSpecs: diff --git a/config/helm/templates/webhookcainjection_patch.yaml b/config/helm/templates/webhookcainjection_patch.yaml index 3aa40c3..71f9648 100644 --- a/config/helm/templates/webhookcainjection_patch.yaml +++ b/config/helm/templates/webhookcainjection_patch.yaml @@ -3,11 +3,11 @@ kind: MutatingWebhookConfiguration metadata: name: mutating-webhook-configuration annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" . }}-serving-cert' + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" . }}-serving-cert' --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "neco-tenant-controller.fullname" . }}-serving-cert' + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" . }}-serving-cert' diff --git a/config/manager/controller_manager_config.yaml b/config/manager/controller_manager_config.yaml index 17ae949..70a69cb 100644 --- a/config/manager/controller_manager_config.yaml +++ b/config/manager/controller_manager_config.yaml @@ -8,4 +8,4 @@ webhook: port: 9443 leaderElection: leaderElect: true - resourceName: 309b12c3.cybozu.com + resourceName: 309b12c3.cybozu.io diff --git a/config/manager/manager.yaml b/config/manager/manager.yaml index a6892a6..84c0cd6 100644 --- a/config/manager/manager.yaml +++ b/config/manager/manager.yaml @@ -24,10 +24,10 @@ spec: spec: containers: - name: manager - image: neco-tenant-controller:dev + image: cattage:dev imagePullPolicy: IfNotPresent command: - - /neco-tenant-controller + - /cattage-controller env: - name: POD_NAMESPACE valueFrom: @@ -68,7 +68,7 @@ spec: initialDelaySeconds: 5 periodSeconds: 10 volumeMounts: - - mountPath: /etc/neco-tenant-controller + - mountPath: /etc/cattage name: config securityContext: runAsNonRoot: true diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index f6b3f0c..0912318 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -39,9 +39,9 @@ rules: - update - watch - apiGroups: - - "" + - cattage.cybozu.io resources: - - namespaces + - tenants verbs: - create - delete @@ -51,31 +51,31 @@ rules: - update - watch - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - - tenants + - tenants/finalizers verbs: - - create - - delete - - get - - list - - patch - update - - watch - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - - tenants/finalizers + - tenants/status verbs: + - get + - patch - update - apiGroups: - - multi-tenancy.cybozu.com + - "" resources: - - tenants/status + - namespaces verbs: + - create + - delete - get + - list - patch - update + - watch - apiGroups: - rbac.authorization.k8s.io resources: diff --git a/config/rbac/tenant_editor_role.yaml b/config/rbac/tenant_editor_role.yaml index d94921c..f8ddcd8 100644 --- a/config/rbac/tenant_editor_role.yaml +++ b/config/rbac/tenant_editor_role.yaml @@ -5,7 +5,7 @@ metadata: name: tenant-editor-role rules: - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - tenants verbs: @@ -17,7 +17,7 @@ rules: - update - watch - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - tenants/status verbs: diff --git a/config/rbac/tenant_viewer_role.yaml b/config/rbac/tenant_viewer_role.yaml index e561876..eb4b80c 100644 --- a/config/rbac/tenant_viewer_role.yaml +++ b/config/rbac/tenant_viewer_role.yaml @@ -5,7 +5,7 @@ metadata: name: tenant-viewer-role rules: - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - tenants verbs: @@ -13,7 +13,7 @@ rules: - list - watch - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io resources: - tenants/status verbs: diff --git a/config/samples/01_tenant.yaml b/config/samples/01_tenant.yaml index 7b9cadb..1110625 100644 --- a/config/samples/01_tenant.yaml +++ b/config/samples/01_tenant.yaml @@ -1,4 +1,4 @@ -apiVersion: multi-tenancy.cybozu.com/v1beta1 +apiVersion: cattage.cybozu.io/v1beta1 kind: Tenant metadata: name: a-team @@ -6,7 +6,7 @@ spec: namespaces: - name: app-a --- -apiVersion: multi-tenancy.cybozu.com/v1beta1 +apiVersion: cattage.cybozu.io/v1beta1 kind: Tenant metadata: name: b-team diff --git a/config/samples/03_application.yaml b/config/samples/03_application.yaml index c0c1234..89301ff 100644 --- a/config/samples/03_application.yaml +++ b/config/samples/03_application.yaml @@ -10,7 +10,7 @@ metadata: spec: project: a-team source: - repoURL: https://github.com/cybozu-go/neco-tenant-controller.git + repoURL: https://github.com/cybozu-go/cattage.git targetRevision: main path: samples/argocd-config destination: diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 510901b..6a6bbfd 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -37,7 +37,7 @@ webhooks: name: mtenant.kb.io rules: - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io apiVersions: - v1beta1 operations: @@ -85,7 +85,7 @@ webhooks: name: vtenant.kb.io rules: - apiGroups: - - multi-tenancy.cybozu.com + - cattage.cybozu.io apiVersions: - v1beta1 operations: diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 65b955a..504ef52 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -6,10 +6,10 @@ import ( "fmt" "strings" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" - extract "github.com/cybozu-go/neco-tenant-controller/pkg/client" - "github.com/cybozu-go/neco-tenant-controller/pkg/config" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + "github.com/cybozu-go/cattage/pkg/argocd" + extract "github.com/cybozu-go/cattage/pkg/client" + "github.com/cybozu-go/cattage/pkg/config" + "github.com/cybozu-go/cattage/pkg/constants" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" apierrors "k8s.io/apimachinery/pkg/api/errors" diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index 00bb23c..4e65b3c 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -5,10 +5,10 @@ import ( "errors" "time" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" - cacheclient "github.com/cybozu-go/neco-tenant-controller/pkg/client" - tenantconfig "github.com/cybozu-go/neco-tenant-controller/pkg/config" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + "github.com/cybozu-go/cattage/pkg/argocd" + cacheclient "github.com/cybozu-go/cattage/pkg/client" + tenantconfig "github.com/cybozu-go/cattage/pkg/config" + "github.com/cybozu-go/cattage/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" @@ -63,7 +63,7 @@ var _ = Describe("Application controller", func() { AppProjectTemplate: "", }, } - ar := NewApplicationReconciler(mgr.GetClient(), mgr.GetEventRecorderFor("neco-tenant-controller"), config) + ar := NewApplicationReconciler(mgr.GetClient(), mgr.GetEventRecorderFor("cattage"), config) err = ar.SetupWithManager(ctx, mgr) Expect(err).ToNot(HaveOccurred()) diff --git a/controllers/suite_test.go b/controllers/suite_test.go index a52b7de..f1ac846 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - multitenancyv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -76,7 +76,7 @@ var _ = BeforeSuite(func() { scheme = runtime.NewScheme() err = clientgoscheme.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) - err = multitenancyv1beta1.AddToScheme(scheme) + err = cattagev1beta1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) //+kubebuilder:scaffold:scheme diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 983c992..0f86533 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -22,11 +22,11 @@ import ( "fmt" "text/template" - tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" - extract "github.com/cybozu-go/neco-tenant-controller/pkg/client" - "github.com/cybozu-go/neco-tenant-controller/pkg/config" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/argocd" + extract "github.com/cybozu-go/cattage/pkg/client" + "github.com/cybozu-go/cattage/pkg/config" + "github.com/cybozu-go/cattage/pkg/constants" corev1 "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -66,9 +66,9 @@ type TenantReconciler struct { config *config.Config } -//+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=multi-tenancy.cybozu.com,resources=tenants/finalizers,verbs=update +//+kubebuilder:rbac:groups=cattage.cybozu.io,resources=tenants,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=cattage.cybozu.io,resources=tenants/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=cattage.cybozu.io,resources=tenants/finalizers,verbs=update //+kubebuilder:rbac:groups=argoproj.io,resources=appprojects,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch;create;update;patch;delete //+kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete @@ -86,7 +86,7 @@ type TenantReconciler struct { func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) { logger := log.FromContext(ctx) - tenant := &tenantv1beta1.Tenant{} + tenant := &cattagev1beta1.Tenant{} if err := r.client.Get(ctx, req.NamespacedName, tenant); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) } @@ -98,7 +98,7 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return ctrl.Result{}, nil } - defer func(before tenantv1beta1.TenantStatus) { + defer func(before cattagev1beta1.TenantStatus) { if !equality.Semantic.DeepEqual(tenant.Status, before) { logger.Info("update status", "status", tenant.Status, "before", before) if err2 := r.client.Status().Update(ctx, tenant); err2 != nil { @@ -110,9 +110,9 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res err = r.reconcileNamespaces(ctx, tenant) if err != nil { - tenant.Status.Health = tenantv1beta1.TenantUnhealthy + tenant.Status.Health = cattagev1beta1.TenantUnhealthy meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ - Type: tenantv1beta1.ConditionReady, + Type: cattagev1beta1.ConditionReady, Status: metav1.ConditionFalse, Reason: "Failed", Message: err.Error(), @@ -122,9 +122,9 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res err = r.reconcileArgoCD(ctx, tenant) if err != nil { - tenant.Status.Health = tenantv1beta1.TenantUnhealthy + tenant.Status.Health = cattagev1beta1.TenantUnhealthy meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ - Type: tenantv1beta1.ConditionReady, + Type: cattagev1beta1.ConditionReady, Status: metav1.ConditionFalse, Reason: "Failed", Message: err.Error(), @@ -132,9 +132,9 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return ctrl.Result{}, err } - tenant.Status.Health = tenantv1beta1.TenantHealthy + tenant.Status.Health = cattagev1beta1.TenantHealthy meta.SetStatusCondition(&tenant.Status.Conditions, metav1.Condition{ - Type: tenantv1beta1.ConditionReady, + Type: cattagev1beta1.ConditionReady, Status: metav1.ConditionTrue, Reason: "OK", }) @@ -143,7 +143,7 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return ctrl.Result{}, nil } -func containNamespace(roots []tenantv1beta1.NamespaceSpec, ns corev1.Namespace) bool { +func containNamespace(roots []cattagev1beta1.NamespaceSpec, ns corev1.Namespace) bool { for _, root := range roots { if root.Name == ns.Name { return true @@ -171,7 +171,7 @@ func (r *TenantReconciler) disownNamespace(ctx context.Context, ns *corev1.Names return nil } -func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *tenantv1beta1.Tenant, ns *corev1.Namespace) error { +func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *cattagev1beta1.Tenant, ns *corev1.Namespace) error { rb := &rbacv1.RoleBinding{} err := r.client.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: tenant.Name + "-admin"}, rb) if apierrors.IsNotFound(err) { @@ -190,7 +190,7 @@ func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *tenantv1beta1 return nil } -func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *tenantv1beta1.Tenant) error { +func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *cattagev1beta1.Tenant) error { proj := argocd.AppProject() err := r.client.Get(ctx, client.ObjectKey{Namespace: r.config.ArgoCD.Namespace, Name: tenant.Name}, proj) if apierrors.IsNotFound(err) { @@ -205,7 +205,7 @@ func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *tenantv return r.client.Delete(ctx, proj) } -func (r *TenantReconciler) finalize(ctx context.Context, tenant *tenantv1beta1.Tenant) error { +func (r *TenantReconciler) finalize(ctx context.Context, tenant *cattagev1beta1.Tenant) error { logger := log.FromContext(ctx) if !controllerutil.ContainsFinalizer(tenant, constants.Finalizer) { return nil @@ -300,7 +300,7 @@ func (r *TenantReconciler) patchRoleBinding(ctx context.Context, rb *acrbacv1.Ro }) } -func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *tenantv1beta1.Tenant) error { +func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *cattagev1beta1.Tenant) error { for _, ns := range tenant.Spec.Namespaces { namespace := accorev1.Namespace(ns.Name) labels := make(map[string]string) @@ -380,7 +380,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *tena return nil } -func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *tenantv1beta1.Tenant) error { +func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev1beta1.Tenant) error { logger := log.FromContext(ctx) orig := argocd.AppProject() @@ -480,7 +480,7 @@ func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { } return ctrl.NewControllerManagedBy(mgr). - For(&tenantv1beta1.Tenant{}). + For(&cattagev1beta1.Tenant{}). Watches(&source.Kind{Type: &corev1.Namespace{}}, funcs). Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, funcs). Watches(&source.Kind{Type: argocd.AppProject()}, funcs). diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 1ade34a..8c5a146 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -6,11 +6,11 @@ import ( "errors" "time" - tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" - cacheclient "github.com/cybozu-go/neco-tenant-controller/pkg/client" - tenantconfig "github.com/cybozu-go/neco-tenant-controller/pkg/config" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/argocd" + cacheclient "github.com/cybozu-go/cattage/pkg/client" + tenantconfig "github.com/cybozu-go/cattage/pkg/config" + "github.com/cybozu-go/cattage/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" @@ -81,12 +81,12 @@ var _ = Describe("Tenant controller", func() { }) It("should create root namespaces, rolebindings and an appproject", func() { - tenant := &tenantv1beta1.Tenant{ + tenant := &cattagev1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "x-team", }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ + Spec: cattagev1beta1.TenantSpec{ + Namespaces: []cattagev1beta1.NamespaceSpec{ { Name: "app-x", Labels: map[string]string{ @@ -100,7 +100,7 @@ var _ = Describe("Tenant controller", func() { }, }, }, - ArgoCD: tenantv1beta1.ArgoCDSpec{ + ArgoCD: cattagev1beta1.ArgoCDSpec{ ExtraAdmins: []string{ "d-team", }, @@ -194,16 +194,16 @@ var _ = Describe("Tenant controller", func() { }) It("should disown root namespace", func() { - tenant := &tenantv1beta1.Tenant{ + tenant := &cattagev1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "y-team", }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ + Spec: cattagev1beta1.TenantSpec{ + Namespaces: []cattagev1beta1.NamespaceSpec{ {Name: "app-y1"}, {Name: "app-y2"}, }, - ArgoCD: tenantv1beta1.ArgoCDSpec{}, + ArgoCD: cattagev1beta1.ArgoCDSpec{}, }, } err := k8sClient.Create(ctx, tenant) @@ -312,7 +312,7 @@ var _ = Describe("Tenant controller", func() { By("removing app-y2") err = k8sClient.Get(ctx, client.ObjectKey{Name: tenant.Name}, tenant) Expect(err).ToNot(HaveOccurred()) - tenant.Spec.Namespaces = []tenantv1beta1.NamespaceSpec{ + tenant.Spec.Namespaces = []cattagev1beta1.NamespaceSpec{ {Name: "app-y1"}, } err = k8sClient.Update(ctx, tenant) @@ -366,23 +366,23 @@ var _ = Describe("Tenant controller", func() { By("removing app-y1") err = k8sClient.Get(ctx, client.ObjectKey{Name: tenant.Name}, tenant) Expect(err).ToNot(HaveOccurred()) - tenant.Spec.Namespaces = []tenantv1beta1.NamespaceSpec{} + tenant.Spec.Namespaces = []cattagev1beta1.NamespaceSpec{} err = k8sClient.Update(ctx, tenant) Expect(err).To(HaveOccurred()) Expect(err.Error()).Should(ContainSubstring("\"y-team\" is invalid: spec.namespaces: Invalid value: 0: spec.namespaces in body should have at least 1 items")) }) It("should remove tenant", func() { - tenant := &tenantv1beta1.Tenant{ + tenant := &cattagev1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "z-team", Finalizers: []string{constants.Finalizer}, }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ + Spec: cattagev1beta1.TenantSpec{ + Namespaces: []cattagev1beta1.NamespaceSpec{ {Name: "app-z"}, }, - ArgoCD: tenantv1beta1.ArgoCDSpec{}, + ArgoCD: cattagev1beta1.ArgoCDSpec{}, }, } err := k8sClient.Create(ctx, tenant) diff --git a/docs/README.md b/docs/README.md index 4f918cc..afed839 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,9 +1,9 @@ -# neco-tenant-controller documentation +# Cattage documentation -neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. +Cattage is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. It is currently developed and maintained by [Cybozu](https://cybozu-global.com/). -The repository is at https://github.com/cybozu-go/neco-tenant-controller . +The repository is at https://github.com/cybozu-go/cattage . [Accurate]: https://github.com/cybozu-go/accurate [Argo CD]: https://argo-cd.readthedocs.io/en/stable/ diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index 6930e9f..0f6a5b8 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -1,6 +1,6 @@ # Summary -[neco-tenant-controller](README.md) +[Cattage](README.md) # User manual diff --git a/docs/book.toml b/docs/book.toml index a34d3bf..317560f 100644 --- a/docs/book.toml +++ b/docs/book.toml @@ -2,9 +2,9 @@ language = "en" multilingual = false src = "." -title = "neco-tenant-controller Documentation" +title = "Cattage Documentation" [output.html] -git-repository-url = "https://github.com/cybozu-go/neco-tenant-controller" -edit-url-template = "https://github.com/cybozu-go/neco-tenant-controller/edit/main/docs/{path}" -site-url = "/neco-tenant-controller/" +git-repository-url = "https://github.com/cybozu-go/cattage" +edit-url-template = "https://github.com/cybozu-go/cattage/edit/main/docs/{path}" +site-url = "/cattage/" diff --git a/docs/config.md b/docs/config.md index 36dd4b3..ca1961a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -2,7 +2,7 @@ ## Configuration file -`neco-tenant-controller` reads a configuration file on startup. The default location is `/etc/neco-tenant-controller/config.yaml`. +`cattage-controller` reads a configuration file on startup. The default location is `/etc/cattage/config.yaml`. The location can be changed with `--config-file` flag. The configuration file should be a JSON or YAML file having the following keys: @@ -82,9 +82,9 @@ argocd: ## Environment variables -| Name | Required | Description | -|-----------------|----------|---------------------------------------------------------------| -| `POD_NAMESPACE` | Yes | The namespace name where `neco-tenant-controller` is running. | +| Name | Required | Description | +|-----------------|----------|------------------------------------------------| +| `POD_NAMESPACE` | Yes | The namespace name where `cattage` is running. | ## Command-line flags @@ -93,10 +93,10 @@ Flags: --add_dir_header If true, adds the file directory to the header --alsologtostderr log to standard error as well as files --cert-dir string webhook certificate directory - --config-file string Configuration file path (default "/etc/neco-tenant-controller/config.yaml") + --config-file string Configuration file path (default "/etc/cattage/config.yaml") --health-probe-addr string Listen address for health probes (default ":8081") - -h, --help help for neco-tenant-controller - --leader-election-id string ID for leader election by controller-runtime (default "neco-tenant-controller") + -h, --help help for cattage-controller + --leader-election-id string ID for leader election by controller-runtime (default "cattage") --log_backtrace_at traceLocation when logging hits line file:N, emit a stack trace (default :0) --log_dir string If non-empty, write log files in this directory --log_file string If non-empty, use this log file @@ -107,7 +107,7 @@ Flags: --skip_log_headers If true, avoid headers when opening log files --stderrthreshold severity logs at or above this threshold go to stderr (default 2) -v, --v Level number for the log level verbosity - --version version for neco-tenant-controller + --version version for cattage-controller --vmodule moduleSpec comma-separated list of pattern=N settings for file-filtered logging --webhook-addr string Listen address for the webhook endpoint (default ":9443") --zap-devel Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 1b50430..2400bbf 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -63,8 +63,8 @@ TenantSpec defines the desired state of Tenant | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | -| namespaces | | [][NamespaceSpec](#namespacespec) | false | -| argocd | | [ArgoCDSpec](#argocdspec) | false | +| namespaces | Namespaces are the list of root namespaces that belong to this tenant | [][NamespaceSpec](#namespacespec) | true | +| argocd | ArgoCD is the settings of Argo CD for this tenant | [ArgoCDSpec](#argocdspec) | false | [Back to Custom Resources](#custom-resources) @@ -74,6 +74,7 @@ TenantStatus defines the observed state of Tenant | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | +| health | Health is the health of Tenant. | TenantHealth | false | | conditions | Conditions is an array of conditions. | []metav1.Condition | false | [Back to Custom Resources](#custom-resources) diff --git a/docs/design.md b/docs/design.md index df7ec53..ef6c662 100644 --- a/docs/design.md +++ b/docs/design.md @@ -2,7 +2,7 @@ ## Overview -neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. +Cattage is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. ## Motivation diff --git a/docs/development.md b/docs/development.md index b50fb74..8726600 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,18 +1,18 @@ # Development -neco-tenant-controller can be developed in Tilt. +Cattage can be developed in Tilt. 1. Prepare a Linux box running Docker. 2. Checkout this repository. ```console - $ git clone https://github.com/cybozu-go/neco-tenant-controller + $ git clone https://github.com/cybozu-go/cattage ``` 3. Launch local Kubernetes cluster. ```console - $ cd neco-tenant-controller + $ cd cattage $ make dev ``` diff --git a/docs/overview.md b/docs/overview.md index 665ecee..2a72b5e 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -1,6 +1,6 @@ # Overview -neco-tenant-controller is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. +Cattage is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][] with [Accurate][]. ## Features @@ -21,14 +21,14 @@ neco-tenant-controller is a Kubernetes controller that enhances the multi-tenanc - The ownership of sub-namespaces can be changed between tenants Sometimes users may want to move the ownership of an application to another tenant. - When the parent of a sub-namespace is changed, neco-tenant-controller will automatically update the permissions. + When the parent of a sub-namespace is changed, Cattage will automatically update the permissions. ### For Tenant Users - Sync Argo CD Application resources Tenant users can create Application resources in their sub-namespaces without `argocd` command. - neco-tenant-controller will synchronize Application resource between the tenant namespace and argocd namespace. + Cattage will synchronize Application resource between the tenant namespace and argocd namespace. It allows for [App Of Apps Pattern][] in multi-tenancy environments. - Validate Argo CD Application resources diff --git a/docs/setup.md b/docs/setup.md index e5a6d80..e5a5953 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -2,7 +2,7 @@ ## Kubernetes cluster -neco-tenant-controller is a controller that runs in a soft multi-tenancy Kubernetes cluster. +Cattage is a controller that runs in a soft multi-tenancy Kubernetes cluster. Namespaces must be isolated for each tenant. There are many ways to achieve Namespace isolation. @@ -15,14 +15,14 @@ Install Argo CD as shown in the following page: https://argo-cd.readthedocs.io/en/stable/getting_started/ -neco-tenant-controller isolates AppProject resource for each tenant. +Cattage isolates AppProject resource for each tenant. So, please refer to the following page to enable user management. Argo CD supports a lot of authentication methods. https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/ -neco-tenant-controller expects tenant users to be able to create Application resources. +Cattage expects tenant users to be able to create Application resources. Apply the following manifest: ```yaml @@ -50,7 +50,7 @@ rules: ## cert-manager -neco-tenant-controller and Accurate depend on [cert-manager][] to issue TLS certificate for admission webhooks. +Cattage and Accurate depend on [cert-manager][] to issue TLS certificate for admission webhooks. If cert-manager is not installed on your cluster, install it as follows: ```console @@ -60,8 +60,8 @@ $ kubectl apply -f cert-manager.yaml ## Accurate -neco-tenant-controller depends on Accurate. -It expects `multi-tenancy.cybozu.com/tenant` labels and RoleBinding resources to be propagated. +Cattage depends on Accurate. +It expects `cattage.cybozu.io/tenant` labels and RoleBinding resources to be propagated. Include the following settings in your values.yaml: @@ -69,7 +69,7 @@ Include the following settings in your values.yaml: controller: config: labelKeys: - - multi-tenancy.cybozu.com/tenant + - cattage.cybozu.io/tenant watches: - group: rbac.authorization.k8s.io version: v1 @@ -86,7 +86,7 @@ For more information, see the following page: https://cybozu-go.github.io/accurate/helm.html -## neco-tenant-controller +## Cattage Prepare values.yaml as follows: @@ -140,12 +140,12 @@ Read [Configurations](config.md) for details. Setup Helm repository: ```console - $ helm repo add neco-tenant-controller https://cybozu-go.github.io/neco-tenant-controller/ + $ helm repo add cattage https://cybozu-go.github.io/cattage/ $ helm repo update ``` Install the Helm chart with your values.yaml: ```console -$ helm install --create-namespace --namespace neco-tenant-controller neco-tenant-controller neco-tenant-controller/neco-tenant-controller -f values.yaml +$ helm install --create-namespace --namespace cattage cattage cattage/cattage -f values.yaml ``` diff --git a/docs/usage.md b/docs/usage.md index 2041882..99cc2ec 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -5,7 +5,7 @@ Administrators can create the following tenant resource for a tenant team. ```yaml -apiVersion: multi-tenancy.cybozu.com/v1beta1 +apiVersion: cattage.cybozu.io/v1beta1 kind: Tenant metadata: name: your-team @@ -60,7 +60,7 @@ metadata: spec: project: your-team source: - repoURL: https://github.com/cybozu-go/neco-tenant-controller.git + repoURL: https://github.com/cybozu-go/cattage.git targetRevision: main path: samples/testhttpd destination: @@ -108,7 +108,7 @@ The ownership of sub-namespace can be transferred to other tenant. Prepare a new tenant: ```yaml -apiVersion: multi-tenancy.cybozu.com/v1beta1 +apiVersion: cattage.cybozu.io/v1beta1 kind: Tenant metadata: name: new-team diff --git a/e2e/Makefile b/e2e/Makefile index 5de54a2..5554af0 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -22,12 +22,12 @@ help: .PHONY: start start: $(KIND) $(KUBECTL) - $(KIND) create cluster --name=neco-tenant-controller --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m - cd ..; docker build --no-cache -t neco-tenant-controller:dev . - $(KIND) load docker-image neco-tenant-controller:dev --name=neco-tenant-controller + $(KIND) create cluster --name=cattage --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m + cd ..; docker build --no-cache -t cattage:dev . + $(KIND) load docker-image cattage:dev --name=cattage $(MAKE) prepare $(KUBECTL) apply -k ../config/default - $(KUBECTL) -n neco-tenant-controller-system wait --for=condition=available --timeout=180s --all deployments + $(KUBECTL) -n cattage wait --for=condition=available --timeout=180s --all deployments .PHONY: prepare prepare: $(KUBECTL) $(HELM) @@ -49,14 +49,14 @@ test: .PHONY: logs logs: rm -rf logs.tar.gz logs - $(KIND) export logs --name=neco-tenant-controller ./logs + $(KIND) export logs --name=cattage ./logs tar czf logs.tar.gz logs rm -rf logs .PHONY: stop stop: $(KIND) - $(KIND) delete cluster --name=neco-tenant-controller - -docker image rm neco-tenant-controller:dev + $(KIND) delete cluster --name=cattage + -docker image rm cattage:dev -docker image prune -f .PHONY: kind diff --git a/e2e/accurate-values.yaml b/e2e/accurate-values.yaml index 8f2a9ac..39a0654 100644 --- a/e2e/accurate-values.yaml +++ b/e2e/accurate-values.yaml @@ -1,7 +1,7 @@ controller: config: labelKeys: - - multi-tenancy.cybozu.com/tenant + - cattage.cybozu.io/tenant watches: - group: rbac.authorization.k8s.io version: v1 diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 5fd8b9f..ef194db 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -5,13 +5,13 @@ import ( "encoding/json" "errors" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" + "github.com/cybozu-go/cattage/pkg/argocd" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" ) -var _ = Describe("neco-tenant-controller", func() { +var _ = Describe("Cattage", func() { It("should prepare", func() { Eventually(func() error { _, err := kubectl(nil, "apply", "-f", "../config/samples/00_template.yaml") diff --git a/go.mod b/go.mod index ed224f3..3e3c291 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/cybozu-go/neco-tenant-controller +module github.com/cybozu-go/cattage go 1.16 diff --git a/hooks/application.go b/hooks/application.go index cf436ec..18a5538 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -7,9 +7,9 @@ import ( "fmt" "net/http" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" - "github.com/cybozu-go/neco-tenant-controller/pkg/config" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + "github.com/cybozu-go/cattage/pkg/argocd" + "github.com/cybozu-go/cattage/pkg/config" + "github.com/cybozu-go/cattage/pkg/constants" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/hooks/application_test.go b/hooks/application_test.go index 3c00312..03fa6d1 100644 --- a/hooks/application_test.go +++ b/hooks/application_test.go @@ -3,8 +3,8 @@ package hooks import ( "context" - "github.com/cybozu-go/neco-tenant-controller/pkg/argocd" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + "github.com/cybozu-go/cattage/pkg/argocd" + "github.com/cybozu-go/cattage/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" diff --git a/hooks/suite_test.go b/hooks/suite_test.go index c627c0d..bb39b11 100644 --- a/hooks/suite_test.go +++ b/hooks/suite_test.go @@ -26,9 +26,9 @@ import ( "time" //+kubebuilder:scaffold:imports - tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/pkg/config" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/config" + "github.com/cybozu-go/cattage/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" admissionv1beta1 "k8s.io/api/admission/v1beta1" @@ -79,7 +79,7 @@ var _ = BeforeSuite(func() { Expect(cfg).NotTo(BeNil()) scheme := runtime.NewScheme() - err = tenantv1beta1.AddToScheme(scheme) + err = cattagev1beta1.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) err = clientgoscheme.AddToScheme(scheme) Expect(err).NotTo(HaveOccurred()) diff --git a/hooks/tenant.go b/hooks/tenant.go index b4c31bb..fd45718 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -21,9 +21,9 @@ import ( "encoding/json" "net/http" - tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/pkg/config" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/config" + "github.com/cybozu-go/cattage/pkg/constants" admissionv1 "k8s.io/api/admission/v1" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -33,7 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -//+kubebuilder:webhook:path=/mutate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=mtenant.kb.io,admissionReviewVersions={v1} +//+kubebuilder:webhook:path=/mutate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=cattage.cybozu.io,resources=tenants,verbs=create;update,versions=v1beta1,name=mtenant.kb.io,admissionReviewVersions={v1} type tenantMutator struct { dec *admission.Decoder @@ -46,7 +46,7 @@ func (m *tenantMutator) Handle(ctx context.Context, req admission.Request) admis return admission.Allowed("") } - tenant := &tenantv1beta1.Tenant{} + tenant := &cattagev1beta1.Tenant{} if err := m.dec.Decode(req, tenant); err != nil { return admission.Errored(http.StatusBadRequest, err) } @@ -60,7 +60,7 @@ func (m *tenantMutator) Handle(ctx context.Context, req admission.Request) admis return admission.PatchResponseFromRaw(req.Object.Raw, data) } -//+kubebuilder:webhook:path=/validate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=multi-tenancy.cybozu.com,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions={v1} +//+kubebuilder:webhook:path=/validate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=cattage.cybozu.io,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions={v1} type tenantValidator struct { client client.Client @@ -75,7 +75,7 @@ func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) adm return admission.Allowed("") } - tenant := &tenantv1beta1.Tenant{} + tenant := &cattagev1beta1.Tenant{} if err := v.dec.Decode(req, tenant); err != nil { return admission.Errored(http.StatusBadRequest, err) } diff --git a/hooks/tenant_test.go b/hooks/tenant_test.go index 19460ca..80cb767 100644 --- a/hooks/tenant_test.go +++ b/hooks/tenant_test.go @@ -3,8 +3,8 @@ package hooks import ( "context" - tenantv1beta1 "github.com/cybozu-go/neco-tenant-controller/api/v1beta1" - "github.com/cybozu-go/neco-tenant-controller/pkg/constants" + cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/constants" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -15,12 +15,12 @@ var _ = Describe("Tenant webhook", func() { ctx := context.Background() It("should allow creating a tenant", func() { - tenant := &tenantv1beta1.Tenant{ + tenant := &cattagev1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "a-team", }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ + Spec: cattagev1beta1.TenantSpec{ + Namespaces: []cattagev1beta1.NamespaceSpec{ { Name: "app-new", }, @@ -37,12 +37,12 @@ var _ = Describe("Tenant webhook", func() { }) It("should deny creating a tenant with other owner's namespace", func() { - tenant := &tenantv1beta1.Tenant{ + tenant := &cattagev1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "b-team", }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ + Spec: cattagev1beta1.TenantSpec{ + Namespaces: []cattagev1beta1.NamespaceSpec{ { Name: "app-y-team", }, @@ -55,12 +55,12 @@ var _ = Describe("Tenant webhook", func() { }) It("should deny creating a tenant with template namespace", func() { - tenant := &tenantv1beta1.Tenant{ + tenant := &cattagev1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "d-team", }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ + Spec: cattagev1beta1.TenantSpec{ + Namespaces: []cattagev1beta1.NamespaceSpec{ { Name: "template", }, @@ -73,12 +73,12 @@ var _ = Describe("Tenant webhook", func() { }) It("should deny creating a tenant with other group's namespace", func() { - tenant := &tenantv1beta1.Tenant{ + tenant := &cattagev1beta1.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e-team", }, - Spec: tenantv1beta1.TenantSpec{ - Namespaces: []tenantv1beta1.NamespaceSpec{ + Spec: cattagev1beta1.TenantSpec{ + Namespaces: []cattagev1beta1.NamespaceSpec{ { Name: "sub-2", }, diff --git a/pkg/config/types.go b/pkg/config/types.go index 2d90fdc..1e9c2e5 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -10,7 +10,7 @@ import ( "sigs.k8s.io/yaml" ) -// Config represents the configuration file of neco-tenant-controller. +// Config represents the configuration file of cattage. type Config struct { Namespace NamespaceConfig `json:"namespace,omitempty"` ArgoCD ArgoCDConfig `json:"argocd,omitempty"` diff --git a/pkg/constants/indexer.go b/pkg/constants/indexer.go index b04f6a0..dc4e407 100644 --- a/pkg/constants/indexer.go +++ b/pkg/constants/indexer.go @@ -1,4 +1,4 @@ package constants -const RootNamespaces = "neco-tenant-controller.namespaces.root" -const TenantNamespaces = "neco-tenant-controller.namespaces.tenant" +const RootNamespaces = "cattage.namespaces.root" +const TenantNamespaces = "cattage.namespaces.tenant" diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go index e9fe1a8..b950e89 100644 --- a/pkg/constants/meta.go +++ b/pkg/constants/meta.go @@ -1,7 +1,7 @@ package constants // MetaPrefix is the MetaPrefix for labels, annotations, and finalizers of Accurate. -const MetaPrefix = "multi-tenancy.cybozu.com/" +const MetaPrefix = "cattage.cybozu.io/" // Finalizer is the finalizer ID of Accurate. const Finalizer = MetaPrefix + "finalizer" @@ -10,7 +10,7 @@ const OwnerTenant = MetaPrefix + "tenant" const OwnerAppNamespace = MetaPrefix + "owner-namespace" -const FieldManager = MetaPrefix + "neco-tenant-controller" +const FieldManager = MetaPrefix + "cattage" const StatusFieldManager = FieldManager + "/status" const SpecFieldManager = FieldManager + "/spec" const ProjectFieldManager = FieldManager + "/project" diff --git a/version.go b/version.go index 2a6bfaf..656dd85 100644 --- a/version.go +++ b/version.go @@ -1,4 +1,4 @@ -package tenant +package cattage -// Version represents the version of neco-tenant-controller. +// Version represents the version of cattage. const Version = "0.1.0" From 0f61f18e9e80ee7b6b3652c824a846621b05a2bf Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 01:35:08 +0900 Subject: [PATCH 45/89] Update controller-tools Signed-off-by: zoetrope --- Makefile | 2 +- api/v1beta1/zz_generated.deepcopy.go | 1 + charts/cattage/crds/tenant.yaml | 15 +++++++-------- config/crd/bases/cattage.cybozu.io_tenants.yaml | 16 +++++++--------- config/rbac/role.yaml | 1 - config/webhook/manifests.yaml | 2 -- 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 3194c20..8885e0d 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ # Tool versions -CTRL_TOOLS_VERSION=0.7.0 +CTRL_TOOLS_VERSION=0.8.0 CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) KUSTOMIZE_VERSION = 4.4.1 HELM_VERSION = 3.7.1 diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 556e595..ded9756 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -1,3 +1,4 @@ +//go:build !ignore_autogenerated // +build !ignore_autogenerated // Code generated by controller-gen. DO NOT EDIT. diff --git a/charts/cattage/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml index 058564b..be48f4c 100644 --- a/charts/cattage/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -2,7 +2,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.7.0 + controller-gen.kubebuilder.io/version: v0.8.0 labels: app.kubernetes.io/name: cattage name: tenants.cattage.cybozu.io @@ -93,13 +93,12 @@ spec: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + type FooStatus struct{ // Represents the observations of a foo's + current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index c19d3a8..4c7f0ec 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -1,10 +1,9 @@ - --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.7.0 + controller-gen.kubebuilder.io/version: v0.8.0 creationTimestamp: null name: tenants.cattage.cybozu.io spec: @@ -94,13 +93,12 @@ spec: description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a - foo's current state. // Known .status.conditions.type are: - \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type - \ // +patchStrategy=merge // +listType=map // +listMapKey=type - \ Conditions []metav1.Condition `json:\"conditions,omitempty\" - patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` - \n // other fields }" + type FooStatus struct{ // Represents the observations of a foo's + current state. // Known .status.conditions.type are: \"Available\", + \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge + // +listType=map // +listMapKey=type Conditions []metav1.Condition + `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" + protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" properties: lastTransitionTime: description: lastTransitionTime is the last time the condition diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 0912318..2617cd5 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -1,4 +1,3 @@ - --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 6a6bbfd..07c16d3 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -1,4 +1,3 @@ - --- apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration @@ -46,7 +45,6 @@ webhooks: resources: - tenants sideEffects: None - --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration From 333861e238cec04fb905962c93989d7c1e4cb873 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 27 Dec 2021 09:01:03 +0900 Subject: [PATCH 46/89] Update ctlptl Signed-off-by: zoetrope --- Makefile | 2 +- cluster.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 8885e0d..47d1a03 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ HELM_VERSION = 3.7.1 CRD_TO_MARKDOWN_VERSION = 0.0.3 MDBOOK_VERSION = 0.4.15 TILT_VERSION = 0.23.4 -CTLPTL_VERSION = 0.6.2 +CTLPTL_VERSION = 0.7.0 ARGOCD_VERSION = 2.2.2 # Test tools diff --git a/cluster.yaml b/cluster.yaml index bf7cf70..45e03be 100644 --- a/cluster.yaml +++ b/cluster.yaml @@ -7,5 +7,5 @@ apiVersion: ctlptl.dev/v1alpha1 kind: Cluster name: kind-dev product: kind -kubernetesVersion: v1.21.4 +kubernetesVersion: v1.22.0 registry: ctlptl-registry From c7a593f4a70d06b1c7f0ef7d37b4a226a4b6a7ab Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 12 Jan 2022 16:28:36 +0900 Subject: [PATCH 47/89] Use aqua to manage CLI tools Signed-off-by: zoetrope --- .github/workflows/ci.yaml | 8 +- .github/workflows/helm.yaml | 10 +- .github/workflows/mdbook.yaml | 3 + Makefile | 98 ++----- aqua.yaml | 21 ++ cluster.yaml | 2 +- docs/development.md | 33 ++- e2e/Makefile | 71 ++--- e2e/env_test.go | 3 +- e2e/run_test.go | 2 +- go.mod | 91 ++++++- go.sum | 473 +++++++++++++++++++++++++++++----- registry.yaml | 11 + tools.go | 9 + 14 files changed, 612 insertions(+), 223 deletions(-) create mode 100644 aqua.yaml create mode 100644 registry.yaml create mode 100644 tools.go diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index bcfc58e..9b43d79 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -25,6 +25,9 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.go-version }} + - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + with: + aqua_version: v0.10.0 - run: make test - run: make check-generate - run: make envtest @@ -32,13 +35,16 @@ jobs: name: End-to-End Tests strategy: matrix: - k8s-version: ["1.22.2"] + k8s-version: ["1.22.4", "1.23.1"] runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: go-version: ${{ env.go-version }} + - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + with: + aqua_version: v0.10.0 - run: make start KUBERNETES_VERSION=${{ matrix.k8s-version }} working-directory: e2e - run: make test diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml index effecaf..e1ded9f 100644 --- a/.github/workflows/helm.yaml +++ b/.github/workflows/helm.yaml @@ -11,10 +11,9 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - - name: Set up Helm - uses: azure/setup-helm@v1 + - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab with: - version: v3.7.1 + aqua_version: v0.10.0 - name: Check chart version run: | tag_version=${GITHUB_REF##*/chart-v} @@ -38,10 +37,9 @@ jobs: - uses: actions/checkout@v2 with: ref: gh-pages - - name: Set up Helm - uses: azure/setup-helm@v1 + - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab with: - version: v3.7.1 + aqua_version: v0.10.0 - uses: actions/download-artifact@v2 with: name: helm-charts diff --git a/.github/workflows/mdbook.yaml b/.github/workflows/mdbook.yaml index bf305cf..8722c66 100644 --- a/.github/workflows/mdbook.yaml +++ b/.github/workflows/mdbook.yaml @@ -10,6 +10,9 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 + - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + with: + aqua_version: v0.10.0 - run: make book - uses: actions/upload-artifact@v2 with: diff --git a/Makefile b/Makefile index 47d1a03..c2f948e 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,5 @@ # Tool versions -CTRL_TOOLS_VERSION=0.8.0 CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) -KUSTOMIZE_VERSION = 4.4.1 -HELM_VERSION = 3.7.1 -CRD_TO_MARKDOWN_VERSION = 0.0.3 -MDBOOK_VERSION = 0.4.15 -TILT_VERSION = 0.23.4 -CTLPTL_VERSION = 0.7.0 ARGOCD_VERSION = 2.2.2 # Test tools @@ -49,10 +42,10 @@ help: ## Display this help. ##@ Development .PHONY: manifests -manifests: kustomize controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. +manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - $(KUSTOMIZE) build config/helm/crds > charts/cattage/crds/tenant.yaml - $(KUSTOMIZE) build config/helm/templates > charts/cattage/templates/generated.yaml + kustomize build config/helm/crds > charts/cattage/crds/tenant.yaml + kustomize build config/helm/templates > charts/cattage/templates/generated.yaml .PHONY: generate @@ -60,13 +53,13 @@ generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." .PHONY: apidoc -apidoc: crd-to-markdown $(wildcard api/*/*_types.go) - $(CRD_TO_MARKDOWN) --links docs/links.csv -f api/v1beta1/tenant_types.go -n Tenant > docs/crd_tenant.md +apidoc: $(wildcard api/*/*_types.go) + crd-to-markdown --links docs/links.csv -f api/v1beta1/tenant_types.go -n Tenant > docs/crd_tenant.md .PHONY: book -book: mdbook +book: rm -rf docs/book - cd docs; $(MDBOOK) build + cd docs; mdbook build .PHONY: check-generate check-generate: @@ -107,84 +100,29 @@ docker-build: docker build -t cattage:latest . ##@ Development -TILT := $(shell pwd)/bin/tilt -CTLPTL := $(shell pwd)/bin/ctlptl .PHONY: dev -dev: $(CTLPTL) - $(CTLPTL) apply -f ./cluster.yaml +dev: + ctlptl apply -f ./cluster.yaml $(MAKE) -C ./e2e/ prepare .PHONY: stop-dev -stop-dev: $(CTLPTL) - $(CTLPTL) delete -f ./cluster.yaml - -.PHONY: ctlptl -ctlptl: $(CTLPTL) -$(CTLPTL): - mkdir -p bin - curl -fsL https://github.com/tilt-dev/ctlptl/releases/download/v$(CTLPTL_VERSION)/ctlptl.$(CTLPTL_VERSION).linux.x86_64.tar.gz | \ - tar -C bin -xzf - - -.PHONY: tilt -tilt: $(TILT) -$(TILT): - mkdir -p bin - curl -fsL https://github.com/tilt-dev/tilt/releases/download/v$(TILT_VERSION)/tilt.$(TILT_VERSION).linux.x86_64.tar.gz | \ - tar -C bin -xzf - +stop-dev: + ctlptl delete -f ./cluster.yaml ##@ Tools -CONTROLLER_GEN := $(shell pwd)/bin/controller-gen +CONTROLLER_GEN := $(BIN_DIR)/controller-gen +.PHONY: controller-gen controller-gen: ## Download controller-gen locally if necessary. - $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v$(CTRL_TOOLS_VERSION)) + mkdir -p $(BIN_DIR) + GOBIN=$(BIN_DIR) go install sigs.k8s.io/controller-tools/cmd/controller-gen -SETUP_ENVTEST := $(shell pwd)/bin/setup-envtest +SETUP_ENVTEST := $(BIN_DIR)/setup-envtest .PHONY: setup-envtest -setup-envtest: $(SETUP_ENVTEST) ## Download setup-envtest locally if necessary -$(SETUP_ENVTEST): +setup-envtest: ## Download setup-envtest locally if necessary # see https://github.com/kubernetes-sigs/controller-runtime/tree/master/tools/setup-envtest - GOBIN=$(shell pwd)/bin go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest - -KUSTOMIZE := $(shell pwd)/bin/kustomize -.PHONY: kustomize -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. - -$(KUSTOMIZE): - mkdir -p bin - curl -fsL https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv$(KUSTOMIZE_VERSION)/kustomize_v$(KUSTOMIZE_VERSION)_linux_amd64.tar.gz | \ - tar -C bin -xzf - - -HELM := $(shell pwd)/bin/helm -.PHONY: helm -helm: $(HELM) ## Download helm locally if necessary. - -$(HELM): - mkdir -p $(BIN_DIR) - curl -L -sS https://get.helm.sh/helm-v$(HELM_VERSION)-linux-amd64.tar.gz \ - | tar xz -C $(BIN_DIR) --strip-components 1 linux-amd64/helm - -CRD_TO_MARKDOWN := $(shell pwd)/bin/crd-to-markdown -.PHONY: crd-to-markdown -crd-to-markdown: ## Download crd-to-markdown locally if necessary. - $(call go-get-tool,$(CRD_TO_MARKDOWN),github.com/clamoriniere/crd-to-markdown@v$(CRD_TO_MARKDOWN_VERSION)) - -MDBOOK := $(shell pwd)/bin/mdbook -.PHONY: mdbook -mdbook: $(MDBOOK) ## Download mdbook locally if necessary -$(MDBOOK): - mkdir -p bin - curl -fsL https://github.com/rust-lang/mdBook/releases/download/v$(MDBOOK_VERSION)/mdbook-v$(MDBOOK_VERSION)-x86_64-unknown-linux-gnu.tar.gz | tar -C bin -xzf - - -# 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 ;\ -echo "Downloading $(2)" ;\ -GOBIN=$(PROJECT_DIR)/bin go install $(2) ;\ -} -endef + GOBIN=$(BIN_DIR) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest .PHONY: test-tools test-tools: $(STATICCHECK) $(NILERR) diff --git a/aqua.yaml b/aqua.yaml new file mode 100644 index 0000000..6fa9b35 --- /dev/null +++ b/aqua.yaml @@ -0,0 +1,21 @@ +--- +# aqua - Declarative CLI Version Manager +# https://aquaproj.github.io/ +registries: + - type: standard + ref: v0.13.1 # renovate: depName=aquaproj/aqua-registry + - type: local + name: local + path: registry.yaml + +packages: + - name: argoproj/argo-cd@v2.2.2 + - name: kubernetes/kubectl@v1.23.1 + - name: kubernetes-sigs/kubebuilder@v3.2.0 + - name: kubernetes-sigs/kustomize@kustomize/v4.4.1 + - name: kubernetes-sigs/kind@v0.11.1 + - name: rust-lang/mdBook@v0.4.15 + - name: tilt-dev/tilt@v0.23.5 + - name: tilt-dev/ctlptl@v0.7.0 + - name: clamoriniere/crd-to-markdown@v0.0.3 + registry: local diff --git a/cluster.yaml b/cluster.yaml index 45e03be..049768d 100644 --- a/cluster.yaml +++ b/cluster.yaml @@ -7,5 +7,5 @@ apiVersion: ctlptl.dev/v1alpha1 kind: Cluster name: kind-dev product: kind -kubernetesVersion: v1.22.0 +kubernetesVersion: v1.23.0 registry: ctlptl-registry diff --git a/docs/development.md b/docs/development.md index 8726600..d7b518c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,33 +1,46 @@ # Development -Cattage can be developed in Tilt. - 1. Prepare a Linux box running Docker. -2. Checkout this repository. +2. Checkout this repository. ```console $ git clone https://github.com/cybozu-go/cattage ``` -3. Launch local Kubernetes cluster. +## Setup CLI tools + +1. Install [aqua][]. + + https://aquaproj.github.io/docs/tutorial-basics/quick-start + +2. Install CLI tools. + + ```console + $ cd cybozu-go/cattage + $ aqua i -l + ``` + +## Development & Debug + +1. Launch local Kubernetes cluster. ```console - $ cd cattage + $ cd cybozu-go/cattage $ make dev ``` -4. Start Tilt. +2. Start [Tilt][]. ```console - $ make tilt - $ ./bin/tilt up + $ tilt up ``` -5. Access: http://localhost:10350/ -6. Stop the Kubernetes cluster. +3. Access: http://localhost:10350/ +4. Stop the Kubernetes cluster. ```console $ make stop-dev ``` +[aqua]: https://aquaproj.github.io [Tilt]: https://tilt.dev diff --git a/e2e/Makefile b/e2e/Makefile index 5554af0..b18a278 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -1,15 +1,8 @@ -KIND_VERSION = 0.11.1 -KUBERNETES_VERSION = 1.22.2 +KUBERNETES_VERSION = 1.23.1 ARGOCD_VERSION = 2.2.2 -HELM_VERSION = 3.7.1 -BIN_DIR := $(dir $(shell pwd))bin -KIND := $(BIN_DIR)/kind -KUBECTL := $(BIN_DIR)/kubectl -HELM := $(BIN_DIR)/helm -ARGOCD = $(BIN_DIR)/argocd KIND_CONFIG = kind-config.yaml -export KUBECTL KUBECONFIG +export KUBECONFIG .PHONY: help help: @@ -21,62 +14,40 @@ help: @echo "stop Stop the kind cluster" .PHONY: start -start: $(KIND) $(KUBECTL) - $(KIND) create cluster --name=cattage --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m +start: + kind create cluster --name=cattage --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m cd ..; docker build --no-cache -t cattage:dev . - $(KIND) load docker-image cattage:dev --name=cattage + kind load docker-image cattage:dev --name=cattage $(MAKE) prepare - $(KUBECTL) apply -k ../config/default - $(KUBECTL) -n cattage wait --for=condition=available --timeout=180s --all deployments + kubectl apply -k ../config/default + kubectl -n cattage wait --for=condition=available --timeout=180s --all deployments .PHONY: prepare -prepare: $(KUBECTL) $(HELM) - $(KUBECTL) apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml - $(KUBECTL) -n cert-manager wait --for=condition=available --timeout=180s --all deployments - $(KUBECTL) create ns argocd - curl -sSLf https://raw.githubusercontent.com/argoproj/argo-cd/v$(ARGOCD_VERSION)/manifests/install.yaml | $(KUBECTL) -n argocd apply -f - - $(KUBECTL) -n argocd wait --for=condition=available --timeout=180s --all deployments - $(HELM) repo add accurate https://cybozu-go.github.io/accurate/ - $(HELM) repo update - $(HELM) install --create-namespace --namespace accurate accurate -f accurate-values.yaml accurate/accurate - $(KUBECTL) -n accurate wait --for=condition=available --timeout=180s --all deployments +prepare: + kubectl apply -f https://github.com/jetstack/cert-manager/releases/latest/download/cert-manager.yaml + kubectl -n cert-manager wait --for=condition=available --timeout=180s --all deployments + kubectl create ns argocd + curl -sSLf https://raw.githubusercontent.com/argoproj/argo-cd/v$(ARGOCD_VERSION)/manifests/install.yaml | kubectl -n argocd apply -f - + kubectl -n argocd wait --for=condition=available --timeout=180s --all deployments + helm repo add accurate https://cybozu-go.github.io/accurate/ + helm repo update + helm install --create-namespace --namespace accurate accurate -f accurate-values.yaml accurate/accurate + kubectl -n accurate wait --for=condition=available --timeout=180s --all deployments .PHONY: test test: - env PATH=$$(pwd)/../bin:$$PATH RUN_E2E=1 \ + env RUN_E2E=1 \ go test -v -race . -ginkgo.progress -ginkgo.v -ginkgo.failFast .PHONY: logs logs: rm -rf logs.tar.gz logs - $(KIND) export logs --name=cattage ./logs + kind export logs --name=cattage ./logs tar czf logs.tar.gz logs rm -rf logs .PHONY: stop -stop: $(KIND) - $(KIND) delete cluster --name=cattage +stop: + kind delete cluster --name=cattage -docker image rm cattage:dev -docker image prune -f - -.PHONY: kind -kind: $(KIND) -$(KIND): - mkdir -p $(BIN_DIR) - curl -sfL -o $@ https://github.com/kubernetes-sigs/kind/releases/download/v$(KIND_VERSION)/kind-linux-amd64 - chmod a+x $@ - -$(KUBECTL): - mkdir -p $(BIN_DIR) - curl -sfL -o $@ https://dl.k8s.io/release/v$(KUBERNETES_VERSION)/bin/linux/amd64/kubectl - chmod a+x $@ - -$(HELM): - $(MAKE) -C .. helm - -.PHONY: argocd -argocd: $(ARGOCD) -$(ARGOCD): - mkdir -p $(BIN_DIR) - curl -sSLf https://github.com/argoproj/argo-cd/releases/download/v$(ARGOCD_VERSION)/argocd-linux-amd64 -o $(ARGOCD) - chmod 755 $(ARGOCD) diff --git a/e2e/env_test.go b/e2e/env_test.go index 7e4b02f..ea826f6 100644 --- a/e2e/env_test.go +++ b/e2e/env_test.go @@ -3,6 +3,5 @@ package e2e import "os" var ( - runE2E = os.Getenv("RUN_E2E") != "" - kubectlCmd = os.Getenv("KUBECTL") + runE2E = os.Getenv("RUN_E2E") != "" ) diff --git a/e2e/run_test.go b/e2e/run_test.go index d71fb58..1dd92c1 100644 --- a/e2e/run_test.go +++ b/e2e/run_test.go @@ -11,7 +11,7 @@ import ( func kubectl(input []byte, args ...string) ([]byte, error) { stdout := new(bytes.Buffer) stderr := new(bytes.Buffer) - cmd := exec.Command(kubectlCmd, args...) + cmd := exec.Command("kubectl", args...) cmd.Stdout = stdout cmd.Stderr = stderr if input != nil { diff --git a/go.mod b/go.mod index 3e3c291..55bd2a8 100644 --- a/go.mod +++ b/go.mod @@ -1,18 +1,85 @@ module github.com/cybozu-go/cattage -go 1.16 +go 1.17 require ( - github.com/google/go-cmp v0.5.5 - github.com/onsi/ginkgo v1.16.4 - github.com/onsi/gomega v1.15.0 - github.com/spf13/cobra v1.1.3 - k8s.io/api v0.22.2 - k8s.io/apimachinery v0.22.2 - k8s.io/client-go v0.22.2 + github.com/google/go-cmp v0.5.6 + github.com/onsi/ginkgo v1.16.5 + github.com/onsi/gomega v1.17.0 + github.com/spf13/cobra v1.3.0 + k8s.io/api v0.23.1 + k8s.io/apimachinery v0.23.1 + k8s.io/client-go v0.23.1 k8s.io/klog v1.0.0 - k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a - sigs.k8s.io/controller-runtime v0.10.3 - sigs.k8s.io/structured-merge-diff/v4 v4.1.2 - sigs.k8s.io/yaml v1.2.0 + k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 + sigs.k8s.io/controller-runtime v0.11.0 + sigs.k8s.io/controller-tools v0.8.0 + sigs.k8s.io/structured-merge-diff/v4 v4.2.0 + sigs.k8s.io/yaml v1.3.0 +) + +require ( + cloud.google.com/go v0.99.0 // indirect + github.com/Azure/go-autorest v14.2.0+incompatible // indirect + github.com/Azure/go-autorest/autorest v0.11.18 // indirect + github.com/Azure/go-autorest/autorest/adal v0.9.13 // indirect + github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect + github.com/Azure/go-autorest/logger v0.2.1 // indirect + github.com/Azure/go-autorest/tracing v0.6.0 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect + github.com/fsnotify/fsnotify v1.5.1 // indirect + github.com/go-logr/logr v1.2.0 // indirect + github.com/go-logr/zapr v1.2.0 // indirect + github.com/gobuffalo/flect v0.2.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/googleapis/gnostic v0.5.5 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/nxadm/tail v1.4.8 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.11.0 // indirect + github.com/prometheus/client_model v0.2.0 // indirect + github.com/prometheus/common v0.28.0 // indirect + github.com/prometheus/procfs v0.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.19.1 // indirect + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect + golang.org/x/mod v0.5.0 // indirect + golang.org/x/net v0.0.0-20211209124913-491a49abca63 // indirect + golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect + golang.org/x/sys v0.0.0-20211205182925-97ca703d548d // indirect + golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect + golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.27.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect + k8s.io/apiextensions-apiserver v0.23.0 // indirect + k8s.io/component-base v0.23.0 // indirect + k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect + sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect ) diff --git a/go.sum b/go.sum index 8bab914..6197335 100644 --- a/go.sum +++ b/go.sum @@ -8,20 +8,45 @@ cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 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 v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= +cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= +cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= +cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= +cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= +cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= +cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= +cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= +cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= +cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= +cloud.google.com/go v0.98.0/go.mod h1:ua6Ush4NALrHk5QXDWnjvZHN93OuF0HfuEPq9I1X0cM= +cloud.google.com/go v0.99.0 h1:y/cM2iqGgGi5D5DQZl6D9STN/3dR/Vx5Mp8s752oJTY= +cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= 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/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 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/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= 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/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 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= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/go-ansiterm v0.0.0-20210608223527-2377c96fe795/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= @@ -41,6 +66,7 @@ github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUM github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= @@ -52,9 +78,12 @@ github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210826220005-b48c857c3a0e/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/benbjohnson/clock v1.0.3/go.mod h1:bGMdMPoPVvcYyt1gHDf4J2KE153Yf9BuiUKYMaxlTDM= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= @@ -65,20 +94,33 @@ 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/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/bketelsen/crypt v0.0.4/go.mod h1:aI6NrJ0pMGgvZKL1iVgXLnfIFJtfV+bKCoqOes/6LfM= github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/certifi/gocertifi v0.0.0-20191021191039-0944d244cd40/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= github.com/certifi/gocertifi v0.0.0-20200922220541-2c3bb06c6054/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA= 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.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 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/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211130200136-a8f946100490/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cockroachdb/datadriven v0.0.0-20200714090401-bf6692d28da5/go.mod h1:h6jFvWxBdQXxjopDMZyH2UVceIRfR84bdzbkoKrsWNo= github.com/cockroachdb/errors v1.2.4/go.mod h1:rQD95gz6FARkaKkQXUksEje/d9a6wBJoCr5oaCLELYA= github.com/cockroachdb/logtags v0.0.0-20190617123548-eb05cc24525f/go.mod h1:i/u985jwjWRlyHXQbwatDASoW0RMlZ/3i9yJHE2xLkI= @@ -90,6 +132,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -105,20 +148,31 @@ github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT 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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= +github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws= github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs= -github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= +github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= github.com/form3tech-oss/jwt-go v3.2.3+incompatible h1:7ZaBxOI7TMoYBfyA3cQHErNNyAWIKUMIwqxEtgHOs5c= github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= 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/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI= +github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= +github.com/getkin/kin-openapi v0.76.0/go.mod h1:660oXbgy5JFMKreazJaQTw7o+X00qeSyhcnluiMv+Xg= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -132,10 +186,10 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V 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.4.0 h1:K7/B1jt6fIBQVd4Owv2MqGQClcgf0R266+7C/QjRcLc= -github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/zapr v0.4.0 h1:uc1uML3hRYL9/ZZPdgHS/n8Nzo+eaYL/Efxkkamf7OM= -github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= +github.com/go-logr/logr v1.2.0 h1:QK40JKJyMdUDz+h+xvCsru/bJhvG0UxvePV0ufL/AcE= +github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/zapr v1.2.0 h1:n4JnPI1T3Qq1SFEi/F8rwLrZERp2bso19PJZDB9dayk= +github.com/go-logr/zapr v1.2.0/go.mod h1:Qa4Bsj2Vb+FAVeAKsLD8RLQ+YRJB8YDmOAKxaBQf7Ro= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= @@ -144,6 +198,8 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh github.com/go-openapi/swag v0.19.14/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobuffalo/flect v0.2.3 h1:f/ZukRnSNA/DUpSNDadko7Qc0PhGvsew35p/2tu+CRY= +github.com/gobuffalo/flect v0.2.3/go.mod h1:vmkQwuZYhN5Pc4ljYQZzP+1sq+NEkK+lh20jmEmX3jc= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= @@ -151,6 +207,7 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -162,11 +219,16 @@ github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= 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/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.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 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= @@ -179,36 +241,58 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/cel-go v0.9.0/go.mod h1:U7ayypeSkw23szu4GaQTPJGx66c20mx8JklMSxrmI1w= +github.com/google/cel-spec v0.6.0/go.mod h1:Nwjgxy5CbjlPrtCWjeDjUyKMl8w41YBYGjsyDdqk0xA= 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/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/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.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 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/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= 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/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -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/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= +github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gnostic v0.5.1/go.mod h1:6U4PtQXGIEt/Z3h5MAT7FNofLnw9vXk2cUuW7uA/OeU= github.com/googleapis/gnostic v0.5.5 h1:9fHAtK0uDfpveeqqo1hkEZJcFvYXAiCN3UutL8F9xHw= github.com/googleapis/gnostic v0.5.5/go.mod h1:7+EbHbldMins07ALC74bsA81Ovc97DwqyJO1AENw9kA= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 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= @@ -217,13 +301,23 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/api v1.11.0/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.0.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= 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-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -231,13 +325,22 @@ github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b 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/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +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/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/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= @@ -249,9 +352,11 @@ github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUB github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 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= @@ -263,6 +368,7 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= 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/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -270,17 +376,34 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/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.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= 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-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= @@ -288,14 +411,17 @@ github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS4 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/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/term v0.0.0-20210610120745-9d4ed1856297/go.mod h1:vgPCkQMyxTZ7IDy8SXRufE172gr8+K/JE/7hHFxHW3A= 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/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 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= @@ -311,28 +437,35 @@ github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY= -github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= -github.com/onsi/gomega v1.15.0 h1:WjP/FQ/sk43MRmnEcT+MlDw2TFvkrXlprrPST/IudjU= -github.com/onsi/gomega v1.15.0/go.mod h1:cIuvLEne0aoVhAgh/O6ac0Op8WWw9H6eYCriF+tEHG0= +github.com/onsi/gomega v1.17.0 h1:9Luw4uT5HTjHTN8+aNcSThgH1vdXnmdJ8xIfZ4wyTRE= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= 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/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 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.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.0 h1:HNkLOAEQMIDv/K+04rukrLx6ch7msSRwf3/SASFAGtQ= github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= @@ -344,12 +477,15 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 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.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0 h1:iMAkS2TDoNWnKM+Kopnx/8tnEStIfpYA0ur0xQzzhMQ= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.28.0 h1:vGVfV9KrDTvWt5boZO0I19g2E3CsWfpPPKZM9dt3mEw= +github.com/prometheus/common v0.28.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/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.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0 h1:mxy4L2jP6qMonqmq+aTtOx1ifVWUgG/TAmntgbh3xv4= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= @@ -358,7 +494,9 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= @@ -373,15 +511,23 @@ github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE 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/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= +github.com/spf13/afero v1.3.3/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4= +github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo= +github.com/spf13/cobra v1.2.1/go.mod h1:ExllRjgxM/piMAM+3tAZvg8fsklGAf3tPfi+i8t68Nk= +github.com/spf13/cobra v1.3.0 h1:R7cSvGu+Vv+qX0gW5R/85dx2kmmJT5z5NM8ifdYjdn0= +github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 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.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/spf13/viper v1.8.1/go.mod h1:o0Pch8wJ9BVSWGQMbra6iw0oQ5oktSIBaujf1rJH9Ns= +github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= 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= @@ -395,15 +541,22 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tmc/grpc-websocket-proxy v0.0.0-20201229170055-e5319fda7802/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +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.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v2 v2.305.1/go.mod h1:pMEacxZW7o8pg4CrFE7pquyCJJzZvkvdD2RibOCCCGs= go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE= go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc= @@ -412,6 +565,9 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/contrib v0.20.0/go.mod h1:G/EtFaa6qaN7+LxqfIAT3GiZa7Wv5DTBUzl5H4LY0Kc= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0/go.mod h1:oVGt1LRbBOBq1A5BQLlUg9UaU/54aiHw8cgjV3aWZ/E= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.20.0/go.mod h1:2AboqHi0CiIZU0qwhtUfCYD1GeUzvvIXWNkhDt7ZMG4= @@ -427,25 +583,30 @@ go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqe go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -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/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -go.uber.org/zap v1.19.0 h1:mZQZefskPPCMIBCSEH0v2/iUqqLrYtaeqwD6FUGUnFE= go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI= +go.uber.org/zap v1.19.1 h1:ue41HOKd1vGURxrmeKIgELGb3jPW9DMUDGtsinblHwI= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 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-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 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-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 h1:/ZScEX8SfEmUGRHs0gxpqteO5nfNW6axyZbBdw9A12g= -golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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= @@ -468,7 +629,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 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/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/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= @@ -478,7 +639,11 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB 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/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.5.0 h1:UG21uOlmZabA4fW5i7ZX6bjw1xELEGg/ZLgZq9auk/Q= +golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= 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= @@ -495,8 +660,10 @@ golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/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-20190724013045-ca1201d0de80/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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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= @@ -504,27 +671,59 @@ golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLL 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-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/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-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023 h1:ADo5wSpq2gqaCGQWzk7S5vd//0iyyLeAratkEoG5dLE= -golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY= +golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 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/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg= +golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 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/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -536,6 +735,7 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h 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-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-20190312061237-fead79001313/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= @@ -545,49 +745,84 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/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-20191008105621-543471e840be/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-20191204072324-ce4227a45e2e/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-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/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-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200831180312-196b9ba8737a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2 h1:c8PlLMqBbOHoqtjteWm5/kbe6rNY2pbRfbIMVnepueo= -golang.org/x/sys v0.0.0-20210817190340-bfb29a6856f2/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210816183151-1e6c022a8912/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211029165221-6e7872819dc8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E= +golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d h1:SZxvLBoTP5yHO3Frd4z4vrF+DBX9vMVanchswa69toE= -golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= +golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 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.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 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/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -611,6 +846,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw 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-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/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-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -629,13 +865,34 @@ golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapK 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-20200227222343-706bc42d1f0d/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-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff h1:VX/uD7MK0AHXGiScH3fsieUQUcpmRERPDYtqZdJnA+Q= +golang.org/x/tools v0.1.6-0.20210820212750-d4cc65f0b2ff/go.mod h1:YD9qOF0M9xpSpdWTBbzEl5e/RnCefISl8E5Noe10jFM= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -652,12 +909,36 @@ google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsb 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.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= +google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= +google.golang.org/api v0.44.0/go.mod h1:EBOGZqzyhtvMDoxwS97ctnh0zUmYY6CxqXsc1AvkYD8= +google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= +google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= +google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= +google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= +google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= +google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= +google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= +google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= +google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= +google.golang.org/api v0.62.0/go.mod h1:dKmwPCydfsad4qCH08MSdgWjfHOyfpd4VtDGgRFdavw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 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/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -677,12 +958,55 @@ google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvx 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-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201102152239-715cce707fb0/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= +google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= +google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= +google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= +google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= +google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= +google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -691,11 +1015,26 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ 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.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/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= @@ -707,8 +1046,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -720,6 +1060,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy 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/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= @@ -747,38 +1089,49 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.22.2 h1:M8ZzAD0V6725Fjg53fKeTJxGsJvRbk4TEm/fexHMtfw= -k8s.io/api v0.22.2/go.mod h1:y3ydYpLJAaDI+BbSe2xmGcqxiWHmWjkEeIbiwHvnPR8= -k8s.io/apiextensions-apiserver v0.22.2 h1:zK7qI8Ery7j2CaN23UCFaC1hj7dMiI87n01+nKuewd4= -k8s.io/apiextensions-apiserver v0.22.2/go.mod h1:2E0Ve/isxNl7tWLSUDgi6+cmwHi5fQRdwGVCxbC+KFA= -k8s.io/apimachinery v0.22.2 h1:ejz6y/zNma8clPVfNDLnPbleBo6MpoFy/HBiBqCouVk= -k8s.io/apimachinery v0.22.2/go.mod h1:O3oNtNadZdeOMxHFVxOreoznohCpy0z6mocxbZr7oJ0= -k8s.io/apiserver v0.22.2/go.mod h1:vrpMmbyjWrgdyOvZTSpsusQq5iigKNWv9o9KlDAbBHI= -k8s.io/client-go v0.22.2 h1:DaSQgs02aCC1QcwUdkKZWOeaVsQjYvWv8ZazcZ6JcHc= -k8s.io/client-go v0.22.2/go.mod h1:sAlhrkVDf50ZHx6z4K0S40wISNTarf1r800F+RlCF6U= -k8s.io/code-generator v0.22.2/go.mod h1:eV77Y09IopzeXOJzndrDyCI88UBok2h6WxAlBwpxa+o= -k8s.io/component-base v0.22.2 h1:vNIvE0AIrLhjX8drH0BgCNJcR4QZxMXcJzBsDplDx9M= -k8s.io/component-base v0.22.2/go.mod h1:5Br2QhI9OTe79p+TzPe9JKNQYvEKbq9rTJDWllunGug= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/gengo v0.0.0-20201214224949-b6c5ce23f027/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +k8s.io/api v0.23.0/go.mod h1:8wmDdLBHBNxtOIytwLstXt5E9PddnZb0GaMcqsvDBpg= +k8s.io/api v0.23.1 h1:ncu/qfBfUoClqwkTGbeRqqOqBCRoUAflMuOaOD7J0c8= +k8s.io/api v0.23.1/go.mod h1:WfXnOnwSqNtG62Y1CdjoMxh7r7u9QXGCkA1u0na2jgo= +k8s.io/apiextensions-apiserver v0.23.0 h1:uii8BYmHYiT2ZTAJxmvc3X8UhNYMxl2A0z0Xq3Pm+WY= +k8s.io/apiextensions-apiserver v0.23.0/go.mod h1:xIFAEEDlAZgpVBl/1VSjGDmLoXAWRG40+GsWhKhAxY4= +k8s.io/apimachinery v0.23.0/go.mod h1:fFCTTBKvKcwTPFzjlcxp91uPFZr+JA0FubU4fLzzFYc= +k8s.io/apimachinery v0.23.1 h1:sfBjlDFwj2onG0Ijx5C+SrAoeUscPrmghm7wHP+uXlo= +k8s.io/apimachinery v0.23.1/go.mod h1:SADt2Kl8/sttJ62RRsi9MIV4o8f5S3coArm0Iu3fBno= +k8s.io/apiserver v0.23.0/go.mod h1:Cec35u/9zAepDPPFyT+UMrgqOCjgJ5qtfVJDxjZYmt4= +k8s.io/client-go v0.23.0/go.mod h1:hrDnpnK1mSr65lHHcUuIZIXDgEbzc7/683c6hyG4jTA= +k8s.io/client-go v0.23.1 h1:Ma4Fhf/p07Nmj9yAB1H7UwbFHEBrSPg8lviR24U2GiQ= +k8s.io/client-go v0.23.1/go.mod h1:6QSI8fEuqD4zgFK0xbdwfB/PthBsIxCJMa3s17WlcO0= +k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9MvowsE= +k8s.io/component-base v0.23.0 h1:UAnyzjvVZ2ZR1lF35YwtNY6VMN94WtOnArcXBu34es8= +k8s.io/component-base v0.23.0/go.mod h1:DHH5uiFvLC1edCpvcTDV++NKULdYYU6pR9Tt3HIKMKI= +k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= 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/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= -k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= -k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a h1:8dYfu/Fc9Gz2rNJKB9IQRGgQOh2clmRzNIPPY1xLY5g= -k8s.io/utils v0.0.0-20210819203725-bdf08cb9a70a/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= +k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 h1:E3J9oCLlaobFUqsjG9DfKbP2BmgwBL2p7pn0A3dG9W4= +k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65/go.mod h1:sX9MT8g7NVZM5lVL/j8QyCCJe8YSMW30QvGZWaCIDIk= +k8s.io/utils v0.0.0-20210802155522-efc7438f0176/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= +k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 h1:ZKMMxTvduyf5WUtREOqg5LiXaN1KO/+0oOQPRFrClpo= +k8s.io/utils v0.0.0-20211208161948-7d6a63dca704/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 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.22/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/controller-runtime v0.10.3 h1:s5Ttmw/B4AuIbwrXD3sfBkXwnPMMWrqpVj4WRt1dano= -sigs.k8s.io/controller-runtime v0.10.3/go.mod h1:CQp8eyUQZ/Q7PJvnIrB6/hgfTC1kBkGylwsLgOQi1WY= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.25/go.mod h1:Mlj9PNLmG9bZ6BHFwFKDo5afkpWyUISkb9Me0GnK66I= +sigs.k8s.io/controller-runtime v0.11.0 h1:DqO+c8mywcZLFJWILq4iktoECTyn30Bkj0CwgqMpZWQ= +sigs.k8s.io/controller-runtime v0.11.0/go.mod h1:KKwLiTooNGu+JmLZGn9Sl3Gjmfj66eMbCQznLP5zcqA= +sigs.k8s.io/controller-tools v0.8.0 h1:uUkfTGEwrguqYYfcI2RRGUnC8mYdCFDqfwPKUcNJh1o= +sigs.k8s.io/controller-tools v0.8.0/go.mod h1:qE2DXhVOiEq5ijmINcFbqi9GZrrUjzB1TuJU0xa6eoY= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 h1:fD1pz4yfdADVNfFmcP2aBEtudwUQ1AlLnRBALr33v3s= +sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6/go.mod h1:p4QtZmO4uMYipTQNzagwnNoseA6OxSUutVw05NhYDRs= sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.1.2 h1:Hr/htKFmJEbtMgS/UD0N+gtgctAqz81t3nu+sPzynno= sigs.k8s.io/structured-merge-diff/v4 v4.1.2/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= -sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= +sigs.k8s.io/structured-merge-diff/v4 v4.2.0 h1:kDvPBbnPk+qYmkHmSo8vKGp438IASWofnbbUKDE/bv0= +sigs.k8s.io/structured-merge-diff/v4 v4.2.0/go.mod h1:j/nl6xW8vLS49O8YvXW1ocPhZawJtm+Yrr7PPRQ0Vg4= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= +sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= diff --git a/registry.yaml b/registry.yaml new file mode 100644 index 0000000..d380b53 --- /dev/null +++ b/registry.yaml @@ -0,0 +1,11 @@ +packages: +- type: github_release + repo_owner: clamoriniere + repo_name: crd-to-markdown + asset: 'crd-to-markdown_{{.OS}}_{{.Arch}}' + replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..b68dde1 --- /dev/null +++ b/tools.go @@ -0,0 +1,9 @@ +//go:build tools +// +build tools + +package cattage + +import ( + // These are to declare dependency on tools + _ "sigs.k8s.io/controller-tools/cmd/controller-gen" +) From 569f9227fd7a10aef6bd1848504b8b8a5c77f616 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 12 Jan 2022 17:17:56 +0900 Subject: [PATCH 48/89] Reflect review comments Signed-off-by: zoetrope --- controllers/application_controller.go | 1 + pkg/config/{teyps_test.go => types_test.go} | 0 2 files changed, 1 insertion(+) rename pkg/config/{teyps_test.go => types_test.go} (100%) diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 504ef52..16c6d47 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -162,6 +162,7 @@ func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstr tenantName := ns.Labels[constants.OwnerTenant] if tenantName == "" { if argocdApp != nil && argocdApp.GetDeletionTimestamp() == nil { + // Applications on namespaces that do not belong to a tenant will be removed. logger.Info("Remove unmanaged application") err = r.client.Delete(ctx, argocdApp) if err != nil { diff --git a/pkg/config/teyps_test.go b/pkg/config/types_test.go similarity index 100% rename from pkg/config/teyps_test.go rename to pkg/config/types_test.go From 55e64774b150f28f9bd17a40e75c6e0585605a26 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 13 Jan 2022 20:15:22 +0900 Subject: [PATCH 49/89] Add Repositories field Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 4 ++++ api/v1beta1/zz_generated.deepcopy.go | 5 +++++ charts/cattage/crds/tenant.yaml | 6 ++++++ charts/cattage/values.yaml | 16 ++++++++++------ cluster.yaml | 2 +- config/crd/bases/cattage.cybozu.io_tenants.yaml | 6 ++++++ config/manager/configmap.yaml | 16 ++++++++++------ config/samples/01_tenant.yaml | 4 +++- controllers/tenant_controller.go | 14 ++++++++------ controllers/tenant_controller_test.go | 7 +++++-- controllers/testdata/appprojecttemplate.yaml | 12 ++++++++---- controllers/testdata/rolebindingtemplate.yaml | 4 ++-- docs/config.md | 16 ++++++++++------ docs/crd_tenant.md | 1 + docs/setup.md | 16 ++++++++++------ 15 files changed, 89 insertions(+), 40 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 4c63332..d009cd6 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -58,6 +58,10 @@ type ArgoCDSpec struct { // Specify this if you want other tenant teams to be able to use your AppProject. // +optional ExtraAdmins []string `json:"extraAdmins,omitempty"` + + // Repositories contains list of repository URLs which can be used by the tenant. + // +optional + Repositories []string `json:"repositories,omitempty"` } // TenantHealth defines the observed state of Tenant diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index ded9756..406bcd0 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -18,6 +18,11 @@ func (in *ArgoCDSpec) DeepCopyInto(out *ArgoCDSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.Repositories != nil { + in, out := &in.Repositories, &out.Repositories + *out = make([]string, len(*in)) + copy(*out, *in) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ArgoCDSpec. diff --git a/charts/cattage/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml index be48f4c..a91b54d 100644 --- a/charts/cattage/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -49,6 +49,12 @@ spec: items: type: string type: array + repositories: + description: Repositories contains list of repository URLs which + can be used by the tenant. + items: + type: string + type: array type: object namespaces: description: Namespaces are the list of root namespaces that belong diff --git a/charts/cattage/values.yaml b/charts/cattage/values.yaml index 0dbda77..15fc4d1 100644 --- a/charts/cattage/values.yaml +++ b/charts/cattage/values.yaml @@ -41,11 +41,11 @@ controller: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} - {{ end }} + {{- end }} argocd: namespace: argocd appProjectTemplate: | @@ -53,10 +53,10 @@ controller: kind: AppProject spec: destinations: - {{ range .Namespaces }} + {{- range .Namespaces }} - namespace: {{ . }} server: '*' - {{ end }} + {{- end }} namespaceResourceBlacklist: - group: "" kind: ResourceQuota @@ -67,11 +67,15 @@ controller: roles: - groups: - cybozu-go:{{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - cybozu-go:{{ . }} - {{ end }} + {{- end }} name: admin policies: - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow sourceRepos: + {{- range .Repositories }} + - '{{ . }}' + {{- else }} - '*' + {{- end }} diff --git a/cluster.yaml b/cluster.yaml index 049768d..b3d88c2 100644 --- a/cluster.yaml +++ b/cluster.yaml @@ -5,7 +5,7 @@ port: 5005 --- apiVersion: ctlptl.dev/v1alpha1 kind: Cluster -name: kind-dev +name: kind-cattage-dev product: kind kubernetesVersion: v1.23.0 registry: ctlptl-registry diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index 4c7f0ec..bb69974 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -49,6 +49,12 @@ spec: items: type: string type: array + repositories: + description: Repositories contains list of repository URLs which + can be used by the tenant. + items: + type: string + type: array type: object namespaces: description: Namespaces are the list of root namespaces that belong diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index 9684b44..eab1cae 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -22,14 +22,14 @@ data: - kind: ServiceAccount name: node-{{ .Name }} namespace: teleport - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} - kind: ServiceAccount name: node-{{ . }} namespace: teleport - {{ end }} + {{- end }} argocd: namespace: argocd appProjectTemplate: | @@ -37,10 +37,10 @@ data: kind: AppProject spec: destinations: - {{ range .Namespaces }} + {{- range .Namespaces }} - namespace: {{ . }} server: '*' - {{ end }} + {{- end }} namespaceResourceBlacklist: - group: "" kind: ResourceQuota @@ -51,11 +51,15 @@ data: roles: - groups: - cybozu-go:{{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - cybozu-go:{{ . }} - {{ end }} + {{- end }} name: admin policies: - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow sourceRepos: + {{- range .Repositories }} + - '{{ . }}' + {{- else }} - '*' + {{- end }} diff --git a/config/samples/01_tenant.yaml b/config/samples/01_tenant.yaml index 1110625..251bd48 100644 --- a/config/samples/01_tenant.yaml +++ b/config/samples/01_tenant.yaml @@ -13,4 +13,6 @@ metadata: spec: namespaces: - name: app-b - + argocd: + repositories: + - "https://github.com/cybozu-go/*" diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 0f86533..19d5764 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -406,13 +406,15 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev var buf bytes.Buffer err = tpl.Execute(&buf, struct { - Name string - Namespaces []string - ExtraAdmins []string + Name string + Namespaces []string + ExtraAdmins []string + Repositories []string }{ - Name: tenant.Name, - Namespaces: namespaces, - ExtraAdmins: tenant.Spec.ArgoCD.ExtraAdmins, + Name: tenant.Name, + Namespaces: namespaces, + ExtraAdmins: tenant.Spec.ArgoCD.ExtraAdmins, + Repositories: tenant.Spec.ArgoCD.Repositories, }) if err != nil { return err diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 8c5a146..27f89a2 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -104,6 +104,9 @@ var _ = Describe("Tenant controller", func() { ExtraAdmins: []string{ "d-team", }, + Repositories: []string{ + "https://github.com/cybozu-go/*", + }, }, }, } @@ -189,7 +192,7 @@ var _ = Describe("Tenant controller", func() { "policies": ConsistOf("p, proj:x-team:admin, applications, *, x-team/*, allow"), }), ), - "sourceRepos": ConsistOf("*"), + "sourceRepos": ConsistOf("https://github.com/cybozu-go/*"), })) }) @@ -306,7 +309,7 @@ var _ = Describe("Tenant controller", func() { "policies": ConsistOf("p, proj:y-team:admin, applications, *, y-team/*, allow"), }), ), - "sourceRepos": ConsistOf("*"), + "sourceRepos": ConsistOf("https://github.com/cybozu-go/*"), })) By("removing app-y2") diff --git a/controllers/testdata/appprojecttemplate.yaml b/controllers/testdata/appprojecttemplate.yaml index 3549362..1d80b2b 100644 --- a/controllers/testdata/appprojecttemplate.yaml +++ b/controllers/testdata/appprojecttemplate.yaml @@ -2,10 +2,10 @@ apiVersion: argoproj.io/v1alpha1 kind: AppProject spec: destinations: - {{ range .Namespaces }} + {{- range .Namespaces }} - namespace: {{ . }} server: '*' - {{ end }} + {{- end }} namespaceResourceBlacklist: - group: "" kind: ResourceQuota @@ -16,11 +16,15 @@ spec: roles: - groups: - cybozu-go:{{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - cybozu-go:{{ . }} - {{ end }} + {{- end }} name: admin policies: - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow sourceRepos: + {{- range .Repositories }} + - '{{ . }}' + {{- else }} - '*' + {{- end }} diff --git a/controllers/testdata/rolebindingtemplate.yaml b/controllers/testdata/rolebindingtemplate.yaml index 1829b28..f49e467 100644 --- a/controllers/testdata/rolebindingtemplate.yaml +++ b/controllers/testdata/rolebindingtemplate.yaml @@ -8,8 +8,8 @@ subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} - {{ end }} + {{- end }} diff --git a/docs/config.md b/docs/config.md index ca1961a..15ee139 100644 --- a/docs/config.md +++ b/docs/config.md @@ -34,11 +34,11 @@ namespace: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} - {{ end }} + {{- end }} argocd: namespace: argocd appProjectTemplate: | @@ -46,21 +46,25 @@ argocd: kind: AppProject spec: destinations: - {{ range .Namespaces }} + {{- range .Namespaces }} - namespace: {{ . }} server: '*' - {{ end }} + {{- end }} roles: - groups: - {{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - {{ . }} - {{ end }} + {{- end }} name: admin policies: - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow sourceRepos: + {{- range .Repositories }} + - '{{ . }}' + {{- else }} - '*' + {{- end }} ``` `roleBindingTemplate` and `appProjectTemplate` can be written in go-template format. diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 2400bbf..7cb3056 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -18,6 +18,7 @@ ArgoCDSpec defines the desired state of the settings for Argo CD | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | | extraAdmins | ExtraAdmins are the names of the team to add to the AppProject user. Specify this if you want other tenant teams to be able to use your AppProject. | []string | false | +| repositories | Repositories contains list of repository URLs which can be used by the tenant. | []string | false | [Back to Custom Resources](#custom-resources) diff --git a/docs/setup.md b/docs/setup.md index e5a5953..383ffdb 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -105,11 +105,11 @@ controller: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} - {{ end }} + {{- end }} argocd: namespace: argocd appProjectTemplate: | @@ -117,21 +117,25 @@ controller: kind: AppProject spec: destinations: - {{ range .Namespaces }} + {{- range .Namespaces }} - namespace: {{ . }} server: '*' - {{ end }} + {{- end }} roles: - groups: - {{ .Name }} - {{ range .ExtraAdmins }} + {{- range .ExtraAdmins }} - {{ . }} - {{ end }} + {{- end }} name: admin policies: - p, proj:{{ .Name }}:admin, applications, *, {{ .Name }}/*, allow sourceRepos: + {{- range .Repositories }} + - '{{ . }}' + {{- else }} - '*' + {{- end }} ``` `appProjectTemplate` and `roleBindingTemplate` should be configured appropriately for your multi-tenancy environment. From 9b268de03c435325d6f5899724b0c761d0672059 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 14 Jan 2022 11:23:08 +0900 Subject: [PATCH 50/89] Fix controller-gen path Signed-off-by: zoetrope --- Tiltfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tiltfile b/Tiltfile index bf3afcc..2f3750b 100644 --- a/Tiltfile +++ b/Tiltfile @@ -10,10 +10,10 @@ def kubebuilder(): ''' def manifests(): - return 'controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases;' + return './bin/controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases;' def generate(): - return 'controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' + return './bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' def binary(): return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/cattage-controller cmd/cattage-controller/main.go' From fa40efba2ac1d42060387916b9fd3d8ad04f294b Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 14 Jan 2022 11:25:12 +0900 Subject: [PATCH 51/89] Fix test Signed-off-by: zoetrope --- controllers/tenant_controller_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 27f89a2..9a09162 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -309,7 +309,7 @@ var _ = Describe("Tenant controller", func() { "policies": ConsistOf("p, proj:y-team:admin, applications, *, y-team/*, allow"), }), ), - "sourceRepos": ConsistOf("https://github.com/cybozu-go/*"), + "sourceRepos": ConsistOf("*"), })) By("removing app-y2") From c33612949414d63cc4057711acaf648dc6e5de1c Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 14 Jan 2022 14:21:50 +0900 Subject: [PATCH 52/89] Replace Tenant's extraAdmins field with delegates. Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 25 ++++++++----- api/v1beta1/zz_generated.deepcopy.go | 37 ++++++++++++++----- charts/cattage/crds/tenant.yaml | 35 +++++++++++------- charts/cattage/values.yaml | 4 +- .../crd/bases/cattage.cybozu.io_tenants.yaml | 35 +++++++++++------- config/manager/configmap.yaml | 4 +- config/samples/01_tenant.yaml | 4 ++ controllers/tenant_controller.go | 23 +++++++++--- controllers/tenant_controller_test.go | 16 ++++---- controllers/testdata/appprojecttemplate.yaml | 2 +- controllers/testdata/rolebindingtemplate.yaml | 2 +- docs/config.md | 23 ++++++------ docs/crd_tenant.md | 15 +++++++- docs/setup.md | 4 +- 14 files changed, 147 insertions(+), 82 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index d009cd6..f144bb3 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -30,6 +30,10 @@ type TenantSpec struct { // ArgoCD is the settings of Argo CD for this tenant // +optional ArgoCD ArgoCDSpec `json:"argocd,omitempty"` + + // Delegates is a list of other tenants that are delegated access to this tenant. + // +optional + Delegates []Delegate `json:"delegates,omitempty"` } // NamespaceSpec defines the desired state of Namespace @@ -45,25 +49,26 @@ type NamespaceSpec struct { // Annotations are the annotations to add to the namespace // +optional Annotations map[string]string `json:"annotations,omitempty"` - - // ExtraAdmins are the names of the team to add to the namespace administrator. - // Specify this if you want other tenant teams to be able to use your namespace. - // +optional - ExtraAdmins []string `json:"extraAdmins,omitempty"` } // ArgoCDSpec defines the desired state of the settings for Argo CD type ArgoCDSpec struct { - // ExtraAdmins are the names of the team to add to the AppProject user. - // Specify this if you want other tenant teams to be able to use your AppProject. - // +optional - ExtraAdmins []string `json:"extraAdmins,omitempty"` - // Repositories contains list of repository URLs which can be used by the tenant. // +optional Repositories []string `json:"repositories,omitempty"` } +// Delegate defines a tenant that is delegated access to a tenant. +type Delegate struct { + // Name is the name of a delegated tenant + // +kubebuilder:validation:Required + Name string `json:"name"` + + // Roles is a list of roles that the tenant has + // +kubebuilder:validation:MinItems=1 + Roles []string `json:"roles"` +} + // TenantHealth defines the observed state of Tenant // +kubebuilder:validation:Enum=Healthy;Unhealthy type TenantHealth string diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 406bcd0..45a44d4 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -13,11 +13,6 @@ import ( // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ArgoCDSpec) DeepCopyInto(out *ArgoCDSpec) { *out = *in - if in.ExtraAdmins != nil { - in, out := &in.ExtraAdmins, &out.ExtraAdmins - *out = make([]string, len(*in)) - copy(*out, *in) - } if in.Repositories != nil { in, out := &in.Repositories, &out.Repositories *out = make([]string, len(*in)) @@ -35,6 +30,26 @@ func (in *ArgoCDSpec) DeepCopy() *ArgoCDSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Delegate) DeepCopyInto(out *Delegate) { + *out = *in + if in.Roles != nil { + in, out := &in.Roles, &out.Roles + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Delegate. +func (in *Delegate) DeepCopy() *Delegate { + if in == nil { + return nil + } + out := new(Delegate) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NamespaceSpec) DeepCopyInto(out *NamespaceSpec) { *out = *in @@ -52,11 +67,6 @@ func (in *NamespaceSpec) DeepCopyInto(out *NamespaceSpec) { (*out)[key] = val } } - if in.ExtraAdmins != nil { - in, out := &in.ExtraAdmins, &out.ExtraAdmins - *out = make([]string, len(*in)) - copy(*out, *in) - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSpec. @@ -139,6 +149,13 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { } } in.ArgoCD.DeepCopyInto(&out.ArgoCD) + if in.Delegates != nil { + in, out := &in.Delegates, &out.Delegates + *out = make([]Delegate, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSpec. diff --git a/charts/cattage/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml index a91b54d..10bd096 100644 --- a/charts/cattage/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -42,13 +42,6 @@ spec: argocd: description: ArgoCD is the settings of Argo CD for this tenant properties: - extraAdmins: - description: ExtraAdmins are the names of the team to add to the - AppProject user. Specify this if you want other tenant teams - to be able to use your AppProject. - items: - type: string - type: array repositories: description: Repositories contains list of repository URLs which can be used by the tenant. @@ -56,6 +49,27 @@ spec: type: string type: array type: object + delegates: + description: Delegates is a list of other tenants that are delegated + access to this tenant. + items: + description: Delegate defines a tenant that is delegated access + to a tenant. + properties: + name: + description: Name is the name of a delegated tenant + type: string + roles: + description: Roles is a list of roles that the tenant has + items: + type: string + minItems: 1 + type: array + required: + - name + - roles + type: object + type: array namespaces: description: Namespaces are the list of root namespaces that belong to this tenant @@ -67,13 +81,6 @@ spec: type: string description: Annotations are the annotations to add to the namespace type: object - extraAdmins: - description: ExtraAdmins are the names of the team to add to - the namespace administrator. Specify this if you want other - tenant teams to be able to use your namespace. - items: - type: string - type: array labels: additionalProperties: type: string diff --git a/charts/cattage/values.yaml b/charts/cattage/values.yaml index 15fc4d1..41b1b5e 100644 --- a/charts/cattage/values.yaml +++ b/charts/cattage/values.yaml @@ -41,7 +41,7 @@ controller: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} @@ -67,7 +67,7 @@ controller: roles: - groups: - cybozu-go:{{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - cybozu-go:{{ . }} {{- end }} name: admin diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index bb69974..32bd47e 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -42,13 +42,6 @@ spec: argocd: description: ArgoCD is the settings of Argo CD for this tenant properties: - extraAdmins: - description: ExtraAdmins are the names of the team to add to the - AppProject user. Specify this if you want other tenant teams - to be able to use your AppProject. - items: - type: string - type: array repositories: description: Repositories contains list of repository URLs which can be used by the tenant. @@ -56,6 +49,27 @@ spec: type: string type: array type: object + delegates: + description: Delegates is a list of other tenants that are delegated + access to this tenant. + items: + description: Delegate defines a tenant that is delegated access + to a tenant. + properties: + name: + description: Name is the name of a delegated tenant + type: string + roles: + description: Roles is a list of roles that the tenant has + items: + type: string + minItems: 1 + type: array + required: + - name + - roles + type: object + type: array namespaces: description: Namespaces are the list of root namespaces that belong to this tenant @@ -67,13 +81,6 @@ spec: type: string description: Annotations are the annotations to add to the namespace type: object - extraAdmins: - description: ExtraAdmins are the names of the team to add to - the namespace administrator. Specify this if you want other - tenant teams to be able to use your namespace. - items: - type: string - type: array labels: additionalProperties: type: string diff --git a/config/manager/configmap.yaml b/config/manager/configmap.yaml index eab1cae..51fb42d 100644 --- a/config/manager/configmap.yaml +++ b/config/manager/configmap.yaml @@ -22,7 +22,7 @@ data: - kind: ServiceAccount name: node-{{ .Name }} namespace: teleport - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} @@ -51,7 +51,7 @@ data: roles: - groups: - cybozu-go:{{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - cybozu-go:{{ . }} {{- end }} name: admin diff --git a/config/samples/01_tenant.yaml b/config/samples/01_tenant.yaml index 251bd48..93f9a08 100644 --- a/config/samples/01_tenant.yaml +++ b/config/samples/01_tenant.yaml @@ -16,3 +16,7 @@ spec: argocd: repositories: - "https://github.com/cybozu-go/*" + delegates: + - name: a-team + roles: + - admin diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 19d5764..2c1020a 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -300,6 +300,17 @@ func (r *TenantReconciler) patchRoleBinding(ctx context.Context, rb *acrbacv1.Ro }) } +func rolesMap(delegates []cattagev1beta1.Delegate) map[string][]string { + result := make(map[string][]string) + + for _, d := range delegates { + for _, role := range d.Roles { + result[role] = append(result[role], d.Name) + } + } + return result +} + func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *cattagev1beta1.Tenant) error { for _, ns := range tenant.Spec.Namespaces { namespace := accorev1.Namespace(ns.Name) @@ -332,11 +343,11 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt } var buf bytes.Buffer err = tpl.Execute(&buf, struct { - Name string - ExtraAdmins []string + Name string + Roles map[string][]string }{ - Name: tenant.Name, - ExtraAdmins: ns.ExtraAdmins, + Name: tenant.Name, + Roles: rolesMap(tenant.Spec.Delegates), }) if err != nil { return err @@ -408,12 +419,12 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev err = tpl.Execute(&buf, struct { Name string Namespaces []string - ExtraAdmins []string + Roles map[string][]string Repositories []string }{ Name: tenant.Name, Namespaces: namespaces, - ExtraAdmins: tenant.Spec.ArgoCD.ExtraAdmins, + Roles: rolesMap(tenant.Spec.Delegates), Repositories: tenant.Spec.ArgoCD.Repositories, }) if err != nil { diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 9a09162..c5d7045 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -95,19 +95,21 @@ var _ = Describe("Tenant controller", func() { Annotations: map[string]string{ "abc": "def", }, - ExtraAdmins: []string{ - "c-team", - }, }, }, ArgoCD: cattagev1beta1.ArgoCDSpec{ - ExtraAdmins: []string{ - "d-team", - }, Repositories: []string{ "https://github.com/cybozu-go/*", }, }, + Delegates: []cattagev1beta1.Delegate{ + { + Name: "c-team", + Roles: []string{ + "admin", + }, + }, + }, }, } err := k8sClient.Create(ctx, tenant) @@ -187,7 +189,7 @@ var _ = Describe("Tenant controller", func() { }), "roles": ConsistOf( MatchAllKeys(Keys{ - "groups": ConsistOf("cybozu-go:x-team", "cybozu-go:d-team"), + "groups": ConsistOf("cybozu-go:x-team", "cybozu-go:c-team"), "name": Equal("admin"), "policies": ConsistOf("p, proj:x-team:admin, applications, *, x-team/*, allow"), }), diff --git a/controllers/testdata/appprojecttemplate.yaml b/controllers/testdata/appprojecttemplate.yaml index 1d80b2b..ffead51 100644 --- a/controllers/testdata/appprojecttemplate.yaml +++ b/controllers/testdata/appprojecttemplate.yaml @@ -16,7 +16,7 @@ spec: roles: - groups: - cybozu-go:{{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - cybozu-go:{{ . }} {{- end }} name: admin diff --git a/controllers/testdata/rolebindingtemplate.yaml b/controllers/testdata/rolebindingtemplate.yaml index f49e467..df13553 100644 --- a/controllers/testdata/rolebindingtemplate.yaml +++ b/controllers/testdata/rolebindingtemplate.yaml @@ -8,7 +8,7 @@ subjects: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} diff --git a/docs/config.md b/docs/config.md index 15ee139..99e9eeb 100644 --- a/docs/config.md +++ b/docs/config.md @@ -34,7 +34,7 @@ namespace: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} @@ -53,7 +53,7 @@ argocd: roles: - groups: - {{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - {{ . }} {{- end }} name: admin @@ -71,18 +71,19 @@ argocd: `roleBindingTemplate` can use the following variables: -| Key | Type | Description | -|---------------|------------|-------------------------------| -| `Name` | `string` | The name of a tenant. | -| `ExtraAdmins` | `[]string` | List of extra administrators. | +| Key | Type | Description | +|---------|-----------------------|----------------------------------------------------------------------------------| +| `Name` | `string` | The name of the tenant. | +| `Roles` | `map[string][]string` | Map of other tenants that are accessible to this tenant. The key is a role name. | `appProjectTemplate` can use the following variables: -| Key | Type | Description | -|---------------|------------|----------------------------------------------------------------------| -| `Name` | `string` | The name of a tenant. | -| `ExtraAdmins` | `[]string` | List of extra administrators. | -| `Namespaces` | `[]string` | List of namespaces belonging to a tenant (including sub-namespaces). | +| Key | Type | Description | +|----------------|-----------------------|----------------------------------------------------------------------------------| +| `Name` | `string` | The name of the tenant. | +| `Namespaces` | `[]string` | List of namespaces belonging to a tenant (including sub-namespaces). | +| `Repositories` | `[]string` | List of repository URLs which can be used by the tenant. | +| `Roles` | `map[string][]string` | Map of other tenants that are accessible to this tenant. The key is a role name. | ## Environment variables diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 7cb3056..911936c 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -6,6 +6,7 @@ ### Sub Resources * [ArgoCDSpec](#argocdspec) +* [Delegate](#delegate) * [NamespaceSpec](#namespacespec) * [TenantList](#tenantlist) * [TenantSpec](#tenantspec) @@ -17,11 +18,21 @@ ArgoCDSpec defines the desired state of the settings for Argo CD | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | -| extraAdmins | ExtraAdmins are the names of the team to add to the AppProject user. Specify this if you want other tenant teams to be able to use your AppProject. | []string | false | | repositories | Repositories contains list of repository URLs which can be used by the tenant. | []string | false | [Back to Custom Resources](#custom-resources) +#### Delegate + +Delegate defines a tenant that is delegated access to a tenant. + +| Field | Description | Scheme | Required | +| ----- | ----------- | ------ | -------- | +| name | Name is the name of a delegated tenant | string | true | +| roles | Roles is a list of roles that the tenant has | []string | true | + +[Back to Custom Resources](#custom-resources) + #### NamespaceSpec NamespaceSpec defines the desired state of Namespace @@ -31,7 +42,6 @@ NamespaceSpec defines the desired state of Namespace | name | Name is the name of namespace to be generated | string | true | | labels | Labels are the labels to add to the namespace | map[string]string | false | | annotations | Annotations are the annotations to add to the namespace | map[string]string | false | -| extraAdmins | ExtraAdmins are the names of the team to add to the namespace administrator. Specify this if you want other tenant teams to be able to use your namespace. | []string | false | [Back to Custom Resources](#custom-resources) @@ -66,6 +76,7 @@ TenantSpec defines the desired state of Tenant | ----- | ----------- | ------ | -------- | | namespaces | Namespaces are the list of root namespaces that belong to this tenant | [][NamespaceSpec](#namespacespec) | true | | argocd | ArgoCD is the settings of Argo CD for this tenant | [ArgoCDSpec](#argocdspec) | false | +| delegates | Delegates is a list of other tenants that are delegated access to this tenant. | [][Delegate](#delegate) | false | [Back to Custom Resources](#custom-resources) diff --git a/docs/setup.md b/docs/setup.md index 383ffdb..73bd7fe 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -105,7 +105,7 @@ controller: - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - apiGroup: rbac.authorization.k8s.io kind: Group name: {{ . }} @@ -124,7 +124,7 @@ controller: roles: - groups: - {{ .Name }} - {{- range .ExtraAdmins }} + {{- range .Roles.admin }} - {{ . }} {{- end }} name: admin From 291b3f448022252e5da37a0d93998506d1f0afee Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 14 Jan 2022 15:23:28 +0900 Subject: [PATCH 53/89] Supported version Signed-off-by: zoetrope --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 0f46c99..5ed51bb 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,16 @@ Cattage is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][ - The ownership of sub-namespaces can be changed between tenants. - Tenant users can create Application resources in their sub-namespaces without `argocd` command. It allows for [App Of Apps Pattern](https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/#app-of-apps-pattern) in multi-tenancy environments. +## Supported Version + +Cattage depends on the specification of Argo CD and Accurate. + +Cattage supports the following versions: + +| Cattage | Argo CD | Accurate | +|---------|---------|----------| +| 0.1.0 | 2.2.x | 0.3.x | + ## Documentation [docs](docs/) directory contains documents about designs and specifications. From 7b2e93618eb899de61cbfd1d4a4bd394953648a1 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 14 Jan 2022 15:35:54 +0900 Subject: [PATCH 54/89] Accurate constants Signed-off-by: zoetrope --- controllers/tenant_controller.go | 9 +++--- controllers/tenant_controller_test.go | 41 ++++++++++++++------------- e2e/e2e_test.go | 3 +- hooks/suite_test.go | 19 +++++++------ hooks/tenant.go | 7 +++-- pkg/accurate/constants.go | 22 ++++++++++++++ 6 files changed, 64 insertions(+), 37 deletions(-) create mode 100644 pkg/accurate/constants.go diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 2c1020a..2956bed 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -23,6 +23,7 @@ import ( "text/template" cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/accurate" "github.com/cybozu-go/cattage/pkg/argocd" extract "github.com/cybozu-go/cattage/pkg/client" "github.com/cybozu-go/cattage/pkg/config" @@ -321,7 +322,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt for k, v := range ns.Labels { labels[k] = v } - labels["accurate.cybozu.com/type"] = "root" + labels[accurate.LabelType] = accurate.NSTypeRoot labels[constants.OwnerTenant] = tenant.Name namespace.WithLabels(labels) annotations := make(map[string]string) @@ -362,7 +363,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt constants.OwnerTenant: tenant.Name, }) rb.WithAnnotations(map[string]string{ - "accurate.cybozu.com/propagate": "update", + accurate.AnnPropagate: accurate.PropagateUpdate, }) err = r.patchRoleBinding(ctx, rb) @@ -503,8 +504,8 @@ func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { func SetupIndexForNamespace(ctx context.Context, mgr manager.Manager) error { ns := &corev1.Namespace{} err := mgr.GetFieldIndexer().IndexField(ctx, ns, constants.RootNamespaces, func(rawObj client.Object) []string { - nsType := rawObj.GetLabels()["accurate.cybozu.com/type"] - if nsType != "root" { + nsType := rawObj.GetLabels()[accurate.LabelType] + if nsType != accurate.NSTypeRoot { return nil } tenantName := rawObj.GetLabels()[constants.OwnerTenant] diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index c5d7045..35a9ac7 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -7,6 +7,7 @@ import ( "time" cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/accurate" "github.com/cybozu-go/cattage/pkg/argocd" cacheclient "github.com/cybozu-go/cattage/pkg/client" tenantconfig "github.com/cybozu-go/cattage/pkg/config" @@ -46,7 +47,7 @@ var _ = Describe("Tenant controller", func() { config = &tenantconfig.Config{ Namespace: tenantconfig.NamespaceConfig{ CommonLabels: map[string]string{ - "accurate.cybozu.com/template": "init-template", + accurate.LabelTemplate: "init-template", }, CommonAnnotations: map[string]string{ "hoge": "fuga", @@ -124,11 +125,11 @@ var _ = Describe("Tenant controller", func() { }).Should(Succeed()) Expect(ns.Labels).Should(MatchAllKeys(Keys{ - "kubernetes.io/metadata.name": Equal("app-x"), - "accurate.cybozu.com/type": Equal("root"), - constants.OwnerTenant: Equal("x-team"), - "foo": Equal("bar"), - "accurate.cybozu.com/template": Equal("init-template"), + "kubernetes.io/metadata.name": Equal("app-x"), + accurate.LabelType: Equal(accurate.NSTypeRoot), + constants.OwnerTenant: Equal("x-team"), + "foo": Equal("bar"), + accurate.LabelTemplate: Equal("init-template"), })) Expect(ns.Annotations).Should(MatchAllKeys(Keys{ "abc": Equal("def"), @@ -222,10 +223,10 @@ var _ = Describe("Tenant controller", func() { return nil }).Should(Succeed()) Expect(nsy1.Labels).Should(MatchAllKeys(Keys{ - "kubernetes.io/metadata.name": Equal("app-y1"), - "accurate.cybozu.com/type": Equal("root"), - constants.OwnerTenant: Equal("y-team"), - "accurate.cybozu.com/template": Equal("init-template"), + "kubernetes.io/metadata.name": Equal("app-y1"), + accurate.LabelType: Equal(accurate.NSTypeRoot), + constants.OwnerTenant: Equal("y-team"), + accurate.LabelTemplate: Equal("init-template"), })) nsy2 := &corev1.Namespace{} @@ -236,10 +237,10 @@ var _ = Describe("Tenant controller", func() { return nil }).Should(Succeed()) Expect(nsy2.Labels).Should(MatchAllKeys(Keys{ - "kubernetes.io/metadata.name": Equal("app-y2"), - "accurate.cybozu.com/type": Equal("root"), - constants.OwnerTenant: Equal("y-team"), - "accurate.cybozu.com/template": Equal("init-template"), + "kubernetes.io/metadata.name": Equal("app-y2"), + accurate.LabelType: Equal(accurate.NSTypeRoot), + constants.OwnerTenant: Equal("y-team"), + accurate.LabelTemplate: Equal("init-template"), })) rby1 := &rbacv1.RoleBinding{} @@ -335,7 +336,7 @@ var _ = Describe("Tenant controller", func() { }).Should(Succeed()) Expect(nsy2.Labels).Should(MatchAllKeys(Keys{ "kubernetes.io/metadata.name": Equal("app-y2"), - "accurate.cybozu.com/type": Equal("root"), + accurate.LabelType: Equal(accurate.NSTypeRoot), })) Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-y2", Name: "y-team-admin"}, rby2) @@ -401,10 +402,10 @@ var _ = Describe("Tenant controller", func() { return nil }).Should(Succeed()) Expect(ns.Labels).Should(MatchAllKeys(Keys{ - "kubernetes.io/metadata.name": Equal("app-z"), - "accurate.cybozu.com/type": Equal("root"), - constants.OwnerTenant: Equal("z-team"), - "accurate.cybozu.com/template": Equal("init-template"), + "kubernetes.io/metadata.name": Equal("app-z"), + accurate.LabelType: Equal(accurate.NSTypeRoot), + constants.OwnerTenant: Equal("z-team"), + accurate.LabelTemplate: Equal("init-template"), })) rb := &rbacv1.RoleBinding{} @@ -476,7 +477,7 @@ var _ = Describe("Tenant controller", func() { }).Should(Succeed()) Expect(ns.Labels).Should(MatchAllKeys(Keys{ "kubernetes.io/metadata.name": Equal("app-z"), - "accurate.cybozu.com/type": Equal("root"), + accurate.LabelType: Equal(accurate.NSTypeRoot), })) Eventually(func() error { err := k8sClient.Get(ctx, client.ObjectKey{Namespace: "app-z", Name: "z-team-admin"}, rb) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index ef194db..c66b291 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" + "github.com/cybozu-go/cattage/pkg/accurate" "github.com/cybozu-go/cattage/pkg/argocd" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -68,7 +69,7 @@ var _ = Describe("Cattage", func() { }) It("should change ownership", func() { - kubectlSafe(nil, "label", "ns", "sub-1", "accurate.cybozu.com/parent=app-b", "--overwrite") + kubectlSafe(nil, "label", "ns", "sub-1", accurate.LabelParent+"=app-b", "--overwrite") Eventually(func() error { out, err := kubectl(nil, "get", "app", "-n", "sub-1", "sample", "-o", "json") diff --git a/hooks/suite_test.go b/hooks/suite_test.go index bb39b11..4fd6b30 100644 --- a/hooks/suite_test.go +++ b/hooks/suite_test.go @@ -27,6 +27,7 @@ import ( //+kubebuilder:scaffold:imports cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/accurate" "github.com/cybozu-go/cattage/pkg/config" "github.com/cybozu-go/cattage/pkg/constants" . "github.com/onsi/ginkgo" @@ -133,8 +134,8 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-1" ns.Labels = map[string]string{ - constants.OwnerTenant: "a-team", - "accurate.cybozu.com/parent": "app-a-team", + constants.OwnerTenant: "a-team", + accurate.LabelParent: "app-a-team", } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -142,8 +143,8 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "sub-2" ns.Labels = map[string]string{ - constants.OwnerTenant: "e-team", - "accurate.cybozu.com/parent": "app-e-team", + constants.OwnerTenant: "e-team", + accurate.LabelParent: "app-e-team", } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -151,8 +152,8 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "app-a-team" ns.Labels = map[string]string{ - constants.OwnerTenant: "a-team", - "accurate.cybozu.com/type": "root", + constants.OwnerTenant: "a-team", + accurate.LabelType: accurate.NSTypeRoot, } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -160,8 +161,8 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "app-y-team" ns.Labels = map[string]string{ - constants.OwnerTenant: "y-team", - "accurate.cybozu.com/type": "root", + constants.OwnerTenant: "y-team", + accurate.LabelType: accurate.NSTypeRoot, } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) @@ -169,7 +170,7 @@ var _ = BeforeSuite(func() { ns = &corev1.Namespace{} ns.Name = "template" ns.Labels = map[string]string{ - "accurate.cybozu.com/type": "template", + accurate.LabelType: accurate.NSTypeTemplate, } err = k8sClient.Create(ctx, ns) Expect(err).NotTo(HaveOccurred()) diff --git a/hooks/tenant.go b/hooks/tenant.go index fd45718..06a8def 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -22,6 +22,7 @@ import ( "net/http" cattagev1beta1 "github.com/cybozu-go/cattage/api/v1beta1" + "github.com/cybozu-go/cattage/pkg/accurate" "github.com/cybozu-go/cattage/pkg/config" "github.com/cybozu-go/cattage/pkg/constants" admissionv1 "k8s.io/api/admission/v1" @@ -93,11 +94,11 @@ func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) adm if owner != "" && owner != tenant.Name { return admission.Denied("deny to specify other owner's namespace") } - nsType := namespace.Labels["accurate.cybozu.com/type"] - if nsType != "" && nsType != "root" { + nsType := namespace.Labels[accurate.LabelType] + if nsType != "" && nsType != accurate.NSTypeRoot { return admission.Denied("deny to specify a namespace other than root") } - parent := namespace.Labels["accurate.cybozu.com/parent"] + parent := namespace.Labels[accurate.LabelParent] if parent != "" { return admission.Denied("deny to specify a sub namespace") } diff --git a/pkg/accurate/constants.go b/pkg/accurate/constants.go new file mode 100644 index 0000000..b402cce --- /dev/null +++ b/pkg/accurate/constants.go @@ -0,0 +1,22 @@ +package accurate + +const MetaPrefix = "accurate.cybozu.com/" + +// Labels +const ( + LabelType = MetaPrefix + "type" + LabelTemplate = MetaPrefix + "template" + LabelParent = MetaPrefix + "parent" +) + +// Annotations +const ( + AnnPropagate = MetaPrefix + "propagate" +) + +// Label or annotation values +const ( + NSTypeTemplate = "template" + NSTypeRoot = "root" + PropagateUpdate = "update" +) From 8b8c1ad86b7406fddea48d232be69972050b0bb3 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 14 Jan 2022 15:46:34 +0900 Subject: [PATCH 55/89] Rename namespaces with rootNamespaces. Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 8 ++++---- api/v1beta1/zz_generated.deepcopy.go | 14 +++++++------- charts/cattage/crds/tenant.yaml | 8 ++++---- config/crd/bases/cattage.cybozu.io_tenants.yaml | 8 ++++---- config/samples/01_tenant.yaml | 4 ++-- controllers/tenant_controller.go | 6 +++--- controllers/tenant_controller_test.go | 12 ++++++------ docs/crd_tenant.md | 8 ++++---- docs/usage.md | 6 +++--- hooks/tenant.go | 2 +- hooks/tenant_test.go | 8 ++++---- 11 files changed, 42 insertions(+), 42 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index f144bb3..a12b84f 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -22,10 +22,10 @@ import ( // TenantSpec defines the desired state of Tenant type TenantSpec struct { - // Namespaces are the list of root namespaces that belong to this tenant + // RootNamespaces are the list of root namespaces that belong to this tenant // +kubebuilder:validation:Required // +kubebuilder:validation:MinItems=1 - Namespaces []NamespaceSpec `json:"namespaces"` + RootNamespaces []RootNamespaceSpec `json:"rootNamespaces"` // ArgoCD is the settings of Argo CD for this tenant // +optional @@ -36,8 +36,8 @@ type TenantSpec struct { Delegates []Delegate `json:"delegates,omitempty"` } -// NamespaceSpec defines the desired state of Namespace -type NamespaceSpec struct { +// RootNamespaceSpec defines the desired state of Namespace +type RootNamespaceSpec struct { // Name is the name of namespace to be generated // +kubebuilder:validation:Required Name string `json:"name"` diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 45a44d4..520a1d9 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -51,7 +51,7 @@ func (in *Delegate) DeepCopy() *Delegate { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *NamespaceSpec) DeepCopyInto(out *NamespaceSpec) { +func (in *RootNamespaceSpec) DeepCopyInto(out *RootNamespaceSpec) { *out = *in if in.Labels != nil { in, out := &in.Labels, &out.Labels @@ -69,12 +69,12 @@ func (in *NamespaceSpec) DeepCopyInto(out *NamespaceSpec) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NamespaceSpec. -func (in *NamespaceSpec) DeepCopy() *NamespaceSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RootNamespaceSpec. +func (in *RootNamespaceSpec) DeepCopy() *RootNamespaceSpec { if in == nil { return nil } - out := new(NamespaceSpec) + out := new(RootNamespaceSpec) in.DeepCopyInto(out) return out } @@ -141,9 +141,9 @@ func (in *TenantList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { *out = *in - if in.Namespaces != nil { - in, out := &in.Namespaces, &out.Namespaces - *out = make([]NamespaceSpec, len(*in)) + if in.RootNamespaces != nil { + in, out := &in.RootNamespaces, &out.RootNamespaces + *out = make([]RootNamespaceSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/charts/cattage/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml index 10bd096..57623ef 100644 --- a/charts/cattage/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -70,11 +70,11 @@ spec: - roles type: object type: array - namespaces: - description: Namespaces are the list of root namespaces that belong + rootNamespaces: + description: RootNamespaces are the list of root namespaces that belong to this tenant items: - description: NamespaceSpec defines the desired state of Namespace + description: RootNamespaceSpec defines the desired state of Namespace properties: annotations: additionalProperties: @@ -95,7 +95,7 @@ spec: minItems: 1 type: array required: - - namespaces + - rootNamespaces type: object status: description: TenantStatus defines the observed state of Tenant diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index 32bd47e..a6d3263 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -70,11 +70,11 @@ spec: - roles type: object type: array - namespaces: - description: Namespaces are the list of root namespaces that belong + rootNamespaces: + description: RootNamespaces are the list of root namespaces that belong to this tenant items: - description: NamespaceSpec defines the desired state of Namespace + description: RootNamespaceSpec defines the desired state of Namespace properties: annotations: additionalProperties: @@ -95,7 +95,7 @@ spec: minItems: 1 type: array required: - - namespaces + - rootNamespaces type: object status: description: TenantStatus defines the observed state of Tenant diff --git a/config/samples/01_tenant.yaml b/config/samples/01_tenant.yaml index 93f9a08..98cee88 100644 --- a/config/samples/01_tenant.yaml +++ b/config/samples/01_tenant.yaml @@ -3,7 +3,7 @@ kind: Tenant metadata: name: a-team spec: - namespaces: + rootNamespaces: - name: app-a --- apiVersion: cattage.cybozu.io/v1beta1 @@ -11,7 +11,7 @@ kind: Tenant metadata: name: b-team spec: - namespaces: + rootNamespaces: - name: app-b argocd: repositories: diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 2956bed..9cb5484 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -144,7 +144,7 @@ func (r *TenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (res return ctrl.Result{}, nil } -func containNamespace(roots []cattagev1beta1.NamespaceSpec, ns corev1.Namespace) bool { +func containNamespace(roots []cattagev1beta1.RootNamespaceSpec, ns corev1.Namespace) bool { for _, root := range roots { if root.Name == ns.Name { return true @@ -313,7 +313,7 @@ func rolesMap(delegates []cattagev1beta1.Delegate) map[string][]string { } func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *cattagev1beta1.Tenant) error { - for _, ns := range tenant.Spec.Namespaces { + for _, ns := range tenant.Spec.RootNamespaces { namespace := accorev1.Namespace(ns.Name) labels := make(map[string]string) for k, v := range r.config.Namespace.CommonLabels { @@ -376,7 +376,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt return fmt.Errorf("failed to list namespaces: %w", err) } for _, ns := range nss.Items { - if containNamespace(tenant.Spec.Namespaces, ns) { + if containNamespace(tenant.Spec.RootNamespaces, ns) { continue } err := r.disownNamespace(ctx, &ns) diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index 35a9ac7..befe2e0 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -87,7 +87,7 @@ var _ = Describe("Tenant controller", func() { Name: "x-team", }, Spec: cattagev1beta1.TenantSpec{ - Namespaces: []cattagev1beta1.NamespaceSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ { Name: "app-x", Labels: map[string]string{ @@ -205,7 +205,7 @@ var _ = Describe("Tenant controller", func() { Name: "y-team", }, Spec: cattagev1beta1.TenantSpec{ - Namespaces: []cattagev1beta1.NamespaceSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ {Name: "app-y1"}, {Name: "app-y2"}, }, @@ -318,7 +318,7 @@ var _ = Describe("Tenant controller", func() { By("removing app-y2") err = k8sClient.Get(ctx, client.ObjectKey{Name: tenant.Name}, tenant) Expect(err).ToNot(HaveOccurred()) - tenant.Spec.Namespaces = []cattagev1beta1.NamespaceSpec{ + tenant.Spec.RootNamespaces = []cattagev1beta1.RootNamespaceSpec{ {Name: "app-y1"}, } err = k8sClient.Update(ctx, tenant) @@ -372,10 +372,10 @@ var _ = Describe("Tenant controller", func() { By("removing app-y1") err = k8sClient.Get(ctx, client.ObjectKey{Name: tenant.Name}, tenant) Expect(err).ToNot(HaveOccurred()) - tenant.Spec.Namespaces = []cattagev1beta1.NamespaceSpec{} + tenant.Spec.RootNamespaces = []cattagev1beta1.RootNamespaceSpec{} err = k8sClient.Update(ctx, tenant) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("\"y-team\" is invalid: spec.namespaces: Invalid value: 0: spec.namespaces in body should have at least 1 items")) + Expect(err.Error()).Should(ContainSubstring("\"y-team\" is invalid: spec.rootNamespaces: Invalid value: 0: spec.rootNamespaces in body should have at least 1 items")) }) It("should remove tenant", func() { @@ -385,7 +385,7 @@ var _ = Describe("Tenant controller", func() { Finalizers: []string{constants.Finalizer}, }, Spec: cattagev1beta1.TenantSpec{ - Namespaces: []cattagev1beta1.NamespaceSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ {Name: "app-z"}, }, ArgoCD: cattagev1beta1.ArgoCDSpec{}, diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 911936c..57f9524 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -7,7 +7,7 @@ * [ArgoCDSpec](#argocdspec) * [Delegate](#delegate) -* [NamespaceSpec](#namespacespec) +* [RootNamespaceSpec](#rootnamespacespec) * [TenantList](#tenantlist) * [TenantSpec](#tenantspec) * [TenantStatus](#tenantstatus) @@ -33,9 +33,9 @@ Delegate defines a tenant that is delegated access to a tenant. [Back to Custom Resources](#custom-resources) -#### NamespaceSpec +#### RootNamespaceSpec -NamespaceSpec defines the desired state of Namespace +RootNamespaceSpec defines the desired state of Namespace | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | @@ -74,7 +74,7 @@ TenantSpec defines the desired state of Tenant | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | -| namespaces | Namespaces are the list of root namespaces that belong to this tenant | [][NamespaceSpec](#namespacespec) | true | +| rootNamespaces | RootNamespaces are the list of root namespaces that belong to this tenant | [][RootNamespaceSpec](#rootnamespacespec) | true | | argocd | ArgoCD is the settings of Argo CD for this tenant | [ArgoCDSpec](#argocdspec) | false | | delegates | Delegates is a list of other tenants that are delegated access to this tenant. | [][Delegate](#delegate) | false | diff --git a/docs/usage.md b/docs/usage.md index 99cc2ec..ee75fe5 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -10,7 +10,7 @@ kind: Tenant metadata: name: your-team spec: - namespaces: + rootNamespaces: - name: your-root ``` @@ -113,7 +113,7 @@ kind: Tenant metadata: name: new-team spec: - namespaces: + rootNamespaces: - name: new-root ``` @@ -136,7 +136,7 @@ When a tenant user delete an Application resource on the tenant's namespace, an If `resources-finalizer.argocd.argoproj.io` is annotated, resources deployed by the Application will be deleted. When an administrator deleted a tenant resource: -- Namespaces for the tenant will remain +- Root-namespaces and sub-namespaces for the tenant will remain - RoleBinding on the namespaces will be deleted - Applications on the namespaces will be deleted - AppProject for the tenant will be deleted diff --git a/hooks/tenant.go b/hooks/tenant.go index 06a8def..345d74e 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -81,7 +81,7 @@ func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) adm return admission.Errored(http.StatusBadRequest, err) } - for _, ns := range tenant.Spec.Namespaces { + for _, ns := range tenant.Spec.RootNamespaces { namespace := &corev1.Namespace{} err := v.client.Get(ctx, client.ObjectKey{Name: ns.Name}, namespace) if apierrors.IsNotFound(err) { diff --git a/hooks/tenant_test.go b/hooks/tenant_test.go index 80cb767..6808b10 100644 --- a/hooks/tenant_test.go +++ b/hooks/tenant_test.go @@ -20,7 +20,7 @@ var _ = Describe("Tenant webhook", func() { Name: "a-team", }, Spec: cattagev1beta1.TenantSpec{ - Namespaces: []cattagev1beta1.NamespaceSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ { Name: "app-new", }, @@ -42,7 +42,7 @@ var _ = Describe("Tenant webhook", func() { Name: "b-team", }, Spec: cattagev1beta1.TenantSpec{ - Namespaces: []cattagev1beta1.NamespaceSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ { Name: "app-y-team", }, @@ -60,7 +60,7 @@ var _ = Describe("Tenant webhook", func() { Name: "d-team", }, Spec: cattagev1beta1.TenantSpec{ - Namespaces: []cattagev1beta1.NamespaceSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ { Name: "template", }, @@ -78,7 +78,7 @@ var _ = Describe("Tenant webhook", func() { Name: "e-team", }, Spec: cattagev1beta1.TenantSpec{ - Namespaces: []cattagev1beta1.NamespaceSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ { Name: "sub-2", }, From 0c3a71feeaf5c01b705eebb2eb392c32a8000b02 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 14 Jan 2022 16:09:12 +0900 Subject: [PATCH 56/89] GoReleaser & container-structure-test Signed-off-by: zoetrope --- .github/workflows/ci.yaml | 37 +++++++++++++++++++++++ .github/workflows/release.yaml | 37 +++++++++++++++-------- .goreleaser.yml | 55 ++++++++++++++++++++++++++++++++++ Dockerfile | 18 +++++------ Makefile | 8 ++--- aqua.yaml | 1 + cst.yaml | 17 +++++++++++ e2e/Makefile | 1 + 8 files changed, 146 insertions(+), 28 deletions(-) create mode 100644 .goreleaser.yml create mode 100644 cst.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9b43d79..3a5ede4 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -17,6 +17,19 @@ jobs: with: go-version: ${{ env.go-version }} - run: make build + check-goreleaser-config: + name: Check goreleaser.yml + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: Setup go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.go-version }} + - uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: check -f .goreleaser.yml test: name: Small tests runs-on: ubuntu-20.04 @@ -57,3 +70,27 @@ jobs: with: name: logs-${{ matrix.k8s-version }}.tar.gz path: e2e/logs.tar.gz + dry-run: + name: Dry-run release + runs-on: ubuntu-20.04 + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-buildx-action@v1 + - name: Setup go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.go-version }} + - name: GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: --snapshot --skip-publish --rm-dist + - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + with: + aqua_version: v0.10.0 + - name: Test built containers + run: make container-structure-test diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index f64e9d7..d5cf856 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -6,18 +6,29 @@ on: env: go-version: 1.17 jobs: - image: - name: Push Container Image + release: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v2 - - name: Build images - run: | - docker build -t cattage:dev . - - name: Login to ghcr.io - run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u $GITHUB_ACTOR --password-stdin - - name: Push images - run: | - TAG=${GITHUB_REF#refs/tags/v} - docker tag cattage:dev ghcr.io/cybozu-go/cattage:$TAG - docker push ghcr.io/cybozu-go/cattage:$TAG + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-buildx-action@v1 + - name: GHCR Login + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Setup go + uses: actions/setup-go@v2 + with: + go-version: ${{ env.go-version }} + - name: GoReleaser + uses: goreleaser/goreleaser-action@v2 + with: + version: latest + args: release --rm-dist + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..8033983 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,55 @@ +project_name: cattage +dist: bin/ +builds: + - env: + - CGO_ENABLED=0 + main: ./cmd/cattage-controller + binary: cattage-controller + goos: + - linux + goarch: + - amd64 + - arm64 +dockers: + - image_templates: + - "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Version }}-amd64" + use: buildx + dockerfile: Dockerfile + extra_files: + - LICENSE + build_flag_templates: + - "--platform=linux/amd64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - image_templates: + - "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Version }}-arm64" + use: buildx + goarch: arm64 + dockerfile: Dockerfile + extra_files: + - LICENSE + build_flag_templates: + - "--platform=linux/arm64" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" +docker_manifests: + - name_template: "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Version }}" + image_templates: + - "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Version }}-amd64" + - "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Version }}-arm64" + - name_template: "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Major }}.{{ .Minor }}" + image_templates: + - "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Version }}-amd64" + - "ghcr.io/cybozu-go/{{.ProjectName}}:{{ .Version }}-arm64" +checksum: + name_template: 'checksums.txt' +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' diff --git a/Dockerfile b/Dockerfile index edb2091..6139603 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,10 @@ -# Build the manager binary -FROM quay.io/cybozu/golang:1.17-focal as builder - -COPY ./ . -RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o cattage-controller ./cmd/cattage-controller - -# the controller image FROM scratch -LABEL org.opencontainers.image.source https://github.com/cybozu-go/cattage - -COPY --from=builder /work/cattage-controller ./ -USER 10000:10000 +LABEL org.opencontainers.image.authors="Cybozu, Inc." \ + org.opencontainers.image.title="cattage" \ + org.opencontainers.image.source="https://github.com/cybozu-go/cattage" +WORKDIR / +COPY LICENSE / +COPY cattage-controller / +USER 65532:65532 ENTRYPOINT ["/cattage-controller"] diff --git a/Makefile b/Makefile index c2f948e..c63d24d 100644 --- a/Makefile +++ b/Makefile @@ -88,6 +88,10 @@ test: test-tools $(STATICCHECK) ./... $(NILERR) ./... +.PHONY: container-structure-test +container-structure-test: + container-structure-test test --image ghcr.io/cybozu-go/cattage:$(shell git describe --tags --abbrev=0 || echo v0.0.0)-next-amd64 --config cst.yaml + ##@ Build .PHONY: build @@ -95,10 +99,6 @@ build: mkdir -p bin GOBIN=$(shell pwd)/bin go install ./cmd/... -.PHONY: docker-build -docker-build: - docker build -t cattage:latest . - ##@ Development .PHONY: dev diff --git a/aqua.yaml b/aqua.yaml index 6fa9b35..fd804c9 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -17,5 +17,6 @@ packages: - name: rust-lang/mdBook@v0.4.15 - name: tilt-dev/tilt@v0.23.5 - name: tilt-dev/ctlptl@v0.7.0 + - name: GoogleContainerTools/container-structure-test@v1.11.0 - name: clamoriniere/crd-to-markdown@v0.0.3 registry: local diff --git a/cst.yaml b/cst.yaml new file mode 100644 index 0000000..c38fca5 --- /dev/null +++ b/cst.yaml @@ -0,0 +1,17 @@ +schemaVersion: "2.0.0" +fileExistenceTests: + - name: "cattage-controller" + path: "/cattage-controller" + shouldExist: true + permissions: "-rwxr-xr-x" +metadataTest: + entrypoint: ["/cattage-controller"] + labels: + - key: "org.opencontainers.image.authors" + value: "Cybozu, Inc." + - key: "org.opencontainers.image.title" + value: "cattage" + - key: "org.opencontainers.image.source" + value: "https://github.com/cybozu-go/cattage" +licenseTests: + - files: ["/LICENSE"] diff --git a/e2e/Makefile b/e2e/Makefile index b18a278..abd1b26 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -16,6 +16,7 @@ help: .PHONY: start start: kind create cluster --name=cattage --config=$(KIND_CONFIG) --image=kindest/node:v$(KUBERNETES_VERSION) --wait 1m + cd ..; CGO_ENABLED=0 go build -o cattage-controller ./cmd/cattage-controller/main.go cd ..; docker build --no-cache -t cattage:dev . kind load docker-image cattage:dev --name=cattage $(MAKE) prepare From cc2114565412617d912c7e937ccd3cd9ae60c596 Mon Sep 17 00:00:00 2001 From: Akihiro Ikezoe Date: Tue, 18 Jan 2022 18:23:24 +0900 Subject: [PATCH 57/89] Update docs/config.md Co-authored-by: Daichi Sakaue --- docs/config.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config.md b/docs/config.md index 99e9eeb..7288060 100644 --- a/docs/config.md +++ b/docs/config.md @@ -9,8 +9,8 @@ The configuration file should be a JSON or YAML file having the following keys: | Key | Type | Description | |---------------------------------|---------------------|--------------------------------------------------------------------------------------------| -| `namespace.commonLabels` | `map[string]string` | Labels to be added to all namespaces belonging to a tenant. | -| `namespace.commonAnnotations` | `map[string]string` | Annotations to be added to all namespaces belonging to a tenant. | +| `namespace.commonLabels` | `map[string]string` | Labels to be added to all namespaces belonging to all tenants. | +| `namespace.commonAnnotations` | `map[string]string` | Annotations to be added to all namespaces belonging to all tenants. | | `namespace.roleBindingTemplate` | `string` | Template for RoleBinding resource that is created on all namespaces belonging to a tenant. | | `argocd.namepsace` | `string` | The name of namespace where Argo CD is running. | | `argocd.appProjectTemplate` | `string` | Template for AppProject resources that is created for each tenant. | From fd791de2ba35ac81b8f43857a66a4f781b82bcc7 Mon Sep 17 00:00:00 2001 From: Akihiro Ikezoe Date: Tue, 18 Jan 2022 18:23:59 +0900 Subject: [PATCH 58/89] Update docs/design.md Co-authored-by: Daichi Sakaue --- docs/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design.md b/docs/design.md index ef6c662..49c7109 100644 --- a/docs/design.md +++ b/docs/design.md @@ -6,7 +6,7 @@ Cattage is a Kubernetes controller that enhances the multi-tenancy of [Argo CD][ ## Motivation -Argo CD has a problem that it is difficult to implement app-of-apps pattern in a multi-tenancy environment. +There is a known limitation for Argo CD to implement app-of-apps pattern in a multi-tenancy environment. https://github.com/argoproj/argo-cd/issues/2785 From 91394fb7f6e2203bbad0e1c1caf1f79f7fc4d25a Mon Sep 17 00:00:00 2001 From: Akihiro Ikezoe Date: Tue, 18 Jan 2022 18:24:24 +0900 Subject: [PATCH 59/89] Update docs/design.md Co-authored-by: Daichi Sakaue --- docs/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design.md b/docs/design.md index 49c7109..f92f9c6 100644 --- a/docs/design.md +++ b/docs/design.md @@ -14,7 +14,7 @@ We have developed the following mechanism to resolve the problem. https://blog.kintone.io/entry/production-grade-delivery-workflow-using-argocd#Multi-tenancy -However, the mechanism has the following problems: +However, the mechanism still has the following problems: - Tenant users cannot create app-of-apps Application resources. They need to ask an administrator for that. - Application resources are not strictly validated. Tenant users can specify Project for other tenants, and can also specify duplicate names. From 5d3fc32d909b1d60aa6f14c915d6be118efec68e Mon Sep 17 00:00:00 2001 From: Akihiro Ikezoe Date: Tue, 18 Jan 2022 18:24:44 +0900 Subject: [PATCH 60/89] Update docs/design.md Co-authored-by: Daichi Sakaue --- docs/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design.md b/docs/design.md index f92f9c6..d5c8a1c 100644 --- a/docs/design.md +++ b/docs/design.md @@ -21,7 +21,7 @@ However, the mechanism still has the following problems: - When a SubNamespace is created in [HNC][] or [Accurate][], an administrator needs to add it to the destinations of the Application resource. (Argo CD supports specifying wildcards in destinations, but that is not enough for us.) -We are hoping for a better solution. +We need to build a better solution. ## Goals From 834d6714db5de6364aefbe77a275f1b60984766a Mon Sep 17 00:00:00 2001 From: Akihiro Ikezoe Date: Tue, 18 Jan 2022 18:25:05 +0900 Subject: [PATCH 61/89] Update docs/design.md Co-authored-by: Daichi Sakaue --- docs/design.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design.md b/docs/design.md index d5c8a1c..bfd2ec1 100644 --- a/docs/design.md +++ b/docs/design.md @@ -35,7 +35,7 @@ We need to build a better solution. ### Adding a tenant An administrator only need to create one custom resource to add a tenant to a Kubernetes cluster. -No more manual operations to add Applications AppProjects and Namespaces. +No more manual operations to add Applications, AppProjects and Namespaces. Tenant users can create sub-namespaces within their tenant. From 781133ec25093702b9af997f6148d159e24509c4 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 18 Jan 2022 17:50:32 +0900 Subject: [PATCH 62/89] Remove acknowledgements Signed-off-by: zoetrope --- api/v1beta1/groupversion_info.go | 16 ---------------- api/v1beta1/tenant_types.go | 16 ---------------- controllers/suite_test.go | 16 ---------------- controllers/tenant_controller.go | 16 ---------------- hooks/suite_test.go | 16 ---------------- hooks/tenant.go | 16 ---------------- 6 files changed, 96 deletions(-) diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go index 1c9d0b4..7b6cb38 100644 --- a/api/v1beta1/groupversion_info.go +++ b/api/v1beta1/groupversion_info.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 v1beta1 contains API Schema definitions for the multi-tenancy v1beta1 API group //+kubebuilder:object:generate=true //+groupName=cattage.cybozu.io diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index a12b84f..641ad28 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_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 v1beta1 import ( diff --git a/controllers/suite_test.go b/controllers/suite_test.go index f1ac846..fc803b5 100644 --- a/controllers/suite_test.go +++ b/controllers/suite_test.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 controllers import ( diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 9cb5484..aac22b6 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.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 controllers import ( diff --git a/hooks/suite_test.go b/hooks/suite_test.go index 4fd6b30..fdaf67c 100644 --- a/hooks/suite_test.go +++ b/hooks/suite_test.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 hooks import ( diff --git a/hooks/tenant.go b/hooks/tenant.go index 345d74e..ce7e717 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.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 hooks import ( From a2cadcfb869e4b5ee500e8a59236b5399c45f3e5 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 18 Jan 2022 17:53:56 +0900 Subject: [PATCH 63/89] Fix group and domain Signed-off-by: zoetrope --- PROJECT | 2 +- api/v1beta1/groupversion_info.go | 2 +- charts/cattage/templates/generated.yaml | 4 ++-- config/webhook/manifests.yaml | 4 ++-- hooks/tenant.go | 8 ++++---- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/PROJECT b/PROJECT index 2d3fb33..a871c26 100644 --- a/PROJECT +++ b/PROJECT @@ -9,7 +9,7 @@ resources: namespaced: true controller: true domain: cybozu.io - group: multi-tenancy + group: cattage kind: Tenant path: github.com/cybozu-go/cattage/api/v1beta1 version: v1beta1 diff --git a/api/v1beta1/groupversion_info.go b/api/v1beta1/groupversion_info.go index 7b6cb38..38bc13b 100644 --- a/api/v1beta1/groupversion_info.go +++ b/api/v1beta1/groupversion_info.go @@ -1,4 +1,4 @@ -// Package v1beta1 contains API Schema definitions for the multi-tenancy v1beta1 API group +// Package v1beta1 contains API Schema definitions for the cattage v1beta1 API group //+kubebuilder:object:generate=true //+groupName=cattage.cybozu.io package v1beta1 diff --git a/charts/cattage/templates/generated.yaml b/charts/cattage/templates/generated.yaml index 3c853ec..dee22ed 100644 --- a/charts/cattage/templates/generated.yaml +++ b/charts/cattage/templates/generated.yaml @@ -327,7 +327,7 @@ webhooks: service: name: '{{ template "cattage.fullname" . }}-webhook-service' namespace: '{{ .Release.Namespace }}' - path: /mutate-multi-tenancy-cybozu-com-v1beta1-tenant + path: /mutate-cattage-cybozu-io-v1beta1-tenant failurePolicy: Fail name: mtenant.kb.io rules: @@ -381,7 +381,7 @@ webhooks: service: name: '{{ template "cattage.fullname" . }}-webhook-service' namespace: '{{ .Release.Namespace }}' - path: /validate-multi-tenancy-cybozu-com-v1beta1-tenant + path: /validate-cattage-cybozu-io-v1beta1-tenant failurePolicy: Fail name: vtenant.kb.io rules: diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index 07c16d3..e084ecd 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -31,7 +31,7 @@ webhooks: service: name: webhook-service namespace: system - path: /mutate-multi-tenancy-cybozu-com-v1beta1-tenant + path: /mutate-cattage-cybozu-io-v1beta1-tenant failurePolicy: Fail name: mtenant.kb.io rules: @@ -78,7 +78,7 @@ webhooks: service: name: webhook-service namespace: system - path: /validate-multi-tenancy-cybozu-com-v1beta1-tenant + path: /validate-cattage-cybozu-io-v1beta1-tenant failurePolicy: Fail name: vtenant.kb.io rules: diff --git a/hooks/tenant.go b/hooks/tenant.go index ce7e717..94d4699 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -18,7 +18,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) -//+kubebuilder:webhook:path=/mutate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=cattage.cybozu.io,resources=tenants,verbs=create;update,versions=v1beta1,name=mtenant.kb.io,admissionReviewVersions={v1} +//+kubebuilder:webhook:path=/mutate-cattage-cybozu-io-v1beta1-tenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=cattage.cybozu.io,resources=tenants,verbs=create;update,versions=v1beta1,name=mtenant.kb.io,admissionReviewVersions={v1} type tenantMutator struct { dec *admission.Decoder @@ -45,7 +45,7 @@ func (m *tenantMutator) Handle(ctx context.Context, req admission.Request) admis return admission.PatchResponseFromRaw(req.Object.Raw, data) } -//+kubebuilder:webhook:path=/validate-multi-tenancy-cybozu-com-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=cattage.cybozu.io,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions={v1} +//+kubebuilder:webhook:path=/validate-cattage-cybozu-io-v1beta1-tenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=cattage.cybozu.io,resources=tenants,verbs=create;update,versions=v1beta1,name=vtenant.kb.io,admissionReviewVersions={v1} type tenantValidator struct { client client.Client @@ -98,12 +98,12 @@ func SetupTenantWebhook(mgr manager.Manager, dec *admission.Decoder, config *con m := &tenantMutator{ dec: dec, } - serv.Register("/mutate-multi-tenancy-cybozu-com-v1beta1-tenant", &webhook.Admission{Handler: m}) + serv.Register("/mutate-cattage-cybozu-io-v1beta1-tenant", &webhook.Admission{Handler: m}) v := &tenantValidator{ client: mgr.GetClient(), dec: dec, config: config, } - serv.Register("/validate-multi-tenancy-cybozu-com-v1beta1-tenant", &webhook.Admission{Handler: v}) + serv.Register("/validate-cattage-cybozu-io-v1beta1-tenant", &webhook.Admission{Handler: v}) } From cf30f2675be3931ef56c79e23bbb2be3bf260c1c Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 18 Jan 2022 18:00:55 +0900 Subject: [PATCH 64/89] Reflect comments Signed-off-by: zoetrope --- config/rbac/kustomization.yaml | 8 ++++---- docs/setup.md | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/rbac/kustomization.yaml b/config/rbac/kustomization.yaml index 731832a..40bc8c2 100644 --- a/config/rbac/kustomization.yaml +++ b/config/rbac/kustomization.yaml @@ -12,7 +12,7 @@ resources: # 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 +#- auth_proxy_service.yaml +#- auth_proxy_role.yaml +#- auth_proxy_role_binding.yaml +#- auth_proxy_client_clusterrole.yaml diff --git a/docs/setup.md b/docs/setup.md index 73bd7fe..9e98a6b 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -7,7 +7,7 @@ Namespaces must be isolated for each tenant. There are many ways to achieve Namespace isolation. In EKS and GKE, you can integrate RBAC with IAM. -For on-premises, [Telepot](https://goteleport.com) and [Loft](https://loft.sh) may help you. +For on-premises, [Teleport](https://goteleport.com) and [Loft](https://loft.sh) may help you. ## Argo CD From e907593f13c3827caea8a57b015492d9c904e0ce Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 18 Jan 2022 18:09:10 +0900 Subject: [PATCH 65/89] Fix the name of field-manager Signed-off-by: zoetrope --- controllers/tenant_controller.go | 14 +++++++------- pkg/constants/meta.go | 9 +++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index aac22b6..37d9360 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -138,7 +138,7 @@ func containNamespace(roots []cattagev1beta1.RootNamespaceSpec, ns corev1.Namesp } func (r *TenantReconciler) disownNamespace(ctx context.Context, ns *corev1.Namespace) error { - managed, err := accorev1.ExtractNamespace(ns, constants.FieldManager) + managed, err := accorev1.ExtractNamespace(ns, constants.TenantFieldManager) if err != nil { return err } @@ -240,7 +240,7 @@ func (r *TenantReconciler) patchNamespace(ctx context.Context, ns *accorev1.Name return err } - managed, err := accorev1.ExtractNamespace(&orig, constants.FieldManager) + managed, err := accorev1.ExtractNamespace(&orig, constants.TenantFieldManager) if err != nil { return err } @@ -250,7 +250,7 @@ func (r *TenantReconciler) patchNamespace(ctx context.Context, ns *accorev1.Name } return r.client.Patch(ctx, patch, client.Apply, &client.PatchOptions{ - FieldManager: constants.FieldManager, + FieldManager: constants.TenantFieldManager, Force: pointer.Bool(true), }) } @@ -270,7 +270,7 @@ func (r *TenantReconciler) patchRoleBinding(ctx context.Context, rb *acrbacv1.Ro return err } - managed, err := acrbacv1.ExtractRoleBinding(&orig, constants.FieldManager) + managed, err := acrbacv1.ExtractRoleBinding(&orig, constants.TenantFieldManager) if err != nil { return err } @@ -280,7 +280,7 @@ func (r *TenantReconciler) patchRoleBinding(ctx context.Context, rb *acrbacv1.Ro } return r.client.Patch(ctx, patch, client.Apply, &client.PatchOptions{ - FieldManager: constants.FieldManager, + FieldManager: constants.TenantFieldManager, Force: pointer.Bool(true), }) } @@ -429,7 +429,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev constants.OwnerTenant: tenant.Name, }) - managed, err := extract.ExtractManagedFields(orig, constants.FieldManager) + managed, err := extract.ExtractManagedFields(orig, constants.TenantFieldManager) if err != nil { return err } @@ -439,7 +439,7 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev err = r.client.Patch(ctx, proj, client.Apply, &client.PatchOptions{ Force: pointer.BoolPtr(true), - FieldManager: constants.FieldManager, + FieldManager: constants.TenantFieldManager, }) if err != nil { logger.Error(err, "failed to patch AppProject") diff --git a/pkg/constants/meta.go b/pkg/constants/meta.go index b950e89..657211f 100644 --- a/pkg/constants/meta.go +++ b/pkg/constants/meta.go @@ -10,7 +10,8 @@ const OwnerTenant = MetaPrefix + "tenant" const OwnerAppNamespace = MetaPrefix + "owner-namespace" -const FieldManager = MetaPrefix + "cattage" -const StatusFieldManager = FieldManager + "/status" -const SpecFieldManager = FieldManager + "/spec" -const ProjectFieldManager = FieldManager + "/project" +const TenantFieldManager = MetaPrefix + "tenant-controller" +const ApplicationFieldManager = MetaPrefix + "application-controller" +const StatusFieldManager = ApplicationFieldManager + "/status" +const SpecFieldManager = ApplicationFieldManager + "/spec" +const ProjectFieldManager = ApplicationFieldManager + "/project" From 7d3829a85f4f15f8deff4d28713148c920ee3556 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 27 Jan 2022 20:20:57 +0900 Subject: [PATCH 66/89] Use yq to format manifests for helm Signed-off-by: zoetrope --- Makefile | 4 +- aqua.yaml | 1 + charts/cattage/crds/tenant.yaml | 285 ++++++------- charts/cattage/templates/generated.yaml | 506 ++++++++++-------------- 4 files changed, 343 insertions(+), 453 deletions(-) diff --git a/Makefile b/Makefile index c63d24d..cb9555a 100644 --- a/Makefile +++ b/Makefile @@ -44,8 +44,8 @@ help: ## Display this help. .PHONY: manifests manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases - kustomize build config/helm/crds > charts/cattage/crds/tenant.yaml - kustomize build config/helm/templates > charts/cattage/templates/generated.yaml + kustomize build config/helm/crds | yq e "." - > charts/cattage/crds/tenant.yaml + kustomize build config/helm/templates | yq e "." - > charts/cattage/templates/generated.yaml .PHONY: generate diff --git a/aqua.yaml b/aqua.yaml index fd804c9..66d8a0a 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -18,5 +18,6 @@ packages: - name: tilt-dev/tilt@v0.23.5 - name: tilt-dev/ctlptl@v0.7.0 - name: GoogleContainerTools/container-structure-test@v1.11.0 + - name: mikefarah/yq@v4.17.2 - name: clamoriniere/crd-to-markdown@v0.0.3 registry: local diff --git a/charts/cattage/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml index 57623ef..78576c3 100644 --- a/charts/cattage/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -15,169 +15,136 @@ spec: singular: tenant scope: Cluster versions: - - additionalPrinterColumns: - - jsonPath: .status.health - name: STATUS - type: string - name: v1beta1 - schema: - openAPIV3Schema: - description: Tenant is the Schema for the tenants 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: TenantSpec defines the desired state of Tenant - properties: - argocd: - description: ArgoCD is the settings of Argo CD for this tenant - properties: - repositories: - description: Repositories contains list of repository URLs which - can be used by the tenant. - items: - type: string - type: array - type: object - delegates: - description: Delegates is a list of other tenants that are delegated - access to this tenant. - items: - description: Delegate defines a tenant that is delegated access - to a tenant. + - additionalPrinterColumns: + - jsonPath: .status.health + name: STATUS + type: string + name: v1beta1 + schema: + openAPIV3Schema: + description: Tenant is the Schema for the tenants 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: TenantSpec defines the desired state of Tenant + properties: + argocd: + description: ArgoCD is the settings of Argo CD for this tenant properties: - name: - description: Name is the name of a delegated tenant - type: string - roles: - description: Roles is a list of roles that the tenant has + repositories: + description: Repositories contains list of repository URLs which can be used by the tenant. items: type: string - minItems: 1 type: array - required: - - name - - roles type: object - type: array - rootNamespaces: - description: RootNamespaces are the list of root namespaces that belong - to this tenant - items: - description: RootNamespaceSpec defines the desired state of Namespace - properties: - annotations: - additionalProperties: + delegates: + description: Delegates is a list of other tenants that are delegated access to this tenant. + items: + description: Delegate defines a tenant that is delegated access to a tenant. + properties: + name: + description: Name is the name of a delegated tenant type: string - description: Annotations are the annotations to add to the namespace - type: object - labels: - additionalProperties: + roles: + description: Roles is a list of roles that the tenant has + items: + type: string + minItems: 1 + type: array + required: + - name + - roles + type: object + type: array + rootNamespaces: + description: RootNamespaces are the list of root namespaces that belong to this tenant + items: + description: RootNamespaceSpec defines the desired state of Namespace + properties: + annotations: + additionalProperties: + type: string + description: Annotations are the annotations to add to the namespace + type: object + labels: + additionalProperties: + type: string + description: Labels are the labels to add to the namespace + type: object + name: + description: Name is the name of namespace to be generated type: string - description: Labels are the labels to add to the namespace - type: object - name: - description: Name is the name of namespace to be generated - type: string - required: - - name - type: object - minItems: 1 - type: array - required: - - rootNamespaces - type: object - status: - description: TenantStatus defines the observed state of Tenant - properties: - conditions: - description: Conditions is an array of conditions. - items: - description: "Condition contains details for one aspect of the current - state of this API Resource. --- This struct is intended for direct - use as an array at the field path .status.conditions. For example, - type FooStatus struct{ // Represents the observations of a foo's - current state. // Known .status.conditions.type are: \"Available\", - \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge - // +listType=map // +listMapKey=type Conditions []metav1.Condition - `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" - protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" - properties: - lastTransitionTime: - description: lastTransitionTime is the last time the condition - transitioned from one status to another. This should be when - the underlying condition changed. If that is not known, then - using the time when the API field changed is acceptable. - format: date-time - type: string - message: - description: message is a human readable message indicating - details about the transition. This may be an empty string. - maxLength: 32768 - type: string - observedGeneration: - description: observedGeneration represents the .metadata.generation - that the condition was set based upon. For instance, if .metadata.generation - is currently 12, but the .status.conditions[x].observedGeneration - is 9, the condition is out of date with respect to the current - state of the instance. - format: int64 - minimum: 0 - type: integer - reason: - description: reason contains a programmatic identifier indicating - the reason for the condition's last transition. Producers - of specific condition types may define expected values and - meanings for this field, and whether the values are considered - a guaranteed API. The value should be a CamelCase string. - This field may not be empty. - maxLength: 1024 - minLength: 1 - pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ - type: string - status: - description: status of the condition, one of True, False, Unknown. - enum: - - "True" - - "False" - - Unknown - type: string - type: - description: type of condition in CamelCase or in foo.example.com/CamelCase. - --- Many .condition.type values are consistent across resources - like Available, but because arbitrary conditions can be useful - (see .node.status.conditions), the ability to deconflict is - important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) - maxLength: 316 - pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ - type: string - required: - - lastTransitionTime - - message - - reason - - status - - type - type: object - type: array - health: - description: Health is the health of Tenant. - enum: - - Healthy - - Unhealthy - type: string - type: object - type: object - served: true - storage: true - subresources: - status: {} + required: + - name + type: object + minItems: 1 + type: array + required: + - rootNamespaces + type: object + status: + description: TenantStatus defines the observed state of Tenant + properties: + conditions: + description: Conditions is an array of conditions. + items: + description: "Condition contains details for one aspect of the current state of this API Resource. --- This struct is intended for direct use as an array at the field path .status.conditions. For example, type FooStatus struct{ // Represents the observations of a foo's current state. // Known .status.conditions.type are: \"Available\", \"Progressing\", and \"Degraded\" // +patchMergeKey=type // +patchStrategy=merge // +listType=map // +listMapKey=type Conditions []metav1.Condition `json:\"conditions,omitempty\" patchStrategy:\"merge\" patchMergeKey:\"type\" protobuf:\"bytes,1,rep,name=conditions\"` \n // other fields }" + properties: + lastTransitionTime: + description: lastTransitionTime is the last time the condition transitioned from one status to another. This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: message is a human readable message indicating details about the transition. This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: observedGeneration represents the .metadata.generation that the condition was set based upon. For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: reason contains a programmatic identifier indicating the reason for the condition's last transition. Producers of specific condition types may define expected values and meanings for this field, and whether the values are considered a guaranteed API. The value should be a CamelCase string. This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + health: + description: Health is the health of Tenant. + enum: + - Healthy + - Unhealthy + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/charts/cattage/templates/generated.yaml b/charts/cattage/templates/generated.yaml index dee22ed..1f26ffc 100644 --- a/charts/cattage/templates/generated.yaml +++ b/charts/cattage/templates/generated.yaml @@ -20,37 +20,37 @@ metadata: name: '{{ template "cattage.fullname" . }}-leader-election-role' namespace: '{{ .Release.Namespace }}' rules: -- apiGroups: - - "" - resources: - - configmaps - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - coordination.k8s.io - resources: - - leases - verbs: - - get - - list - - watch - - create - - update - - patch - - delete -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch + - apiGroups: + - "" + resources: + - configmaps + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - coordination.k8s.io + resources: + - leases + verbs: + - get + - list + - watch + - create + - update + - patch + - delete + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole @@ -63,136 +63,98 @@ metadata: helm.sh/chart: '{{ include "cattage.chart" . }}' name: '{{ template "cattage.fullname" . }}-manager-role' rules: -- apiGroups: - - "" - resources: - - events - verbs: - - create - - patch - - update -- apiGroups: - - argoproj.io - resources: - - applications - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - argoproj.io - resources: - - appprojects - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - cattage.cybozu.io - resources: - - tenants - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - cattage.cybozu.io - resources: - - tenants/finalizers - verbs: - - update -- apiGroups: - - cattage.cybozu.io - resources: - - tenants/status - verbs: - - get - - patch - - update -- apiGroups: - - "" - resources: - - namespaces - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - rbac.authorization.k8s.io - resources: - - clusterroles - verbs: - - bind - - escalate - - get - - list - - watch -- apiGroups: - - rbac.authorization.k8s.io - resources: - - rolebindings - verbs: - - create - - delete - - get - - list - - patch - - update - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "cattage.name" . }}' - app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "cattage.chart" . }}' - name: '{{ template "cattage.fullname" . }}-metrics-reader' -rules: -- nonResourceURLs: - - /metrics - verbs: - - get ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - labels: - app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "cattage.name" . }}' - app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "cattage.chart" . }}' - name: '{{ template "cattage.fullname" . }}-proxy-role' -rules: -- apiGroups: - - authentication.k8s.io - resources: - - tokenreviews - verbs: - - create -- apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create + - apiGroups: + - "" + resources: + - events + verbs: + - create + - patch + - update + - apiGroups: + - argoproj.io + resources: + - applications + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - argoproj.io + resources: + - appprojects + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - cattage.cybozu.io + resources: + - tenants + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - cattage.cybozu.io + resources: + - tenants/finalizers + verbs: + - update + - apiGroups: + - cattage.cybozu.io + resources: + - tenants/status + verbs: + - get + - patch + - update + - apiGroups: + - "" + resources: + - namespaces + verbs: + - create + - delete + - get + - list + - patch + - update + - watch + - apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterroles + verbs: + - bind + - escalate + - get + - list + - watch + - apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - create + - delete + - get + - list + - patch + - update + - watch --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding @@ -209,9 +171,9 @@ roleRef: kind: Role name: '{{ template "cattage.fullname" . }}-leader-election-role' subjects: -- kind: ServiceAccount - name: '{{ template "cattage.fullname" . }}-controller-manager' - namespace: '{{ .Release.Namespace }}' + - kind: ServiceAccount + name: '{{ template "cattage.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding @@ -227,47 +189,9 @@ roleRef: kind: ClusterRole name: '{{ template "cattage.fullname" . }}-manager-role' subjects: -- kind: ServiceAccount - name: '{{ template "cattage.fullname" . }}-controller-manager' - namespace: '{{ .Release.Namespace }}' ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - labels: - app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "cattage.name" . }}' - app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "cattage.chart" . }}' - name: '{{ template "cattage.fullname" . }}-proxy-rolebinding' -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: '{{ template "cattage.fullname" . }}-proxy-role' -subjects: -- kind: ServiceAccount - name: '{{ template "cattage.fullname" . }}-controller-manager' - namespace: '{{ .Release.Namespace }}' ---- -apiVersion: v1 -kind: Service -metadata: - labels: - app.kubernetes.io/managed-by: '{{ .Release.Service }}' - app.kubernetes.io/name: '{{ include "cattage.name" . }}' - app.kubernetes.io/version: '{{ .Chart.AppVersion }}' - helm.sh/chart: '{{ include "cattage.chart" . }}' - name: '{{ template "cattage.fullname" . }}-controller-manager-metrics-service' - namespace: '{{ .Release.Namespace }}' -spec: - ports: - - name: https - port: 8443 - protocol: TCP - targetPort: https - selector: - app.kubernetes.io/component: controller - app.kubernetes.io/name: '{{ include "cattage.name" . }}' + - kind: ServiceAccount + name: '{{ template "cattage.fullname" . }}-controller-manager' + namespace: '{{ .Release.Namespace }}' --- apiVersion: v1 kind: Service @@ -281,9 +205,9 @@ metadata: namespace: '{{ .Release.Namespace }}' spec: ports: - - port: 443 - protocol: TCP - targetPort: 9443 + - port: 443 + protocol: TCP + targetPort: 9443 selector: app.kubernetes.io/component: controller app.kubernetes.io/name: '{{ include "cattage.name" . }}' @@ -292,8 +216,7 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" - . }}-serving-cert' + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" . }}-serving-cert' labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' app.kubernetes.io/name: '{{ include "cattage.name" . }}' @@ -301,53 +224,52 @@ metadata: helm.sh/chart: '{{ include "cattage.chart" . }}' name: '{{ template "cattage.fullname" . }}-mutating-webhook-configuration' webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: '{{ template "cattage.fullname" . }}-webhook-service' - namespace: '{{ .Release.Namespace }}' - path: /mutate-argoproj-io-application - failurePolicy: Fail - name: mapplication.kb.io - rules: - - apiGroups: - - argoproj.io - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - applications - sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: '{{ template "cattage.fullname" . }}-webhook-service' - namespace: '{{ .Release.Namespace }}' - path: /mutate-cattage-cybozu-io-v1beta1-tenant - failurePolicy: Fail - name: mtenant.kb.io - rules: - - apiGroups: - - cattage.cybozu.io - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - tenants - sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "cattage.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /mutate-argoproj-io-application + failurePolicy: Fail + name: mapplication.kb.io + rules: + - apiGroups: + - argoproj.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - applications + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "cattage.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /mutate-cattage-cybozu-io-v1beta1-tenant + failurePolicy: Fail + name: mtenant.kb.io + rules: + - apiGroups: + - cattage.cybozu.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - tenants + sideEffects: None --- apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: annotations: - cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" - . }}-serving-cert' + cert-manager.io/inject-ca-from: '{{ .Release.Namespace }}/{{ template "cattage.fullname" . }}-serving-cert' labels: app.kubernetes.io/managed-by: '{{ .Release.Service }}' app.kubernetes.io/name: '{{ include "cattage.name" . }}' @@ -355,43 +277,43 @@ metadata: helm.sh/chart: '{{ include "cattage.chart" . }}' name: '{{ template "cattage.fullname" . }}-validating-webhook-configuration' webhooks: -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: '{{ template "cattage.fullname" . }}-webhook-service' - namespace: '{{ .Release.Namespace }}' - path: /validate-argoproj-io-application - failurePolicy: Fail - name: vapplication.kb.io - rules: - - apiGroups: - - argoproj.io - apiVersions: - - v1alpha1 - operations: - - CREATE - - UPDATE - resources: - - applications - sideEffects: None -- admissionReviewVersions: - - v1 - clientConfig: - service: - name: '{{ template "cattage.fullname" . }}-webhook-service' - namespace: '{{ .Release.Namespace }}' - path: /validate-cattage-cybozu-io-v1beta1-tenant - failurePolicy: Fail - name: vtenant.kb.io - rules: - - apiGroups: - - cattage.cybozu.io - apiVersions: - - v1beta1 - operations: - - CREATE - - UPDATE - resources: - - tenants - sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "cattage.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /validate-argoproj-io-application + failurePolicy: Fail + name: vapplication.kb.io + rules: + - apiGroups: + - argoproj.io + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - applications + sideEffects: None + - admissionReviewVersions: + - v1 + clientConfig: + service: + name: '{{ template "cattage.fullname" . }}-webhook-service' + namespace: '{{ .Release.Namespace }}' + path: /validate-cattage-cybozu-io-v1beta1-tenant + failurePolicy: Fail + name: vtenant.kb.io + rules: + - apiGroups: + - cattage.cybozu.io + apiVersions: + - v1beta1 + operations: + - CREATE + - UPDATE + resources: + - tenants + sideEffects: None From 0548d8beae6f2b496778803eb272247ea1de5aeb Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 27 Jan 2022 20:30:44 +0900 Subject: [PATCH 67/89] Update aqua Signed-off-by: zoetrope --- .github/workflows/ci.yaml | 18 ++++++++++++------ .github/workflows/helm.yaml | 12 ++++++++---- .github/workflows/mdbook.yaml | 6 ++++-- aqua.yaml | 2 +- 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 3a5ede4..1b0630e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,9 +38,11 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.go-version }} - - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 with: - aqua_version: v0.10.0 + aqua_version: v0.10.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: make test - run: make check-generate - run: make envtest @@ -55,9 +57,11 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.go-version }} - - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 with: - aqua_version: v0.10.0 + aqua_version: v0.10.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: make start KUBERNETES_VERSION=${{ matrix.k8s-version }} working-directory: e2e - run: make test @@ -89,8 +93,10 @@ jobs: with: version: latest args: --snapshot --skip-publish --rm-dist - - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 with: - aqua_version: v0.10.0 + aqua_version: v0.10.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Test built containers run: make container-structure-test diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml index e1ded9f..460a7d4 100644 --- a/.github/workflows/helm.yaml +++ b/.github/workflows/helm.yaml @@ -11,9 +11,11 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 with: - aqua_version: v0.10.0 + aqua_version: v0.10.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check chart version run: | tag_version=${GITHUB_REF##*/chart-v} @@ -37,9 +39,11 @@ jobs: - uses: actions/checkout@v2 with: ref: gh-pages - - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 with: - aqua_version: v0.10.0 + aqua_version: v0.10.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - uses: actions/download-artifact@v2 with: name: helm-charts diff --git a/.github/workflows/mdbook.yaml b/.github/workflows/mdbook.yaml index 8722c66..d606d19 100644 --- a/.github/workflows/mdbook.yaml +++ b/.github/workflows/mdbook.yaml @@ -10,9 +10,11 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - uses: aquaproj/aqua-installer@17bae6fde43b710e28a1651c37c7b85fc48fe7ab + - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 with: - aqua_version: v0.10.0 + aqua_version: v0.10.2 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: make book - uses: actions/upload-artifact@v2 with: diff --git a/aqua.yaml b/aqua.yaml index 66d8a0a..188d69a 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -3,7 +3,7 @@ # https://aquaproj.github.io/ registries: - type: standard - ref: v0.13.1 # renovate: depName=aquaproj/aqua-registry + ref: v0.14.4 # renovate: depName=aquaproj/aqua-registry - type: local name: local path: registry.yaml From 6db0363377363ca205ebb91bbc69fa13c03bde27 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 27 Jan 2022 20:50:26 +0900 Subject: [PATCH 68/89] Use EnqueueRequestsFromMapFunc Signed-off-by: zoetrope --- controllers/tenant_controller.go | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 37d9360..2a2bfc0 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -25,12 +25,10 @@ import ( k8syaml "k8s.io/apimachinery/pkg/util/yaml" accorev1 "k8s.io/client-go/applyconfigurations/core/v1" acrbacv1 "k8s.io/client-go/applyconfigurations/rbac/v1" - "k8s.io/client-go/util/workqueue" "k8s.io/utils/pointer" 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/event" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/manager" @@ -452,36 +450,19 @@ func (r *TenantReconciler) reconcileArgoCD(ctx context.Context, tenant *cattagev // SetupWithManager sets up the controller with the Manager. func (r *TenantReconciler) SetupWithManager(mgr ctrl.Manager) error { - tenantHandler := func(o client.Object, q workqueue.RateLimitingInterface) { + tenantHandler := func(o client.Object) []reconcile.Request { owner := o.GetLabels()[constants.OwnerTenant] if owner == "" { - return + return nil } - q.Add(reconcile.Request{NamespacedName: types.NamespacedName{ - Name: owner, - }}) - } - - funcs := handler.Funcs{ - CreateFunc: func(ev event.CreateEvent, q workqueue.RateLimitingInterface) { - tenantHandler(ev.Object, q) - }, - UpdateFunc: func(ev event.UpdateEvent, q workqueue.RateLimitingInterface) { - if ev.ObjectNew.GetDeletionTimestamp() != nil { - return - } - tenantHandler(ev.ObjectOld, q) - }, - DeleteFunc: func(ev event.DeleteEvent, q workqueue.RateLimitingInterface) { - tenantHandler(ev.Object, q) - }, + return []reconcile.Request{{NamespacedName: types.NamespacedName{Name: owner}}} } return ctrl.NewControllerManagedBy(mgr). For(&cattagev1beta1.Tenant{}). - Watches(&source.Kind{Type: &corev1.Namespace{}}, funcs). - Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, funcs). - Watches(&source.Kind{Type: argocd.AppProject()}, funcs). + Watches(&source.Kind{Type: &corev1.Namespace{}}, handler.EnqueueRequestsFromMapFunc(tenantHandler)). + Watches(&source.Kind{Type: &rbacv1.RoleBinding{}}, handler.EnqueueRequestsFromMapFunc(tenantHandler)). + Watches(&source.Kind{Type: argocd.AppProject()}, handler.EnqueueRequestsFromMapFunc(tenantHandler)). Complete(r) } From 6c9c6675c6fd75b6a24095b6855f8917cc5d4a1a Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 27 Jan 2022 22:31:54 +0900 Subject: [PATCH 69/89] Remove only managed resources Signed-off-by: zoetrope --- controllers/tenant_controller.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 2a2bfc0..91421c1 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -166,6 +166,10 @@ func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *cattagev1beta if err != nil { return err } + labels := rb.GetLabels() + if labels == nil || labels[constants.OwnerTenant] != tenant.Name { + return nil + } err = r.client.Delete(ctx, rb) if err != nil { return err @@ -185,6 +189,10 @@ func (r *TenantReconciler) removeAppProject(ctx context.Context, tenant *cattage if err != nil { return err } + labels := proj.GetLabels() + if labels == nil || labels[constants.OwnerTenant] != tenant.Name { + return nil + } return r.client.Delete(ctx, proj) } From 190b082e8763eca17076b34f1dd9f8b3bf513932 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 27 Jan 2022 22:32:06 +0900 Subject: [PATCH 70/89] Fix flaky test Signed-off-by: zoetrope --- controllers/application_controller_test.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index 4e65b3c..ea6768b 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -177,6 +177,15 @@ var _ = Describe("Application controller", func() { return nil }).Should(Succeed()) + events := &corev1.EventList{} + err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) + Expect(err).NotTo(HaveOccurred()) + Expect(events.Items).Should(ConsistOf( + MatchFields(IgnoreExtras, Fields{ + "Reason": Equal("ApplicationSynced"), + }), + )) + ns := &corev1.Namespace{} ns.Name = "sub-2" ns.Labels = map[string]string{} @@ -194,7 +203,7 @@ var _ = Describe("Application controller", func() { return errors.New("application still exists") }).Should(Succeed()) - events := &corev1.EventList{} + events = &corev1.EventList{} err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) Expect(err).NotTo(HaveOccurred()) Expect(events.Items).Should(ConsistOf( From 2f9370246a843c1ce4c9a0f433077aa990dfb284 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 28 Jan 2022 09:51:22 +0900 Subject: [PATCH 71/89] Use composite-action for aqua Signed-off-by: zoetrope --- .github/actions/aqua/action.yaml | 14 ++++++++++++++ .github/workflows/ci.yaml | 18 ++++++------------ .github/workflows/helm.yaml | 12 ++++-------- .github/workflows/mdbook.yaml | 6 ++---- 4 files changed, 26 insertions(+), 24 deletions(-) create mode 100644 .github/actions/aqua/action.yaml diff --git a/.github/actions/aqua/action.yaml b/.github/actions/aqua/action.yaml new file mode 100644 index 0000000..b660c6c --- /dev/null +++ b/.github/actions/aqua/action.yaml @@ -0,0 +1,14 @@ +name: "Setup tools" +description: "Setup tools with aqua" +inputs: + github_token: + description: "GitHub Token" + required: true +runs: + using: composite + steps: + - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 + with: + aqua_version: v0.10.2 + env: + GITHUB_TOKEN: ${{ inputs.github_token }} diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 1b0630e..b41e0d1 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -38,11 +38,9 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.go-version }} - - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 + - uses: ./.github/actions/aqua with: - aqua_version: v0.10.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} - run: make test - run: make check-generate - run: make envtest @@ -57,11 +55,9 @@ jobs: - uses: actions/setup-go@v2 with: go-version: ${{ env.go-version }} - - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 + - uses: ./.github/actions/aqua with: - aqua_version: v0.10.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} - run: make start KUBERNETES_VERSION=${{ matrix.k8s-version }} working-directory: e2e - run: make test @@ -93,10 +89,8 @@ jobs: with: version: latest args: --snapshot --skip-publish --rm-dist - - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 + - uses: ./.github/actions/aqua with: - aqua_version: v0.10.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} - name: Test built containers run: make container-structure-test diff --git a/.github/workflows/helm.yaml b/.github/workflows/helm.yaml index 460a7d4..09d4359 100644 --- a/.github/workflows/helm.yaml +++ b/.github/workflows/helm.yaml @@ -11,11 +11,9 @@ jobs: uses: actions/checkout@v2 with: fetch-depth: 0 - - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 + - uses: ./.github/actions/aqua with: - aqua_version: v0.10.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} - name: Check chart version run: | tag_version=${GITHUB_REF##*/chart-v} @@ -39,11 +37,9 @@ jobs: - uses: actions/checkout@v2 with: ref: gh-pages - - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 + - uses: ./.github/actions/aqua with: - aqua_version: v0.10.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} - uses: actions/download-artifact@v2 with: name: helm-charts diff --git a/.github/workflows/mdbook.yaml b/.github/workflows/mdbook.yaml index d606d19..9c7e133 100644 --- a/.github/workflows/mdbook.yaml +++ b/.github/workflows/mdbook.yaml @@ -10,11 +10,9 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 - - uses: aquaproj/aqua-installer@58fc6b885cba1f956127fa2b40d64eea0400bf7b #v0.7.0 + - uses: ./.github/actions/aqua with: - aqua_version: v0.10.2 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GITHUB_TOKEN }} - run: make book - uses: actions/upload-artifact@v2 with: From 6044f86f8a38a56299a9e35d840a19926ed19165 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 28 Jan 2022 10:27:42 +0900 Subject: [PATCH 72/89] Fix flaky test Signed-off-by: zoetrope --- controllers/application_controller_test.go | 42 +++++++++++----------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index ea6768b..73d1b23 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -177,15 +177,6 @@ var _ = Describe("Application controller", func() { return nil }).Should(Succeed()) - events := &corev1.EventList{} - err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) - Expect(err).NotTo(HaveOccurred()) - Expect(events.Items).Should(ConsistOf( - MatchFields(IgnoreExtras, Fields{ - "Reason": Equal("ApplicationSynced"), - }), - )) - ns := &corev1.Namespace{} ns.Name = "sub-2" ns.Labels = map[string]string{} @@ -203,17 +194,28 @@ var _ = Describe("Application controller", func() { return errors.New("application still exists") }).Should(Succeed()) - events = &corev1.EventList{} - err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) - Expect(err).NotTo(HaveOccurred()) - Expect(events.Items).Should(ConsistOf( - MatchFields(IgnoreExtras, Fields{ - "Reason": Equal("ApplicationSynced"), - }), - MatchFields(IgnoreExtras, Fields{ - "Reason": Equal("ApplicationRemoved"), - }), - )) + Eventually(func() error { + events := &corev1.EventList{} + err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) + if err != nil { + return err + } + var synced, removed bool + for _, ev := range events.Items { + if ev.Reason == "ApplicationSynced" { + synced = true + } else if ev.Reason == "ApplicationRemoved" { + removed = true + } + } + if !synced { + return errors.New("ApplicationSynced event not found") + } + if !removed { + return errors.New("ApplicationRemoved event not found") + } + return nil + }).Should(Succeed()) }) It("should fix project", func() { From 28e44de627e5175a1204999dd05a7227f3ecc537 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 4 Feb 2022 13:44:52 +0900 Subject: [PATCH 73/89] Update dependencies Signed-off-by: zoetrope --- Makefile | 2 +- aqua.yaml | 14 +++++++------- e2e/Makefile | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index cb9555a..c34f431 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Tool versions CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) -ARGOCD_VERSION = 2.2.2 +ARGOCD_VERSION = 2.2.4 # Test tools BIN_DIR := $(shell pwd)/bin diff --git a/aqua.yaml b/aqua.yaml index 188d69a..beb840d 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -3,21 +3,21 @@ # https://aquaproj.github.io/ registries: - type: standard - ref: v0.14.4 # renovate: depName=aquaproj/aqua-registry + ref: v1.0.0 # renovate: depName=aquaproj/aqua-registry - type: local name: local path: registry.yaml packages: - - name: argoproj/argo-cd@v2.2.2 - - name: kubernetes/kubectl@v1.23.1 - - name: kubernetes-sigs/kubebuilder@v3.2.0 + - name: argoproj/argo-cd@v2.2.4 + - name: kubernetes/kubectl@v1.23.3 + - name: kubernetes-sigs/kubebuilder@v3.3.0 - name: kubernetes-sigs/kustomize@kustomize/v4.4.1 - name: kubernetes-sigs/kind@v0.11.1 - name: rust-lang/mdBook@v0.4.15 - - name: tilt-dev/tilt@v0.23.5 - - name: tilt-dev/ctlptl@v0.7.0 + - name: tilt-dev/tilt@v0.23.9 + - name: tilt-dev/ctlptl@v0.7.5 - name: GoogleContainerTools/container-structure-test@v1.11.0 - - name: mikefarah/yq@v4.17.2 + - name: mikefarah/yq@v4.18.1 - name: clamoriniere/crd-to-markdown@v0.0.3 registry: local diff --git a/e2e/Makefile b/e2e/Makefile index abd1b26..12b5693 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -1,5 +1,5 @@ KUBERNETES_VERSION = 1.23.1 -ARGOCD_VERSION = 2.2.2 +ARGOCD_VERSION = 2.2.4 KIND_CONFIG = kind-config.yaml export KUBECONFIG From 92a02f8c9afd0315f866cda47b1150aca2495949 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Fri, 4 Feb 2022 13:48:52 +0900 Subject: [PATCH 74/89] Add a workflow to run tilt ci Signed-off-by: zoetrope --- .github/workflows/ci.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index b41e0d1..5295fa8 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -70,6 +70,20 @@ jobs: with: name: logs-${{ matrix.k8s-version }}.tar.gz path: e2e/logs.tar.gz + tilt: + name: Run tilt ci + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-go@v2 + with: + go-version: ${{ env.go-version }} + - uses: ./.github/actions/aqua + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + - run: make controller-gen + - run: make dev + - run: tilt ci dry-run: name: Dry-run release runs-on: ubuntu-20.04 From 6c3167a6be77bdf6930d5bb857e8f9a29badbd0e Mon Sep 17 00:00:00 2001 From: zoetrope Date: Mon, 7 Feb 2022 19:14:30 +0900 Subject: [PATCH 75/89] Refactor Tiltfie Signed-off-by: zoetrope --- Tiltfile | 102 ++++++++++++------ .../{03_application.yaml => application.yaml} | 0 ...02_subnamespace.yaml => subnamespace.yaml} | 0 .../{00_template.yaml => template.yaml} | 0 .../samples/{01_tenant.yaml => tenant.yaml} | 0 e2e/e2e_test.go | 8 +- 6 files changed, 71 insertions(+), 39 deletions(-) rename config/samples/{03_application.yaml => application.yaml} (100%) rename config/samples/{02_subnamespace.yaml => subnamespace.yaml} (100%) rename config/samples/{00_template.yaml => template.yaml} (100%) rename config/samples/{01_tenant.yaml => tenant.yaml} (100%) diff --git a/Tiltfile b/Tiltfile index 2f3750b..c16b9fc 100644 --- a/Tiltfile +++ b/Tiltfile @@ -1,50 +1,82 @@ load('ext://restart_process', 'docker_build_with_restart') -load('ext://cert_manager', 'deploy_cert_manager') -def kubebuilder(): +DOCKERFILE = '''FROM golang:alpine +WORKDIR / +COPY ./bin/cattage-controller / +CMD ["/cattage-controller"] +''' - DOCKERFILE = '''FROM golang:alpine - WORKDIR / - COPY ./bin/cattage-controller / - CMD ["/cattage-controller"] - ''' - def manifests(): - return './bin/controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases;' +def manifests(): + return './bin/controller-gen crd rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases;' - def generate(): - return './bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' - def binary(): - return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/cattage-controller cmd/cattage-controller/main.go' +def generate(): + return './bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' - installed = local("which kubebuilder") - print("kubebuilder is present:", installed) - DIRNAME = os.path.basename(os. getcwd()) +def binary(): + return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/cattage-controller cmd/cattage-controller/main.go' - local(manifests() + generate()) +# Don't watch generated files +watch_settings(ignore=['config/crd/bases/', 'config/rbac/role.yaml', 'config/webhook/manifests.yaml']) - local_resource('CRD', manifests() + 'kustomize build config/crd | kubectl apply -f -', deps=["api"], ignore=['*/*/zz_generated.deepcopy.go']) +# Generate manifests and go files +local(manifests() + generate()) - watch_settings(ignore=['config/crd/bases/', 'config/rbac/role.yaml', 'config/webhook/manifests.yaml']) - k8s_yaml(kustomize('./config/dev')) +# Deploy CRD +local_resource( + 'CRD', manifests() + 'kustomize build config/crd | kubectl apply -f -', deps=["api"], + ignore=['*/*/zz_generated.deepcopy.go'], labels=['cattage']) - deps = ['controllers', 'pkg', 'hooks', 'cmd', 'version.go'] - deps.append('api') +# Deploy Cattage +k8s_yaml(kustomize('./config/dev')) +k8s_resource(new_name='Cattage Resources', objects=[ + 'cattage:namespace', + 'tenants.cattage.cybozu.io:customresourcedefinition', + 'cattage-mutating-webhook-configuration:mutatingwebhookconfiguration', + 'cattage-controller-manager:serviceaccount', + 'cattage-leader-election-role:role', + 'cattage-manager-role:clusterrole', + 'cattage-leader-election-rolebinding:rolebinding', + 'cattage-manager-rolebinding:clusterrolebinding', + 'cattage-controller-config:configmap', + 'cattage-manager-config:configmap', + 'cattage-serving-cert:certificate', + 'cattage-selfsigned-issuer:issuer', + 'cattage-validating-webhook-configuration:validatingwebhookconfiguration' +], labels=['cattage']) - local_resource('Watch&Compile', generate() + binary(), deps=deps, ignore=['*/*/zz_generated.deepcopy.go']) +k8s_resource(workload='cattage-controller-manager', labels=['cattage']) +local_resource( + 'Watch & Compile', generate() + binary(), deps=['controllers', 'pkg', 'hooks', 'cmd', 'version.go', 'api'], + ignore=['*/*/zz_generated.deepcopy.go'], + labels=['cattage']) - local_resource('Sample YAML', 'kubectl apply -f ./config/samples', deps=["./config/samples"], resource_deps=[DIRNAME + "-controller-manager"]) +docker_build_with_restart( + 'cattage:dev', '.', + dockerfile_contents=DOCKERFILE, + entrypoint=['/cattage-controller', '--zap-devel=true'], + only=['./bin/cattage-controller'], + live_update=[ + sync('./bin/cattage-controller', '/cattage-controller'), + ] +) - docker_build_with_restart('cattage:dev', '.', - dockerfile_contents=DOCKERFILE, - entrypoint=['/cattage-controller', '--zap-devel=true'], - only=['./bin/cattage-controller'], - live_update=[ - sync('./bin/cattage-controller', '/cattage-controller'), - ] - ) - -# deploy_cert_manager(version="v1.6.1") -kubebuilder() +# Sample +local_resource( + 'Sample: Template', 'kubectl apply -f ./config/samples/template.yaml', + deps=["./config/samples/template.yaml"], labels=['sample']) +local_resource( + 'Sample: Tenant', 'kubectl apply -f ./config/samples/tenant.yaml', deps=["./config/samples/tenant.yaml"], + resource_deps=["cattage-controller-manager", "Sample: Template"], labels=['sample']) +local_resource( + 'Sample: SubNamespace', 'kubectl apply -f ./config/samples/subnamespace.yaml', + deps=["./config/samples/subnamespace.yaml"], resource_deps=["Sample: Tenant"], labels=['sample']) +local_resource( + 'Wait for SubNamespace', + 'kubectl wait namespace/sub-1 --for=jsonpath="{.status.phase}"=Active --timeout=10s', + resource_deps=["Sample: SubNamespace"], labels=['sample']) +local_resource( + 'Sample: Application', 'kubectl apply -f ./config/samples/application.yaml', + deps=["./config/samples/application.yaml"], resource_deps=["Wait for SubNamespace"], labels=['sample']) diff --git a/config/samples/03_application.yaml b/config/samples/application.yaml similarity index 100% rename from config/samples/03_application.yaml rename to config/samples/application.yaml diff --git a/config/samples/02_subnamespace.yaml b/config/samples/subnamespace.yaml similarity index 100% rename from config/samples/02_subnamespace.yaml rename to config/samples/subnamespace.yaml diff --git a/config/samples/00_template.yaml b/config/samples/template.yaml similarity index 100% rename from config/samples/00_template.yaml rename to config/samples/template.yaml diff --git a/config/samples/01_tenant.yaml b/config/samples/tenant.yaml similarity index 100% rename from config/samples/01_tenant.yaml rename to config/samples/tenant.yaml diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index c66b291..8233195 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -15,19 +15,19 @@ import ( var _ = Describe("Cattage", func() { It("should prepare", func() { Eventually(func() error { - _, err := kubectl(nil, "apply", "-f", "../config/samples/00_template.yaml") + _, err := kubectl(nil, "apply", "-f", "../config/samples/template.yaml") return err }).Should(Succeed()) Eventually(func() error { - _, err := kubectl(nil, "apply", "-f", "../config/samples/01_tenant.yaml") + _, err := kubectl(nil, "apply", "-f", "../config/samples/tenant.yaml") return err }).Should(Succeed()) Eventually(func() error { - _, err := kubectl(nil, "apply", "-f", "../config/samples/02_subnamespace.yaml") + _, err := kubectl(nil, "apply", "-f", "../config/samples/subnamespace.yaml") return err }).Should(Succeed()) Eventually(func() error { - _, err := kubectl(nil, "apply", "-f", "../config/samples/03_application.yaml") + _, err := kubectl(nil, "apply", "-f", "../config/samples/application.yaml") return err }).Should(Succeed()) }) From 2340fd5bacf64347ee0311d5496c319127b8ba7e Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 11:44:53 +0900 Subject: [PATCH 76/89] Add a log that the corresponding tenant application cannot be found Signed-off-by: zoetrope --- controllers/application_controller.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 16c6d47..0ce058c 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -47,6 +47,7 @@ type ApplicationReconciler struct { //+kubebuilder:rbac:groups="",resources=events,verbs=create;update;patch func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + logger := log.FromContext(ctx) app := argocd.Application() if err := r.client.Get(ctx, req.NamespacedName, app); err != nil { return ctrl.Result{}, client.IgnoreNotFound(err) @@ -66,6 +67,9 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) tenantApp = argocd.Application() err := r.client.Get(ctx, client.ObjectKey{Namespace: ownerNs, Name: argocdApp.GetName()}, tenantApp) if err != nil { + if apierrors.IsNotFound(err) { + logger.Error(err, "Unable to find the corresponding tenant application.") + } return ctrl.Result{}, err } } else { From fe8be8c780a49710afe3752ca27b8eabb661b3bf Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 11:46:29 +0900 Subject: [PATCH 77/89] Add periods Signed-off-by: zoetrope --- Tiltfile | 7 ++++- api/v1beta1/tenant_types.go | 28 +++++++++---------- .../crd/bases/cattage.cybozu.io_tenants.yaml | 22 +++++++-------- docs/crd_tenant.md | 26 ++++++++--------- 4 files changed, 44 insertions(+), 39 deletions(-) diff --git a/Tiltfile b/Tiltfile index c16b9fc..6b10665 100644 --- a/Tiltfile +++ b/Tiltfile @@ -15,14 +15,19 @@ def generate(): return './bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./...";' +def apidoc(): + return 'make apidoc;' + + def binary(): return 'CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -o bin/cattage-controller cmd/cattage-controller/main.go' + # Don't watch generated files watch_settings(ignore=['config/crd/bases/', 'config/rbac/role.yaml', 'config/webhook/manifests.yaml']) # Generate manifests and go files -local(manifests() + generate()) +local(manifests() + generate() + apidoc()) # Deploy CRD local_resource( diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 641ad28..3e1c678 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -4,14 +4,14 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -// TenantSpec defines the desired state of Tenant +// TenantSpec defines the desired state of Tenant. type TenantSpec struct { - // RootNamespaces are the list of root namespaces that belong to this tenant + // RootNamespaces are the list of root namespaces that belong to this tenant. // +kubebuilder:validation:Required // +kubebuilder:validation:MinItems=1 RootNamespaces []RootNamespaceSpec `json:"rootNamespaces"` - // ArgoCD is the settings of Argo CD for this tenant + // ArgoCD is the settings of Argo CD for this tenant. // +optional ArgoCD ArgoCDSpec `json:"argocd,omitempty"` @@ -20,22 +20,22 @@ type TenantSpec struct { Delegates []Delegate `json:"delegates,omitempty"` } -// RootNamespaceSpec defines the desired state of Namespace +// RootNamespaceSpec defines the desired state of Namespace. type RootNamespaceSpec struct { - // Name is the name of namespace to be generated + // Name is the name of namespace to be generated. // +kubebuilder:validation:Required Name string `json:"name"` - // Labels are the labels to add to the namespace + // Labels are the labels to add to the namespace. // +optional Labels map[string]string `json:"labels,omitempty"` - // Annotations are the annotations to add to the namespace + // Annotations are the annotations to add to the namespace. // +optional Annotations map[string]string `json:"annotations,omitempty"` } -// ArgoCDSpec defines the desired state of the settings for Argo CD +// ArgoCDSpec defines the desired state of the settings for Argo CD. type ArgoCDSpec struct { // Repositories contains list of repository URLs which can be used by the tenant. // +optional @@ -44,16 +44,16 @@ type ArgoCDSpec struct { // Delegate defines a tenant that is delegated access to a tenant. type Delegate struct { - // Name is the name of a delegated tenant + // Name is the name of a delegated tenant. // +kubebuilder:validation:Required Name string `json:"name"` - // Roles is a list of roles that the tenant has + // Roles is a list of roles that the tenant has. // +kubebuilder:validation:MinItems=1 Roles []string `json:"roles"` } -// TenantHealth defines the observed state of Tenant +// TenantHealth defines the observed state of Tenant. // +kubebuilder:validation:Enum=Healthy;Unhealthy type TenantHealth string @@ -62,7 +62,7 @@ const ( TenantUnhealthy = TenantHealth("Unhealthy") ) -// TenantStatus defines the observed state of Tenant +// TenantStatus defines the observed state of Tenant. type TenantStatus struct { // Health is the health of Tenant. // +optional @@ -82,7 +82,7 @@ const ( //+kubebuilder:resource:scope=Cluster //+kubebuilder:printcolumn:name="STATUS",type="string",JSONPath=".status.health" -// Tenant is the Schema for the tenants API +// Tenant is the Schema for the tenants API. type Tenant struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` @@ -93,7 +93,7 @@ type Tenant struct { //+kubebuilder:object:root=true -// TenantList contains a list of Tenant +// TenantList contains a list of Tenant. type TenantList struct { metav1.TypeMeta `json:",inline"` metav1.ListMeta `json:"metadata,omitempty"` diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index a6d3263..583686d 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -22,7 +22,7 @@ spec: name: v1beta1 schema: openAPIV3Schema: - description: Tenant is the Schema for the tenants API + description: Tenant is the Schema for the tenants API. properties: apiVersion: description: 'APIVersion defines the versioned schema of this representation @@ -37,10 +37,10 @@ spec: metadata: type: object spec: - description: TenantSpec defines the desired state of Tenant + description: TenantSpec defines the desired state of Tenant. properties: argocd: - description: ArgoCD is the settings of Argo CD for this tenant + description: ArgoCD is the settings of Argo CD for this tenant. properties: repositories: description: Repositories contains list of repository URLs which @@ -57,10 +57,10 @@ spec: to a tenant. properties: name: - description: Name is the name of a delegated tenant + description: Name is the name of a delegated tenant. type: string roles: - description: Roles is a list of roles that the tenant has + description: Roles is a list of roles that the tenant has. items: type: string minItems: 1 @@ -72,22 +72,22 @@ spec: type: array rootNamespaces: description: RootNamespaces are the list of root namespaces that belong - to this tenant + to this tenant. items: - description: RootNamespaceSpec defines the desired state of Namespace + description: RootNamespaceSpec defines the desired state of Namespace. properties: annotations: additionalProperties: type: string - description: Annotations are the annotations to add to the namespace + description: Annotations are the annotations to add to the namespace. type: object labels: additionalProperties: type: string - description: Labels are the labels to add to the namespace + description: Labels are the labels to add to the namespace. type: object name: - description: Name is the name of namespace to be generated + description: Name is the name of namespace to be generated. type: string required: - name @@ -98,7 +98,7 @@ spec: - rootNamespaces type: object status: - description: TenantStatus defines the observed state of Tenant + description: TenantStatus defines the observed state of Tenant. properties: conditions: description: Conditions is an array of conditions. diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 57f9524..da4b757 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -14,7 +14,7 @@ #### ArgoCDSpec -ArgoCDSpec defines the desired state of the settings for Argo CD +ArgoCDSpec defines the desired state of the settings for Argo CD. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | @@ -28,26 +28,26 @@ Delegate defines a tenant that is delegated access to a tenant. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | -| name | Name is the name of a delegated tenant | string | true | -| roles | Roles is a list of roles that the tenant has | []string | true | +| name | Name is the name of a delegated tenant. | string | true | +| roles | Roles is a list of roles that the tenant has. | []string | true | [Back to Custom Resources](#custom-resources) #### RootNamespaceSpec -RootNamespaceSpec defines the desired state of Namespace +RootNamespaceSpec defines the desired state of Namespace. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | -| name | Name is the name of namespace to be generated | string | true | -| labels | Labels are the labels to add to the namespace | map[string]string | false | -| annotations | Annotations are the annotations to add to the namespace | map[string]string | false | +| name | Name is the name of namespace to be generated. | string | true | +| labels | Labels are the labels to add to the namespace. | map[string]string | false | +| annotations | Annotations are the annotations to add to the namespace. | map[string]string | false | [Back to Custom Resources](#custom-resources) #### Tenant -Tenant is the Schema for the tenants API +Tenant is the Schema for the tenants API. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | @@ -59,7 +59,7 @@ Tenant is the Schema for the tenants API #### TenantList -TenantList contains a list of Tenant +TenantList contains a list of Tenant. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | @@ -70,19 +70,19 @@ TenantList contains a list of Tenant #### TenantSpec -TenantSpec defines the desired state of Tenant +TenantSpec defines the desired state of Tenant. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | -| rootNamespaces | RootNamespaces are the list of root namespaces that belong to this tenant | [][RootNamespaceSpec](#rootnamespacespec) | true | -| argocd | ArgoCD is the settings of Argo CD for this tenant | [ArgoCDSpec](#argocdspec) | false | +| rootNamespaces | RootNamespaces are the list of root namespaces that belong to this tenant. | [][RootNamespaceSpec](#rootnamespacespec) | true | +| argocd | ArgoCD is the settings of Argo CD for this tenant. | [ArgoCDSpec](#argocdspec) | false | | delegates | Delegates is a list of other tenants that are delegated access to this tenant. | [][Delegate](#delegate) | false | [Back to Custom Resources](#custom-resources) #### TenantStatus -TenantStatus defines the observed state of Tenant +TenantStatus defines the observed state of Tenant. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | From 9b8d71c68dcd6af4493e29ded6bcb101141761db Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 11:53:34 +0900 Subject: [PATCH 78/89] Rename Delegate to DelegateSpec Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 6 +++--- api/v1beta1/zz_generated.deepcopy.go | 10 +++++----- config/crd/bases/cattage.cybozu.io_tenants.yaml | 2 +- controllers/tenant_controller.go | 2 +- controllers/tenant_controller_test.go | 2 +- docs/crd_tenant.md | 8 ++++---- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 3e1c678..3227081 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -17,7 +17,7 @@ type TenantSpec struct { // Delegates is a list of other tenants that are delegated access to this tenant. // +optional - Delegates []Delegate `json:"delegates,omitempty"` + Delegates []DelegateSpec `json:"delegates,omitempty"` } // RootNamespaceSpec defines the desired state of Namespace. @@ -42,8 +42,8 @@ type ArgoCDSpec struct { Repositories []string `json:"repositories,omitempty"` } -// Delegate defines a tenant that is delegated access to a tenant. -type Delegate struct { +// DelegateSpec defines a tenant that is delegated access to a tenant. +type DelegateSpec struct { // Name is the name of a delegated tenant. // +kubebuilder:validation:Required Name string `json:"name"` diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go index 520a1d9..862e453 100644 --- a/api/v1beta1/zz_generated.deepcopy.go +++ b/api/v1beta1/zz_generated.deepcopy.go @@ -31,7 +31,7 @@ func (in *ArgoCDSpec) DeepCopy() *ArgoCDSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Delegate) DeepCopyInto(out *Delegate) { +func (in *DelegateSpec) DeepCopyInto(out *DelegateSpec) { *out = *in if in.Roles != nil { in, out := &in.Roles, &out.Roles @@ -40,12 +40,12 @@ func (in *Delegate) DeepCopyInto(out *Delegate) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Delegate. -func (in *Delegate) DeepCopy() *Delegate { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DelegateSpec. +func (in *DelegateSpec) DeepCopy() *DelegateSpec { if in == nil { return nil } - out := new(Delegate) + out := new(DelegateSpec) in.DeepCopyInto(out) return out } @@ -151,7 +151,7 @@ func (in *TenantSpec) DeepCopyInto(out *TenantSpec) { in.ArgoCD.DeepCopyInto(&out.ArgoCD) if in.Delegates != nil { in, out := &in.Delegates, &out.Delegates - *out = make([]Delegate, len(*in)) + *out = make([]DelegateSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index 583686d..5e6c9e5 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -53,7 +53,7 @@ spec: description: Delegates is a list of other tenants that are delegated access to this tenant. items: - description: Delegate defines a tenant that is delegated access + description: DelegateSpec defines a tenant that is delegated access to a tenant. properties: name: diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index 91421c1..a31c4bd 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -291,7 +291,7 @@ func (r *TenantReconciler) patchRoleBinding(ctx context.Context, rb *acrbacv1.Ro }) } -func rolesMap(delegates []cattagev1beta1.Delegate) map[string][]string { +func rolesMap(delegates []cattagev1beta1.DelegateSpec) map[string][]string { result := make(map[string][]string) for _, d := range delegates { diff --git a/controllers/tenant_controller_test.go b/controllers/tenant_controller_test.go index befe2e0..a8bb4f7 100644 --- a/controllers/tenant_controller_test.go +++ b/controllers/tenant_controller_test.go @@ -103,7 +103,7 @@ var _ = Describe("Tenant controller", func() { "https://github.com/cybozu-go/*", }, }, - Delegates: []cattagev1beta1.Delegate{ + Delegates: []cattagev1beta1.DelegateSpec{ { Name: "c-team", Roles: []string{ diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index da4b757..13db963 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -6,7 +6,7 @@ ### Sub Resources * [ArgoCDSpec](#argocdspec) -* [Delegate](#delegate) +* [DelegateSpec](#delegatespec) * [RootNamespaceSpec](#rootnamespacespec) * [TenantList](#tenantlist) * [TenantSpec](#tenantspec) @@ -22,9 +22,9 @@ ArgoCDSpec defines the desired state of the settings for Argo CD. [Back to Custom Resources](#custom-resources) -#### Delegate +#### DelegateSpec -Delegate defines a tenant that is delegated access to a tenant. +DelegateSpec defines a tenant that is delegated access to a tenant. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | @@ -76,7 +76,7 @@ TenantSpec defines the desired state of Tenant. | ----- | ----------- | ------ | -------- | | rootNamespaces | RootNamespaces are the list of root namespaces that belong to this tenant. | [][RootNamespaceSpec](#rootnamespacespec) | true | | argocd | ArgoCD is the settings of Argo CD for this tenant. | [ArgoCDSpec](#argocdspec) | false | -| delegates | Delegates is a list of other tenants that are delegated access to this tenant. | [][Delegate](#delegate) | false | +| delegates | Delegates is a list of other tenants that are delegated access to this tenant. | [][DelegateSpec](#delegatespec) | false | [Back to Custom Resources](#custom-resources) From 731c20f3fa3c44d5116d103e4d18560c15c49d38 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 14:55:36 +0900 Subject: [PATCH 79/89] Deny specifying other tenant's root namespace Signed-off-by: zoetrope --- hooks/tenant.go | 21 ++++++++++++++++++--- hooks/tenant_test.go | 40 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/hooks/tenant.go b/hooks/tenant.go index 94d4699..441cd81 100644 --- a/hooks/tenant.go +++ b/hooks/tenant.go @@ -64,8 +64,23 @@ func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) adm if err := v.dec.Decode(req, tenant); err != nil { return admission.Errored(http.StatusBadRequest, err) } + tenantList := &cattagev1beta1.TenantList{} + if err := v.client.List(ctx, tenantList); err != nil { + return admission.Errored(http.StatusBadRequest, err) + } for _, ns := range tenant.Spec.RootNamespaces { + for _, t := range tenantList.Items { + if tenant.Name == t.Name { + continue + } + for _, n := range t.Spec.RootNamespaces { + if ns.Name == n.Name { + return admission.Denied("other tenant's root namespace is not allowed") + } + } + } + namespace := &corev1.Namespace{} err := v.client.Get(ctx, client.ObjectKey{Name: ns.Name}, namespace) if apierrors.IsNotFound(err) { @@ -76,15 +91,15 @@ func (v *tenantValidator) Handle(ctx context.Context, req admission.Request) adm } owner := namespace.Labels[constants.OwnerTenant] if owner != "" && owner != tenant.Name { - return admission.Denied("deny to specify other owner's namespace") + return admission.Denied("other owner's namespace is not allowed") } nsType := namespace.Labels[accurate.LabelType] if nsType != "" && nsType != accurate.NSTypeRoot { - return admission.Denied("deny to specify a namespace other than root") + return admission.Denied("namespace other than root is not allowed") } parent := namespace.Labels[accurate.LabelParent] if parent != "" { - return admission.Denied("deny to specify a sub namespace") + return admission.Denied("sub namespace is not allowed") } } diff --git a/hooks/tenant_test.go b/hooks/tenant_test.go index 6808b10..8d310e3 100644 --- a/hooks/tenant_test.go +++ b/hooks/tenant_test.go @@ -51,7 +51,7 @@ var _ = Describe("Tenant webhook", func() { } err := k8sClient.Create(ctx, tenant) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("deny to specify other owner's namespace")) + Expect(err.Error()).Should(ContainSubstring("other owner's namespace is not allowed")) }) It("should deny creating a tenant with template namespace", func() { @@ -69,7 +69,7 @@ var _ = Describe("Tenant webhook", func() { } err := k8sClient.Create(ctx, tenant) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("deny to specify a namespace other than root")) + Expect(err.Error()).Should(ContainSubstring("namespace other than root is not allowed")) }) It("should deny creating a tenant with other group's namespace", func() { @@ -87,6 +87,40 @@ var _ = Describe("Tenant webhook", func() { } err := k8sClient.Create(ctx, tenant) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("deny to specify a sub namespace")) + Expect(err.Error()).Should(ContainSubstring("sub namespace is not allowed")) + }) + + It("should deny creating a tenant with other tenant's root namespace", func() { + tenantF := &cattagev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "f-team", + }, + Spec: cattagev1beta1.TenantSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ + { + Name: "app-f-team", + }, + }, + }, + } + err := k8sClient.Create(ctx, tenantF) + Expect(err).NotTo(HaveOccurred()) + + tenantG := &cattagev1beta1.Tenant{ + ObjectMeta: metav1.ObjectMeta{ + Name: "g-team", + }, + Spec: cattagev1beta1.TenantSpec{ + RootNamespaces: []cattagev1beta1.RootNamespaceSpec{ + { + Name: "app-f-team", + }, + }, + }, + } + err = k8sClient.Create(ctx, tenantG) + Expect(err).To(HaveOccurred()) + + Expect(err.Error()).Should(ContainSubstring("other tenant's root namespace is not allowed")) }) }) From 82db40b12e7b662a524f78570368edbee57dbba1 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 15:46:50 +0900 Subject: [PATCH 80/89] Fix typo Signed-off-by: zoetrope --- docs/usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/usage.md b/docs/usage.md index ee75fe5..a5d4a72 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -15,7 +15,7 @@ spec: ``` The name of the tenant resource must match the name of the group in Kubernetes and Argo CD. -The namespaces specified in `spec.namespaces` will be created automatically. +The namespaces specified in `spec.rootNamespaces` will be created automatically. ```console $ kubectl get ns your-root From 4ab9f12ff720f4a676b14a5e29620dbe7514e421 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 15:49:39 +0900 Subject: [PATCH 81/89] Return a warning message if application is created on a root namespace. Signed-off-by: zoetrope --- hooks/application.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/hooks/application.go b/hooks/application.go index 18a5538..87f3af4 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -7,6 +7,8 @@ import ( "fmt" "net/http" + "github.com/cybozu-go/cattage/pkg/accurate" + "github.com/cybozu-go/cattage/pkg/argocd" "github.com/cybozu-go/cattage/pkg/config" "github.com/cybozu-go/cattage/pkg/constants" @@ -119,6 +121,13 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request return admission.Denied("cannot specify a project for other tenants") } + nsType := ns.Labels[accurate.LabelType] + if nsType == accurate.NSTypeRoot { + return admission.Allowed("ok").WithWarnings( + "Application resource has been created on a root namespace.", + "It is recommended to create Application resource on a sub-namespace.") + } + return admission.Allowed("ok") } From ecda60911e460fe28db8e4c2d841134579a2f156 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 16:51:14 +0900 Subject: [PATCH 82/89] Document relationship between commonLabels/Annotations and rootNamespaces.labels/annotations Signed-off-by: zoetrope --- api/v1beta1/tenant_types.go | 2 ++ config/crd/bases/cattage.cybozu.io_tenants.yaml | 2 ++ docs/config.md | 14 +++++++------- pkg/config/types.go | 2 ++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/api/v1beta1/tenant_types.go b/api/v1beta1/tenant_types.go index 3227081..1f9bee3 100644 --- a/api/v1beta1/tenant_types.go +++ b/api/v1beta1/tenant_types.go @@ -27,10 +27,12 @@ type RootNamespaceSpec struct { Name string `json:"name"` // Labels are the labels to add to the namespace. + // This supersedes `namespace.commonLabels` in the configuration. // +optional Labels map[string]string `json:"labels,omitempty"` // Annotations are the annotations to add to the namespace. + // This supersedes `namespace.commonAnnotations` in the configuration. // +optional Annotations map[string]string `json:"annotations,omitempty"` } diff --git a/config/crd/bases/cattage.cybozu.io_tenants.yaml b/config/crd/bases/cattage.cybozu.io_tenants.yaml index 5e6c9e5..e921a9b 100644 --- a/config/crd/bases/cattage.cybozu.io_tenants.yaml +++ b/config/crd/bases/cattage.cybozu.io_tenants.yaml @@ -80,11 +80,13 @@ spec: additionalProperties: type: string description: Annotations are the annotations to add to the namespace. + This supersedes `namespace.commonAnnotations` in the configuration. type: object labels: additionalProperties: type: string description: Labels are the labels to add to the namespace. + This supersedes `namespace.commonLabels` in the configuration. type: object name: description: Name is the name of namespace to be generated. diff --git a/docs/config.md b/docs/config.md index 7288060..811c36e 100644 --- a/docs/config.md +++ b/docs/config.md @@ -7,13 +7,13 @@ The location can be changed with `--config-file` flag. The configuration file should be a JSON or YAML file having the following keys: -| Key | Type | Description | -|---------------------------------|---------------------|--------------------------------------------------------------------------------------------| -| `namespace.commonLabels` | `map[string]string` | Labels to be added to all namespaces belonging to all tenants. | -| `namespace.commonAnnotations` | `map[string]string` | Annotations to be added to all namespaces belonging to all tenants. | -| `namespace.roleBindingTemplate` | `string` | Template for RoleBinding resource that is created on all namespaces belonging to a tenant. | -| `argocd.namepsace` | `string` | The name of namespace where Argo CD is running. | -| `argocd.appProjectTemplate` | `string` | Template for AppProject resources that is created for each tenant. | +| Key | Type | Description | +|---------------------------------|---------------------|--------------------------------------------------------------------------------------------------------------------------------------------------| +| `namespace.commonLabels` | `map[string]string` | Labels to be added to all namespaces belonging to all tenants. This may be overridden by `rootNamespaces.labels` of a tenant resource. | +| `namespace.commonAnnotations` | `map[string]string` | Annotations to be added to all namespaces belonging to all tenants. This may be overridden by `rootNamespaces.annotations` of a tenant resource. | +| `namespace.roleBindingTemplate` | `string` | Template for RoleBinding resource that is created on all namespaces belonging to a tenant. | +| `argocd.namepsace` | `string` | The name of namespace where Argo CD is running. | +| `argocd.appProjectTemplate` | `string` | Template for AppProject resources that is created for each tenant. | The repository includes an example as follows: diff --git a/pkg/config/types.go b/pkg/config/types.go index 1e9c2e5..6a93d83 100644 --- a/pkg/config/types.go +++ b/pkg/config/types.go @@ -19,9 +19,11 @@ type Config struct { // NamespaceConfig represents the configuration about Namespaces type NamespaceConfig struct { // CommonLabels are labels to be added to all namespaces belonging to a tenant + // This may be overridden by `rootNamespaces.labels` of a tenant resource. CommonLabels map[string]string `json:"commonLabels,omitempty"` // CommonAnnotations are annotations to be added to all namespaces belonging to a tenant + // This may be overridden by `rootNamespaces.annotations` of a tenant resource. CommonAnnotations map[string]string `json:"commonAnnotations,omitempty"` // RoleBindingTemplate is a template for RoleBinding resource that is created on all namespaces belonging to a tenant From 226d162832702405d2980bacb2c011231a96c200 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Tue, 8 Feb 2022 21:42:05 +0900 Subject: [PATCH 83/89] Prevent project overwriting Signed-off-by: zoetrope --- controllers/application_controller.go | 99 ++++++++++------------ controllers/application_controller_test.go | 68 ++++----------- docs/crd_tenant.md | 4 +- docs/usage.md | 35 +++++++- e2e/e2e_test.go | 51 ----------- e2e/run_test.go | 4 +- hooks/application.go | 55 +++++++----- hooks/application_test.go | 6 +- 8 files changed, 134 insertions(+), 188 deletions(-) diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 0ce058c..7f65df2 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "strings" + "time" "github.com/cybozu-go/cattage/pkg/argocd" extract "github.com/cybozu-go/cattage/pkg/client" @@ -92,12 +93,7 @@ func (r *ApplicationReconciler) Reconcile(ctx context.Context, req ctrl.Request) } } - err := r.reconcileApplication(ctx, argocdApp, tenantApp) - if err != nil { - return ctrl.Result{}, err - } - - return ctrl.Result{}, nil + return r.reconcileApplication(ctx, argocdApp, tenantApp) } func (r *ApplicationReconciler) finalize(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) (ctrl.Result, error) { @@ -128,85 +124,84 @@ func (r *ApplicationReconciler) finalize(ctx context.Context, argocdApp *unstruc return ctrl.Result{Requeue: true}, nil } -func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { +func (r *ApplicationReconciler) reconcileApplication(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) (ctrl.Result, error) { logger := log.FromContext(ctx) - removed, err := r.fixProject(ctx, argocdApp, tenantApp) + canSync, err := r.canSyncApplicationSpec(ctx, argocdApp, tenantApp) if err != nil { - logger.Error(err, "failed to validate application project") - return err + return ctrl.Result{}, err } - if removed { - return nil + if !canSync { + return ctrl.Result{ + RequeueAfter: 30 * time.Minute, + }, nil } // Sync application spec from tenant to argocd. err = r.syncApplicationSpec(ctx, argocdApp, tenantApp) if err != nil { logger.Error(err, "failed to sync application spec") - return err + return ctrl.Result{}, err } // Sync application status from argocd to tenant. err = r.syncApplicationStatus(ctx, argocdApp, tenantApp) if err != nil { logger.Error(err, "failed to sync application status") - return err + return ctrl.Result{}, err } - return nil + return ctrl.Result{}, nil } -func (r *ApplicationReconciler) fixProject(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) (removed bool, err error) { +func (r *ApplicationReconciler) canSyncApplicationSpec(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) (bool, error) { logger := log.FromContext(ctx) - ns := &corev1.Namespace{} - err = r.client.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) + err := r.client.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) if err != nil { - return + return false, err + } + tenantName, ok := ns.Labels[constants.OwnerTenant] + if !ok { + logger.Info("the namespace does not belong to a tenant", "targetNamespace", ns.Name) + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "CannotSync", "the namespace '%s' does not belong to a tenant", ns.Name) + return false, nil } - tenantName := ns.Labels[constants.OwnerTenant] - if tenantName == "" { - if argocdApp != nil && argocdApp.GetDeletionTimestamp() == nil { - // Applications on namespaces that do not belong to a tenant will be removed. - logger.Info("Remove unmanaged application") - err = r.client.Delete(ctx, argocdApp) + + if argocdApp != nil { + ownerNs, ok := argocdApp.GetLabels()[constants.OwnerAppNamespace] + if !ok { + project, found, err := unstructured.NestedString(argocdApp.UnstructuredContent(), "spec", "project") if err != nil { - r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "RemoveApplicationFailed", "Failed to remove unmanaged application", err) - return + return false, err } - r.recorder.Eventf(tenantApp, corev1.EventTypeNormal, "ApplicationRemoved", "Remove unmanaged application succeeded") + if !found { + return false, errors.New("spec.project not found in the application: " + argocdApp.GetNamespace() + "/" + argocdApp.GetName()) + } + if project != tenantName { + logger.Info("project of the application does not match the tenant name", "project", project, "tenantName", tenantName) + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "CannotSync", "project '%s' of the application '%s/%s' does not match the tenant name '%s'", project, argocdApp.GetNamespace(), argocdApp.GetName(), tenantName) + return false, nil + } + } else if tenantApp.GetNamespace() != ownerNs { + logger.Info("the application is already managed by other namespace", "tenantNamespace", tenantApp.GetNamespace(), "ownerNamespace", ownerNs) + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "CannotSync", "the application '%s/%s' is already managed by other namespace '%s'", tenantApp.GetNamespace(), tenantApp.GetName(), ownerNs) + return false, nil } - removed = true - return } + project, found, err := unstructured.NestedString(tenantApp.UnstructuredContent(), "spec", "project") if err != nil { - return + return false, err } if !found { - err = errors.New("spec.project not found") - return + return false, errors.New("spec.project not found in the application: " + tenantApp.GetNamespace() + "/" + tenantApp.GetName()) } if project != tenantName { - logger.Info("Overwrite project", "before", project, "after", tenantName) - newApp := argocd.Application() - newApp.SetNamespace(tenantApp.GetNamespace()) - newApp.SetName(tenantApp.GetName()) - err = unstructured.SetNestedField(newApp.UnstructuredContent(), tenantName, "spec", "project") - if err != nil { - return - } - err = r.client.Patch(ctx, newApp, client.Apply, &client.PatchOptions{ - Force: pointer.BoolPtr(true), - FieldManager: constants.ProjectFieldManager, - }) - if err != nil { - r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "FixProjectFailed", "Failed to fix application project", err) - return - } - r.recorder.Eventf(tenantApp, corev1.EventTypeNormal, "ProjectFixed", "Fix application project succeeded") - return + logger.Info("project of the application does not match the tenant name", "project", project, "tenantName", tenantName) + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "CannotSync", "project '%s' of the application '%s/%s' does not match the tenant name '%s'", project, tenantApp.GetNamespace(), tenantApp.GetName(), tenantName) + return false, nil } - return + + return true, nil } func (r *ApplicationReconciler) syncApplicationSpec(ctx context.Context, argocdApp *unstructured.Unstructured, tenantApp *unstructured.Unstructured) error { diff --git a/controllers/application_controller_test.go b/controllers/application_controller_test.go index 73d1b23..25ffda6 100644 --- a/controllers/application_controller_test.go +++ b/controllers/application_controller_test.go @@ -162,7 +162,7 @@ var _ = Describe("Application controller", func() { )) }) - It("should remove an application on unmanaged namespace", func() { + It("should fail to sync an application on unmanaged namespace", func() { tenantApp, err := fillApplication("unmanaged-app", "sub-2", "a-team") Expect(err).ToNot(HaveOccurred()) @@ -183,17 +183,6 @@ var _ = Describe("Application controller", func() { err = k8sClient.Update(ctx, ns) Expect(err).NotTo(HaveOccurred()) - Eventually(func() error { - err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp) - if apierrors.IsNotFound(err) { - return nil - } - if err != nil { - return err - } - return errors.New("application still exists") - }).Should(Succeed()) - Eventually(func() error { events := &corev1.EventList{} err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) @@ -204,7 +193,7 @@ var _ = Describe("Application controller", func() { for _, ev := range events.Items { if ev.Reason == "ApplicationSynced" { synced = true - } else if ev.Reason == "ApplicationRemoved" { + } else if ev.Reason == "CannotSync" { removed = true } } @@ -212,13 +201,13 @@ var _ = Describe("Application controller", func() { return errors.New("ApplicationSynced event not found") } if !removed { - return errors.New("ApplicationRemoved event not found") + return errors.New("CannotSync event not found") } return nil }).Should(Succeed()) }) - It("should fix project", func() { + It("should fail to sync application on other tenant's namespace", func() { tenantApp, err := fillApplication("changed-app", "sub-3", "a-team") Expect(err).ToNot(HaveOccurred()) @@ -242,50 +231,27 @@ var _ = Describe("Application controller", func() { Expect(err).NotTo(HaveOccurred()) Eventually(func() error { - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: tenantApp.GetNamespace(), Name: tenantApp.GetName()}, tenantApp); err != nil { - return err - } - project, found, err := unstructured.NestedString(tenantApp.UnstructuredContent(), "spec", "project") + events := &corev1.EventList{} + err = k8sClient.List(ctx, events, client.InNamespace("sub-2")) if err != nil { return err } - if !found { - return errors.New("spec.project not found") - } - if project != "b-team" { - return errors.New("spec.project has not been fixed: " + project) - } - return nil - }).Should(Succeed()) - - Eventually(func() error { - if err := k8sClient.Get(ctx, client.ObjectKey{Namespace: config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp); err != nil { - return err - } - project, found, err := unstructured.NestedString(argocdApp.UnstructuredContent(), "spec", "project") - if err != nil { - return err + var synced, removed bool + for _, ev := range events.Items { + if ev.Reason == "ApplicationSynced" { + synced = true + } else if ev.Reason == "CannotSync" { + removed = true + } } - if !found { - return errors.New("spec.project not found") + if !synced { + return errors.New("ApplicationSynced event not found") } - if project != "b-team" { - return errors.New("spec.project has not been fixed: " + project) + if !removed { + return errors.New("CannotSync event not found") } return nil }).Should(Succeed()) - - events := &corev1.EventList{} - err = k8sClient.List(ctx, events, client.InNamespace("sub-3")) - Expect(err).NotTo(HaveOccurred()) - Expect(events.Items).Should(ConsistOf( - MatchFields(IgnoreExtras, Fields{ - "Reason": Equal("ApplicationSynced"), - }), - MatchFields(IgnoreExtras, Fields{ - "Reason": Equal("ProjectFixed"), - }), - )) }) It("should remove application", func() { diff --git a/docs/crd_tenant.md b/docs/crd_tenant.md index 13db963..52ab2ea 100644 --- a/docs/crd_tenant.md +++ b/docs/crd_tenant.md @@ -40,8 +40,8 @@ RootNamespaceSpec defines the desired state of Namespace. | Field | Description | Scheme | Required | | ----- | ----------- | ------ | -------- | | name | Name is the name of namespace to be generated. | string | true | -| labels | Labels are the labels to add to the namespace. | map[string]string | false | -| annotations | Annotations are the annotations to add to the namespace. | map[string]string | false | +| labels | Labels are the labels to add to the namespace. This supersedes `namespace.commonLabels` in the configuration. | map[string]string | false | +| annotations | Annotations are the annotations to add to the namespace. This supersedes `namespace.commonAnnotations` in the configuration. | map[string]string | false | [Back to Custom Resources](#custom-resources) diff --git a/docs/usage.md b/docs/usage.md index a5d4a72..553bc1c 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -101,7 +101,24 @@ LAST SEEN TYPE REASON OBJECT MESSAGE 34s Normal StatusSynced application/testhttpd Sync application status succeeded ``` -## Changing ownership +## How to manage resources that already exist + +Cattage can manage resources that have existed before with Tenant and Application. + +You can make an existing namespace belong to Tenant. +However, the namespace must be root or not managed by accurate. + +A RoleBinding resource named `-admin` will be created on a namespace belonging to a tenant. +If a resource with the same name already exists, it will be overwritten. + +A AppProject resource with the same name as a tenant will be created in argocd namespace. +If a resource with the same name already exists, it will be overwritten. + +When you create an Application on a sub-namespace, an Application resource with the same name will be created in argocd namespace. +If that Application in argocd namespace exists, the Application will be overwritten only if the `spec.project` filed matches. +If not, then the creation of the Application resource will be rejected. + +## How to change ownership The ownership of sub-namespace can be transferred to other tenant. @@ -123,13 +140,23 @@ Use `kubectl accurate sub move` command to change the parent of your-sub namespa $ kubectl accurate sub move your-sub new-root ``` -`spec.project` field will be updated. +As a result, `application/testhttpd` in your-sub will be out of sync with `application/testhttpd` in argocd. +You can see it as an event resource. ```console -$ kubectl get app -n your-sub testhttpd -o jsonpath="{.spec.project}" -new-team +$ kubectl get events -n your-sub +LAST SEEN TYPE REASON OBJECT MESSAGE +10s Warning CannotSync application/testhttpd project 'your-team' of the application 'your-sub/testhttpd' does not match the tenant name 'new-team' +``` + +Please change the project of `application/testhttpd` correctly. + +```console +$ kubectl patch app testhttpd -n your-sub --type='json' -p '[{ "op": "replace", "path": "/spec/project", "value": "new-team"}]' ``` +The application will sync again. + ## Remove resources When a tenant user delete an Application resource on the tenant's namespace, an Application resource on argocd namespace will be deleted as well. diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 8233195..db499c8 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" - "github.com/cybozu-go/cattage/pkg/accurate" "github.com/cybozu-go/cattage/pkg/argocd" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" @@ -67,54 +66,4 @@ var _ = Describe("Cattage", func() { return nil }).Should(Succeed()) }) - - It("should change ownership", func() { - kubectlSafe(nil, "label", "ns", "sub-1", accurate.LabelParent+"=app-b", "--overwrite") - - Eventually(func() error { - out, err := kubectl(nil, "get", "app", "-n", "sub-1", "sample", "-o", "json") - if err != nil { - return err - } - app := argocd.Application() - if err := json.Unmarshal(out, app); err != nil { - return err - } - - project, found, err := unstructured.NestedString(app.UnstructuredContent(), "spec", "project") - if err != nil { - return err - } - if !found { - return errors.New("project not found") - } - if project != "b-team" { - return errors.New("project is not fixed") - } - - healthStatus, found, err := unstructured.NestedString(app.UnstructuredContent(), "status", "health", "status") - if err != nil { - return err - } - if !found { - return errors.New("status not found") - } - if healthStatus != "Healthy" { - return errors.New("status is not healthy") - } - - syncStatus, found, err := unstructured.NestedString(app.UnstructuredContent(), "status", "sync", "status") - if err != nil { - return err - } - if !found { - return errors.New("status not found") - } - if syncStatus != "Synced" { - return errors.New("status is not synced") - } - - return nil - }).Should(Succeed()) - }) }) diff --git a/e2e/run_test.go b/e2e/run_test.go index 1dd92c1..85ef12f 100644 --- a/e2e/run_test.go +++ b/e2e/run_test.go @@ -4,8 +4,6 @@ import ( "bytes" "fmt" "os/exec" - - . "github.com/onsi/gomega" ) func kubectl(input []byte, args ...string) ([]byte, error) { @@ -24,8 +22,10 @@ func kubectl(input []byte, args ...string) ([]byte, error) { return nil, fmt.Errorf("kubectl failed with %s: stderr=%s", err, stderr) } +/* func kubectlSafe(input []byte, args ...string) []byte { out, err := kubectl(input, args...) ExpectWithOffset(1, err).NotTo(HaveOccurred()) return out } +*/ diff --git a/hooks/application.go b/hooks/application.go index 87f3af4..c5c611c 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -7,6 +7,10 @@ import ( "fmt" "net/http" + "k8s.io/apimachinery/pkg/util/validation/field" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "github.com/cybozu-go/cattage/pkg/accurate" "github.com/cybozu-go/cattage/pkg/argocd" @@ -67,49 +71,54 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request return admission.Allowed("") } - app := argocd.Application() - if err := v.dec.Decode(req, app); err != nil { + tenantApp := argocd.Application() + if err := v.dec.Decode(req, tenantApp); err != nil { return admission.Errored(http.StatusBadRequest, err) } - if app.GetNamespace() == v.config.ArgoCD.Namespace { + if tenantApp.GetNamespace() == v.config.ArgoCD.Namespace { return admission.Allowed("") } - if app.GetDeletionTimestamp() != nil { + if tenantApp.GetDeletionTimestamp() != nil { return admission.Allowed("") } ns := &corev1.Namespace{} - err := v.Client.Get(ctx, client.ObjectKey{Name: app.GetNamespace()}, ns) + err := v.Client.Get(ctx, client.ObjectKey{Name: tenantApp.GetNamespace()}, ns) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } tenantName, ok := ns.Labels[constants.OwnerTenant] if !ok { - return admission.Denied("an application cannot be created on unmanaged namespaces") + return admission.Denied("cannot create the application on a namespace that does not belong to a tenant") } - apps := argocd.ApplicationList() - err = v.Client.List(ctx, apps, client.InNamespace(v.config.ArgoCD.Namespace)) - if err != nil { + argocdApp := argocd.Application() + err = v.Client.Get(ctx, client.ObjectKey{Namespace: v.config.ArgoCD.Namespace, Name: tenantApp.GetName()}, argocdApp) + if err != nil && !apierrors.IsNotFound(err) { return admission.Errored(http.StatusInternalServerError, err) } - for _, a := range apps.Items { - if app.GetName() == a.GetName() { - ownerNs := a.GetLabels()[constants.OwnerAppNamespace] - if ownerNs == "" { - break + if !apierrors.IsNotFound(err) { + ownerNs, ok := argocdApp.GetLabels()[constants.OwnerAppNamespace] + if !ok { + project, found, err := unstructured.NestedString(argocdApp.UnstructuredContent(), "spec", "project") + if err != nil { + return admission.Errored(http.StatusBadRequest, fmt.Errorf("unable to get spec.project; %w", err)) + } + if !found { + return admission.Errored(http.StatusBadRequest, errors.New("spec.project not found")) } - if app.GetNamespace() == ownerNs { - break + if project != tenantName { + return admission.Denied(field.Forbidden(field.NewPath("spec", "project"), "project of the application does not match the tenant name").Error()) } - return admission.Denied("cannot create an application with the same name") + } else if tenantApp.GetNamespace() != ownerNs { + return admission.Denied(field.Forbidden(field.NewPath("metadata", "namespace"), "the application is already managed by other namespace").Error()) } } - project, found, err := unstructured.NestedString(app.UnstructuredContent(), "spec", "project") + project, found, err := unstructured.NestedString(tenantApp.UnstructuredContent(), "spec", "project") if err != nil { return admission.Errored(http.StatusBadRequest, fmt.Errorf("unable to get spec.project; %w", err)) } @@ -118,17 +127,17 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request } if tenantName != project { - return admission.Denied("cannot specify a project for other tenants") + return admission.Denied(field.Forbidden(field.NewPath("spec", "project"), "project of the application does not match the tenant name").Error()) } nsType := ns.Labels[accurate.LabelType] if nsType == accurate.NSTypeRoot { - return admission.Allowed("ok").WithWarnings( - "Application resource has been created on a root namespace.", - "It is recommended to create Application resource on a sub-namespace.") + return admission.Allowed("").WithWarnings( + "The application resource has been created on a root namespace.", + "It is recommended to create the application resource on a sub-namespace.") } - return admission.Allowed("ok") + return admission.Allowed("") } // SetupApplicationWebhook registers the webhooks for Application diff --git a/hooks/application_test.go b/hooks/application_test.go index 03fa6d1..179e4fb 100644 --- a/hooks/application_test.go +++ b/hooks/application_test.go @@ -59,7 +59,7 @@ var _ = Describe("Application webhook", func() { err = k8sClient.Create(ctx, app) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("an application cannot be created on unmanaged namespaces")) + Expect(err.Error()).Should(ContainSubstring(" cannot create the application on a namespace that does not belong to a tenant")) }) It("should deny creating an application managed by other application", func() { @@ -75,7 +75,7 @@ var _ = Describe("Application webhook", func() { Expect(err).NotTo(HaveOccurred()) err = k8sClient.Create(ctx, app) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("cannot create an application with the same name")) + Expect(err.Error()).Should(ContainSubstring("the application is already managed by other namespace")) }) It("should allow creating an application managed by nobody", func() { @@ -111,6 +111,6 @@ var _ = Describe("Application webhook", func() { err = k8sClient.Create(ctx, app) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("cannot specify a project for other tenants")) + Expect(err.Error()).Should(ContainSubstring("project of the application does not match the tenant name")) }) }) From e176a47a2aade1aa6df065494cc1e6c363578bfb Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 9 Feb 2022 18:18:19 +0900 Subject: [PATCH 84/89] Update dependencies Signed-off-by: zoetrope --- Makefile | 2 +- aqua.yaml | 6 +++--- e2e/Makefile | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index c34f431..d1cedf2 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ # Tool versions CTRL_RUNTIME_VERSION := $(shell awk '/sigs.k8s.io\/controller-runtime/ {print substr($$2, 2)}' go.mod) -ARGOCD_VERSION = 2.2.4 +ARGOCD_VERSION = 2.2.5 # Test tools BIN_DIR := $(shell pwd)/bin diff --git a/aqua.yaml b/aqua.yaml index beb840d..e79c7a5 100644 --- a/aqua.yaml +++ b/aqua.yaml @@ -3,19 +3,19 @@ # https://aquaproj.github.io/ registries: - type: standard - ref: v1.0.0 # renovate: depName=aquaproj/aqua-registry + ref: v1.1.0 # renovate: depName=aquaproj/aqua-registry - type: local name: local path: registry.yaml packages: - - name: argoproj/argo-cd@v2.2.4 + - name: argoproj/argo-cd@v2.2.5 - name: kubernetes/kubectl@v1.23.3 - name: kubernetes-sigs/kubebuilder@v3.3.0 - name: kubernetes-sigs/kustomize@kustomize/v4.4.1 - name: kubernetes-sigs/kind@v0.11.1 - name: rust-lang/mdBook@v0.4.15 - - name: tilt-dev/tilt@v0.23.9 + - name: tilt-dev/tilt@v0.24.1 - name: tilt-dev/ctlptl@v0.7.5 - name: GoogleContainerTools/container-structure-test@v1.11.0 - name: mikefarah/yq@v4.18.1 diff --git a/e2e/Makefile b/e2e/Makefile index 12b5693..1d45295 100644 --- a/e2e/Makefile +++ b/e2e/Makefile @@ -1,5 +1,5 @@ KUBERNETES_VERSION = 1.23.1 -ARGOCD_VERSION = 2.2.4 +ARGOCD_VERSION = 2.2.5 KIND_CONFIG = kind-config.yaml export KUBECONFIG From 7e2fe6870769d1049935d336afdf755bed650c06 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 9 Feb 2022 18:24:08 +0900 Subject: [PATCH 85/89] Fix Signed-off-by: zoetrope --- charts/cattage/crds/tenant.yaml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/charts/cattage/crds/tenant.yaml b/charts/cattage/crds/tenant.yaml index 78576c3..8d1138f 100644 --- a/charts/cattage/crds/tenant.yaml +++ b/charts/cattage/crds/tenant.yaml @@ -22,7 +22,7 @@ spec: name: v1beta1 schema: openAPIV3Schema: - description: Tenant is the Schema for the tenants API + description: Tenant is the Schema for the tenants 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' @@ -33,10 +33,10 @@ spec: metadata: type: object spec: - description: TenantSpec defines the desired state of Tenant + description: TenantSpec defines the desired state of Tenant. properties: argocd: - description: ArgoCD is the settings of Argo CD for this tenant + description: ArgoCD is the settings of Argo CD for this tenant. properties: repositories: description: Repositories contains list of repository URLs which can be used by the tenant. @@ -47,13 +47,13 @@ spec: delegates: description: Delegates is a list of other tenants that are delegated access to this tenant. items: - description: Delegate defines a tenant that is delegated access to a tenant. + description: DelegateSpec defines a tenant that is delegated access to a tenant. properties: name: - description: Name is the name of a delegated tenant + description: Name is the name of a delegated tenant. type: string roles: - description: Roles is a list of roles that the tenant has + description: Roles is a list of roles that the tenant has. items: type: string minItems: 1 @@ -64,22 +64,22 @@ spec: type: object type: array rootNamespaces: - description: RootNamespaces are the list of root namespaces that belong to this tenant + description: RootNamespaces are the list of root namespaces that belong to this tenant. items: - description: RootNamespaceSpec defines the desired state of Namespace + description: RootNamespaceSpec defines the desired state of Namespace. properties: annotations: additionalProperties: type: string - description: Annotations are the annotations to add to the namespace + description: Annotations are the annotations to add to the namespace. This supersedes `namespace.commonAnnotations` in the configuration. type: object labels: additionalProperties: type: string - description: Labels are the labels to add to the namespace + description: Labels are the labels to add to the namespace. This supersedes `namespace.commonLabels` in the configuration. type: object name: - description: Name is the name of namespace to be generated + description: Name is the name of namespace to be generated. type: string required: - name @@ -90,7 +90,7 @@ spec: - rootNamespaces type: object status: - description: TenantStatus defines the observed state of Tenant + description: TenantStatus defines the observed state of Tenant. properties: conditions: description: Conditions is an array of conditions. From 70a98af37a5b086cbbe2818039c73750d61787cc Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 9 Feb 2022 18:52:44 +0900 Subject: [PATCH 86/89] Fix name Signed-off-by: zoetrope --- controllers/tenant_controller.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/controllers/tenant_controller.go b/controllers/tenant_controller.go index a31c4bd..d24b5c0 100644 --- a/controllers/tenant_controller.go +++ b/controllers/tenant_controller.go @@ -154,7 +154,7 @@ func (r *TenantReconciler) disownNamespace(ctx context.Context, ns *corev1.Names return nil } -func (r *TenantReconciler) removeRBAC(ctx context.Context, tenant *cattagev1beta1.Tenant, ns *corev1.Namespace) error { +func (r *TenantReconciler) removeRoleBinding(ctx context.Context, tenant *cattagev1beta1.Tenant, ns *corev1.Namespace) error { rb := &rbacv1.RoleBinding{} err := r.client.Get(ctx, client.ObjectKey{Namespace: ns.Name, Name: tenant.Name + "-admin"}, rb) if apierrors.IsNotFound(err) { @@ -211,7 +211,7 @@ func (r *TenantReconciler) finalize(ctx context.Context, tenant *cattagev1beta1. if err != nil { return err } - err = r.removeRBAC(ctx, tenant, &ns) + err = r.removeRoleBinding(ctx, tenant, &ns) if err != nil { return err } @@ -373,7 +373,7 @@ func (r *TenantReconciler) reconcileNamespaces(ctx context.Context, tenant *catt if err != nil { return err } - err = r.removeRBAC(ctx, tenant, &ns) + err = r.removeRoleBinding(ctx, tenant, &ns) if err != nil { return err } From a4cedaa7a3e69946566e72c13e9057f757917d25 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 9 Feb 2022 18:53:44 +0900 Subject: [PATCH 87/89] Use klog v2 Signed-off-by: zoetrope --- cmd/cattage-controller/sub/root.go | 2 +- go.mod | 3 +-- go.sum | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/cmd/cattage-controller/sub/root.go b/cmd/cattage-controller/sub/root.go index e642056..28db29e 100644 --- a/cmd/cattage-controller/sub/root.go +++ b/cmd/cattage-controller/sub/root.go @@ -10,7 +10,7 @@ import ( "github.com/cybozu-go/cattage" "github.com/spf13/cobra" - "k8s.io/klog" + klog "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/log/zap" ) diff --git a/go.mod b/go.mod index 55bd2a8..1c46399 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( k8s.io/api v0.23.1 k8s.io/apimachinery v0.23.1 k8s.io/client-go v0.23.1 - k8s.io/klog v1.0.0 + k8s.io/klog/v2 v2.30.0 k8s.io/utils v0.0.0-20211208161948-7d6a63dca704 sigs.k8s.io/controller-runtime v0.11.0 sigs.k8s.io/controller-tools v0.8.0 @@ -79,7 +79,6 @@ require ( gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect k8s.io/apiextensions-apiserver v0.23.0 // indirect k8s.io/component-base v0.23.0 // indirect - k8s.io/klog/v2 v2.30.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect sigs.k8s.io/json v0.0.0-20211020170558-c049b76a60c6 // indirect ) diff --git a/go.sum b/go.sum index 6197335..a6c6dfc 100644 --- a/go.sum +++ b/go.sum @@ -1106,8 +1106,6 @@ k8s.io/code-generator v0.23.0/go.mod h1:vQvOhDXhuzqiVfM/YHp+dmg10WDZCchJVObc9Mvo k8s.io/component-base v0.23.0 h1:UAnyzjvVZ2ZR1lF35YwtNY6VMN94WtOnArcXBu34es8= k8s.io/component-base v0.23.0/go.mod h1:DHH5uiFvLC1edCpvcTDV++NKULdYYU6pR9Tt3HIKMKI= k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -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/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= From 6dc3662388b37e996036c024589ee986370af589 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Wed, 9 Feb 2022 18:56:58 +0900 Subject: [PATCH 88/89] Specify sha1 for goreleaser-action Signed-off-by: zoetrope --- .github/workflows/ci.yaml | 4 ++-- .github/workflows/release.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 5295fa8..d26309a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -26,7 +26,7 @@ jobs: uses: actions/setup-go@v2 with: go-version: ${{ env.go-version }} - - uses: goreleaser/goreleaser-action@v2 + - uses: goreleaser/goreleaser-action@79d4afbba1b4eff8b9a98e3d2e58c4dbaf094e2b # v2.8.1 with: version: latest args: check -f .goreleaser.yml @@ -99,7 +99,7 @@ jobs: with: go-version: ${{ env.go-version }} - name: GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@79d4afbba1b4eff8b9a98e3d2e58c4dbaf094e2b # v2.8.1 with: version: latest args: --snapshot --skip-publish --rm-dist diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d5cf856..0d95d47 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -26,7 +26,7 @@ jobs: with: go-version: ${{ env.go-version }} - name: GoReleaser - uses: goreleaser/goreleaser-action@v2 + uses: goreleaser/goreleaser-action@79d4afbba1b4eff8b9a98e3d2e58c4dbaf094e2b # v2.8.1 with: version: latest args: release --rm-dist From 63e8e08929eac23b16b573a970e2e35c7c3af2c8 Mon Sep 17 00:00:00 2001 From: zoetrope Date: Thu, 10 Feb 2022 14:55:27 +0900 Subject: [PATCH 89/89] Fix typo Signed-off-by: zoetrope --- controllers/application_controller.go | 4 ++-- docs/usage.md | 2 +- hooks/application.go | 2 +- hooks/application_test.go | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/controllers/application_controller.go b/controllers/application_controller.go index 7f65df2..cf4b616 100644 --- a/controllers/application_controller.go +++ b/controllers/application_controller.go @@ -182,8 +182,8 @@ func (r *ApplicationReconciler) canSyncApplicationSpec(ctx context.Context, argo return false, nil } } else if tenantApp.GetNamespace() != ownerNs { - logger.Info("the application is already managed by other namespace", "tenantNamespace", tenantApp.GetNamespace(), "ownerNamespace", ownerNs) - r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "CannotSync", "the application '%s/%s' is already managed by other namespace '%s'", tenantApp.GetNamespace(), tenantApp.GetName(), ownerNs) + logger.Info("the application is already managed by another namespace", "tenantNamespace", tenantApp.GetNamespace(), "ownerNamespace", ownerNs) + r.recorder.Eventf(tenantApp, corev1.EventTypeWarning, "CannotSync", "the application '%s/%s' is already managed by another namespace '%s'", tenantApp.GetNamespace(), tenantApp.GetName(), ownerNs) return false, nil } } diff --git a/docs/usage.md b/docs/usage.md index 553bc1c..0c71086 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -111,7 +111,7 @@ However, the namespace must be root or not managed by accurate. A RoleBinding resource named `-admin` will be created on a namespace belonging to a tenant. If a resource with the same name already exists, it will be overwritten. -A AppProject resource with the same name as a tenant will be created in argocd namespace. +An AppProject resource with the same name as a tenant will be created in argocd namespace. If a resource with the same name already exists, it will be overwritten. When you create an Application on a sub-namespace, an Application resource with the same name will be created in argocd namespace. diff --git a/hooks/application.go b/hooks/application.go index c5c611c..89e4e41 100644 --- a/hooks/application.go +++ b/hooks/application.go @@ -114,7 +114,7 @@ func (v *applicationValidator) Handle(ctx context.Context, req admission.Request return admission.Denied(field.Forbidden(field.NewPath("spec", "project"), "project of the application does not match the tenant name").Error()) } } else if tenantApp.GetNamespace() != ownerNs { - return admission.Denied(field.Forbidden(field.NewPath("metadata", "namespace"), "the application is already managed by other namespace").Error()) + return admission.Denied(field.Forbidden(field.NewPath("metadata", "namespace"), "the application is already managed by another namespace").Error()) } } diff --git a/hooks/application_test.go b/hooks/application_test.go index 179e4fb..9f894c3 100644 --- a/hooks/application_test.go +++ b/hooks/application_test.go @@ -62,7 +62,7 @@ var _ = Describe("Application webhook", func() { Expect(err.Error()).Should(ContainSubstring(" cannot create the application on a namespace that does not belong to a tenant")) }) - It("should deny creating an application managed by other application", func() { + It("should deny creating an application managed by another application", func() { app, err := fillApplication("other-app", "argocd", "a-team") Expect(err).NotTo(HaveOccurred()) app.SetLabels(map[string]string{ @@ -75,7 +75,7 @@ var _ = Describe("Application webhook", func() { Expect(err).NotTo(HaveOccurred()) err = k8sClient.Create(ctx, app) Expect(err).To(HaveOccurred()) - Expect(err.Error()).Should(ContainSubstring("the application is already managed by other namespace")) + Expect(err.Error()).Should(ContainSubstring("the application is already managed by another namespace")) }) It("should allow creating an application managed by nobody", func() {