├── config ├── webhook │ ├── manifests.yaml │ ├── kustomization.yaml │ ├── service.yaml │ └── kustomizeconfig.yaml ├── manager │ ├── kustomization.yaml │ └── manager.yaml ├── certmanager │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── certificate.yaml ├── rbac │ ├── role_binding.yaml │ ├── auth_proxy_role_binding.yaml │ ├── leader_election_role_binding.yaml │ ├── auth_proxy_role.yaml │ ├── kustomization.yaml │ ├── auth_proxy_service.yaml │ ├── role.yaml │ └── leader_election_role.yaml └── default │ ├── manager_prometheus_metrics_patch.yaml │ ├── manager_webhook_patch.yaml │ ├── webhookcainjection_patch.yaml │ ├── manager_auth_proxy_patch.yaml │ └── kustomization.yaml ├── PROJECT ├── .github ├── kind-config.yaml └── workflows │ ├── ci.yaml │ └── ci_and_deploy.yaml ├── go.mod ├── .gitignore ├── hack └── boilerplate.go.txt ├── Dockerfile ├── README.adoc ├── .travis.yml ├── Makefile ├── service └── secret_service.go ├── controllers ├── suite_test.go └── secret_controller.go ├── main.go └── go.sum /config/webhook/manifests.yaml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | version: "2" 2 | domain: kiwigrid.com 3 | repo: github.com/kiwigrid/secret-replicator 4 | -------------------------------------------------------------------------------- /config/certmanager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - certificate.yaml 3 | 4 | configurations: 5 | - kustomizeconfig.yaml 6 | -------------------------------------------------------------------------------- /config/webhook/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manifests.yaml 3 | - service.yaml 4 | 5 | configurations: 6 | - kustomizeconfig.yaml 7 | -------------------------------------------------------------------------------- /.github/kind-config.yaml: -------------------------------------------------------------------------------- 1 | kind: Cluster 2 | apiVersion: kind.sigs.k8s.io/v1alpha3 3 | nodes: 4 | # the control plane node config 5 | - role: control-plane 6 | # the 2 workers 7 | - role: worker 8 | - role: worker -------------------------------------------------------------------------------- /config/webhook/service.yaml: -------------------------------------------------------------------------------- 1 | 2 | apiVersion: v1 3 | kind: Service 4 | metadata: 5 | name: webhook-service 6 | namespace: system 7 | spec: 8 | ports: 9 | - port: 443 10 | targetPort: 9443 11 | selector: 12 | control-plane: controller-manager 13 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: RoleBinding 3 | metadata: 4 | name: leader-election-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: Role 8 | name: leader-election-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: ["authentication.k8s.io"] 7 | resources: 8 | - tokenreviews 9 | verbs: ["create"] 10 | - apiGroups: ["authorization.k8s.io"] 11 | resources: 12 | - subjectaccessreviews 13 | verbs: ["create"] 14 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - role.yaml 3 | - role_binding.yaml 4 | - leader_election_role.yaml 5 | - leader_election_role_binding.yaml 6 | # Comment the following 3 lines if you want to disable 7 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 8 | # which protects your /metrics endpoint. 9 | - auth_proxy_service.yaml 10 | - auth_proxy_role.yaml 11 | - auth_proxy_role_binding.yaml 12 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/kiwigrid/secret-replicator 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/go-logr/logr v0.1.0 7 | github.com/onsi/ginkgo v1.6.0 8 | github.com/onsi/gomega v1.4.2 9 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b 10 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d 11 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible 12 | sigs.k8s.io/controller-runtime v0.2.2 13 | ) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | 10 | # Test binary, build with `go test -c` 11 | *.test 12 | 13 | # Output of the go coverage tool, specifically when used with LiteIDE 14 | *.out 15 | 16 | # Kubernetes Generated files - skip generated files, except for vendored files 17 | 18 | !vendor/**/zz_generated.* 19 | 20 | # editor and IDE paraphernalia 21 | .idea 22 | *.swp 23 | *.swo 24 | *~ 25 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | annotations: 5 | prometheus.io/port: "8443" 6 | prometheus.io/scheme: https 7 | prometheus.io/scrape: "true" 8 | labels: 9 | control-plane: controller-manager 10 | name: controller-manager-metrics-service 11 | namespace: system 12 | spec: 13 | ports: 14 | - name: https 15 | port: 8443 16 | targetPort: https 17 | selector: 18 | control-plane: controller-manager 19 | -------------------------------------------------------------------------------- /config/certmanager/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This configuration is for teaching kustomize how to update name ref and var substitution 2 | nameReference: 3 | - kind: Issuer 4 | group: certmanager.k8s.io 5 | fieldSpecs: 6 | - kind: Certificate 7 | group: certmanager.k8s.io 8 | path: spec/issuerRef/name 9 | 10 | varReference: 11 | - kind: Certificate 12 | group: certmanager.k8s.io 13 | path: spec/commonName 14 | - kind: Certificate 15 | group: certmanager.k8s.io 16 | path: spec/dnsNames 17 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - secret.kiwigrid.com 11 | resources: 12 | - secrets 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - secret.kiwigrid.com 23 | resources: 24 | - secrets/status 25 | verbs: 26 | - get 27 | - patch 28 | - update 29 | -------------------------------------------------------------------------------- /config/default/manager_prometheus_metrics_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch enables Prometheus scraping for the manager pod. 2 | apiVersion: apps/v1 3 | kind: Deployment 4 | metadata: 5 | name: controller-manager 6 | namespace: system 7 | spec: 8 | template: 9 | metadata: 10 | annotations: 11 | prometheus.io/scrape: 'true' 12 | spec: 13 | containers: 14 | # Expose the prometheus metrics on default port 15 | - name: manager 16 | ports: 17 | - containerPort: 8080 18 | name: metrics 19 | protocol: TCP 20 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - "" 21 | resources: 22 | - configmaps/status 23 | verbs: 24 | - get 25 | - update 26 | - patch 27 | - apiGroups: 28 | - "" 29 | resources: 30 | - events 31 | verbs: 32 | - create 33 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ -------------------------------------------------------------------------------- /config/default/manager_webhook_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | ports: 12 | - containerPort: 9443 13 | name: webhook-server 14 | protocol: TCP 15 | volumeMounts: 16 | - mountPath: /tmp/k8s-webhook-server/serving-certs 17 | name: cert 18 | readOnly: true 19 | volumes: 20 | - name: cert 21 | secret: 22 | defaultMode: 420 23 | secretName: webhook-server-cert 24 | -------------------------------------------------------------------------------- /config/default/webhookcainjection_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch add annotation to admission webhook config and 2 | # the variables $(CERTIFICATE_NAMESPACE) and $(CERTIFICATE_NAME) will be substituted by kustomize. 3 | apiVersion: admissionregistration.k8s.io/v1beta1 4 | kind: MutatingWebhookConfiguration 5 | metadata: 6 | name: mutating-webhook-configuration 7 | annotations: 8 | certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 9 | --- 10 | apiVersion: admissionregistration.k8s.io/v1beta1 11 | kind: ValidatingWebhookConfiguration 12 | metadata: 13 | name: validating-webhook-configuration 14 | annotations: 15 | certmanager.k8s.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 16 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the controller manager, 2 | # it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.4.0 14 | args: 15 | - "--secure-listen-address=0.0.0.0:8443" 16 | - "--upstream=http://127.0.0.1:8080/" 17 | - "--logtostderr=true" 18 | - "--v=10" 19 | ports: 20 | - containerPort: 8443 21 | name: https 22 | - name: manager 23 | args: 24 | - "--metrics-addr=127.0.0.1:8080" 25 | - "--enable-leader-election" 26 | -------------------------------------------------------------------------------- /config/webhook/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # the following config is for teaching kustomize where to look at when substituting vars. 2 | # It requires kustomize v2.1.0 or newer to work properly. 3 | nameReference: 4 | - kind: Service 5 | version: v1 6 | fieldSpecs: 7 | - kind: MutatingWebhookConfiguration 8 | group: admissionregistration.k8s.io 9 | path: webhooks/clientConfig/service/name 10 | - kind: ValidatingWebhookConfiguration 11 | group: admissionregistration.k8s.io 12 | path: webhooks/clientConfig/service/name 13 | 14 | namespace: 15 | - kind: MutatingWebhookConfiguration 16 | group: admissionregistration.k8s.io 17 | path: webhooks/clientConfig/service/namespace 18 | create: true 19 | - kind: ValidatingWebhookConfiguration 20 | group: admissionregistration.k8s.io 21 | path: webhooks/clientConfig/service/namespace 22 | create: true 23 | 24 | varReference: 25 | - path: metadata/annotations 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.12.5 as builder 3 | 4 | WORKDIR /workspace 5 | # Copy the Go Modules manifests 6 | COPY go.mod go.mod 7 | COPY go.sum go.sum 8 | # cache deps before building and copying source so that we don't need to re-download as much 9 | # and so that source changes don't invalidate our downloaded layer 10 | RUN go mod download 11 | 12 | # Copy the go source 13 | COPY main.go main.go 14 | # COPY api/ api/ 15 | COPY controllers/ controllers/ 16 | COPY service/ service/ 17 | 18 | # Build 19 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GO111MODULE=on go build -a -o manager main.go 20 | 21 | # Use distroless as minimal base image to package the manager binary 22 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 23 | FROM gcr.io/distroless/static:nonroot 24 | WORKDIR / 25 | COPY --from=builder /workspace/manager . 26 | USER nonroot:nonroot 27 | 28 | ENTRYPOINT ["/manager"] 29 | -------------------------------------------------------------------------------- /config/certmanager/certificate.yaml: -------------------------------------------------------------------------------- 1 | # The following manifests contain a self-signed issuer CR and a certificate CR. 2 | # More document can be found at https://docs.cert-manager.io 3 | apiVersion: certmanager.k8s.io/v1alpha1 4 | kind: Issuer 5 | metadata: 6 | name: selfsigned-issuer 7 | namespace: system 8 | spec: 9 | selfSigned: {} 10 | --- 11 | apiVersion: certmanager.k8s.io/v1alpha1 12 | kind: Certificate 13 | metadata: 14 | name: serving-cert # this name should match the one appeared in kustomizeconfig.yaml 15 | namespace: system 16 | spec: 17 | # $(SERVICE_NAME) and $(SERVICE_NAMESPACE) will be substituted by kustomize 18 | commonName: $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc 19 | dnsNames: 20 | - $(SERVICE_NAME).$(SERVICE_NAMESPACE).svc.cluster.local 21 | issuerRef: 22 | kind: Issuer 23 | name: selfsigned-issuer 24 | secretName: webhook-server-cert # this secret will not be prefixed, since it's not managed by kustomize 25 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | containers: 26 | - command: 27 | - /manager 28 | args: 29 | - --enable-leader-election 30 | image: controller:latest 31 | name: manager 32 | resources: 33 | limits: 34 | cpu: 100m 35 | memory: 30Mi 36 | requests: 37 | cpu: 100m 38 | memory: 20Mi 39 | terminationGracePeriodSeconds: 10 40 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | *This project has been archived!* 2 | 3 | Please use https://github.com/kubeops/config-syncer[config-syncer] or https://github.com/emberstack/kubernetes-reflector[Reflector] instead. 4 | 5 | # Secret Replicator 6 | 7 | [#img-build] 8 | [caption="Figure 1: ",link=https://travis-ci.com/kiwigrid/secret-replicator] 9 | image::https://img.shields.io/travis/com/kiwigrid/secret-replicator.svg?style=plastic[Build] 10 | 11 | 12 | This tool manages existing secrets and keep them in sync over multiple namespaces. 13 | You can also exclude namespaces. 14 | 15 | 16 | ## Environment Configuration 17 | 18 | The following table lists the environment variables for configuration. 19 | 20 | [options="header"] 21 | |======= 22 | |ENV | Description | Default 23 | |`SECRETS_LIST` | a csv list of secrets names | `''` 24 | |`SECRET_NAMESPACE` | the namespace where to find the original secrets | the namespace where the deployment is installed 25 | |`IGNORE_NAMESPACES` | a list of namespaces that should be ignored | default empty 26 | |======= 27 | 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.11.x 4 | 5 | dist: xenial 6 | sudo: required 7 | 8 | # Force-enable Go modules. This will be unnecessary when Go 1.12 lands. 9 | env: 10 | - CHANGE_MINIKUBE_NONE_USER=true INSTALL_K8S_TOOLS=1 RUN_DEP=1 IMG=kiwigrid/secret-replicator 11 | 12 | git: 13 | depth: 1 14 | 15 | # Skip the install step. Don't `go get` dependencies. Only build with the code 16 | # in vendor/ 17 | install: true 18 | 19 | # Don't email me the results of the test runs. 20 | notifications: 21 | email: false 22 | 23 | # Anything in before_script that returns a nonzero exit code will flunk the 24 | # build and immediately stop. It's sorta like having set -e enabled in bash. 25 | # Make sure golangci-lint is vendored. 26 | 27 | before_script: 28 | - ./tools/travis/start-kubeadm-dind.sh 29 | - ./tools/travis/k8s-tools.sh 30 | - ./tools/travis/minikube-install.sh 31 | 32 | # script always runs to completion (set +e). If we have linter issues AND a 33 | # failing test, we want to see both. Configure golangci-lint with a 34 | # .golangci.yml file at the top level of your repo. 35 | script: 36 | - make 37 | 38 | deploy: 39 | - provider: script 40 | script: bash ./tools/travis/deploy.sh 41 | on: 42 | branch: master 43 | - provider: script 44 | script: bash ./tools/travis/deploy.sh $TRAVIS_TAG 45 | on: 46 | tags: true -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI build 2 | 3 | on: [pull_request] 4 | 5 | env: 6 | CHANGE_MINIKUBE_NONE_USER: true 7 | INSTALL_K8S_TOOLS: 1 8 | IMG: kiwigrid/secret-replicator 9 | jobs: 10 | 11 | build: 12 | name: Build 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: Set up Go 1.12 17 | uses: actions/setup-go@v1 18 | with: 19 | go-version: 1.12 20 | id: go 21 | 22 | - name: Check out code into the Go module directory 23 | uses: actions/checkout@v2 24 | 25 | - name: Get dependencies 26 | run: go get -v -t -d ./... 27 | 28 | - name: Build 29 | run: make 30 | 31 | - name: Build Docker 32 | run: make docker-build 33 | 34 | install-chart: 35 | name: install-chart 36 | runs-on: ubuntu-latest 37 | needs: 38 | - build 39 | strategy: 40 | matrix: 41 | k8s: 42 | # assume no breaking changes between the version 43 | - v1.12.10 44 | - v1.17.2 45 | steps: 46 | - name: Checkout 47 | uses: actions/checkout@v1 48 | - name: Create kind ${{ matrix.k8s }} cluster 49 | uses: engineerd/setup-kind@v0.3.0 50 | with: 51 | config: .github/kind-config.yaml 52 | image: kindest/node:${{ matrix.k8s }} 53 | - name: Run tests 54 | env: 55 | TEST_EXTERNAL_KUBE: true 56 | run: make test -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Image URL to use all building/pushing image targets 3 | IMG ?= controller:latest 4 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 5 | CRD_OPTIONS ?= "crd:trivialVersions=true" 6 | 7 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 8 | ifeq (,$(shell go env GOBIN)) 9 | GOBIN=$(shell go env GOPATH)/bin 10 | else 11 | GOBIN=$(shell go env GOBIN) 12 | endif 13 | 14 | all: manager 15 | 16 | # Run tests 17 | test: generate fmt vet manifests 18 | go test ./... -coverprofile cover.out 19 | 20 | # Build manager binary 21 | manager: generate fmt vet 22 | go build -o bin/manager main.go 23 | 24 | # Run against the configured Kubernetes cluster in ~/.kube/config 25 | run: generate fmt vet manifests 26 | go run ./main.go 27 | 28 | # Install CRDs into a cluster 29 | install: manifests 30 | kustomize build config/crd | kubectl apply -f - 31 | 32 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 33 | deploy: manifests 34 | cd config/manager && kustomize edit set image controller=${IMG} 35 | kustomize build config/default | kubectl apply -f - 36 | 37 | # Generate manifests e.g. CRD, RBAC etc. 38 | manifests: controller-gen 39 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 40 | 41 | # Run go fmt against code 42 | fmt: 43 | go fmt ./... 44 | 45 | # Run go vet against code 46 | vet: 47 | go vet ./... 48 | 49 | # Generate code 50 | generate: controller-gen 51 | $(CONTROLLER_GEN) object:headerFile=./hack/boilerplate.go.txt paths="./..." 52 | 53 | # Build the docker image 54 | docker-build: 55 | docker build . -t ${IMG} 56 | 57 | # Push the docker image 58 | docker-push: 59 | docker push ${IMG} 60 | 61 | # find or download controller-gen 62 | # download controller-gen if necessary 63 | controller-gen: 64 | ifeq (, $(shell which controller-gen)) 65 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.2.1 66 | CONTROLLER_GEN=$(GOBIN)/controller-gen 67 | else 68 | CONTROLLER_GEN=$(shell which controller-gen) 69 | endif 70 | -------------------------------------------------------------------------------- /service/secret_service.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/go-logr/logr" 7 | "k8s.io/api/core/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | "k8s.io/apimachinery/pkg/types" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 12 | "strings" 13 | ) 14 | 15 | type PullSecretService struct { 16 | log logr.Logger 17 | } 18 | 19 | func NewPullSecretService() *PullSecretService { 20 | return &PullSecretService{log: logf.Log.WithName("pullsecretservice")} 21 | } 22 | func (s *PullSecretService) CheckServiceAccountExists(client client.Client, copySecret *corev1.Secret, namespace string, currentNamespace string, secrets []string) (bool, error) { 23 | 24 | for _, element := range secrets { 25 | if element == "" { 26 | continue 27 | } 28 | 29 | found := &corev1.Secret{} 30 | err := client.Get(context.TODO(), types.NamespacedName{Name: element, Namespace: namespace}, found) 31 | if err != nil { 32 | s.log.Error(err, "Could not find secret %v in namespace %v") 33 | continue 34 | } 35 | s.CreateOrUpdateSecret(client, copySecret, namespace, copySecret.Name) 36 | } 37 | return true, nil 38 | } 39 | 40 | func (s *PullSecretService) CreateOrUpdateSecret(client client.Client, copySecret *corev1.Secret, namespace string, secretName string) { 41 | secret := &corev1.Secret{} 42 | err := client.Get(context.TODO(), types.NamespacedName{Name: secretName, Namespace: namespace}, secret) 43 | if err != nil { 44 | if strings.Contains(err.Error(), "not found") { 45 | //logrus.Infof("secret %v does not exists in namespace %v creating new", secretName, namespace) 46 | kubSecret := &v1.Secret{} 47 | kubSecret.Namespace = namespace 48 | kubSecret.Name = copySecret.Name 49 | kubSecret.Type = copySecret.Type 50 | kubSecret.Data = map[string][]byte{} 51 | 52 | for k, v := range copySecret.Data { 53 | s.log.Info("copy %s for secret %s", k, copySecret.Name) 54 | kubSecret.Data[k] = v 55 | } 56 | 57 | err := client.Create(context.TODO(), kubSecret) 58 | if err == nil { 59 | s.log.Info(fmt.Sprintf("successful saved secret %v in namespace %v", secretName, namespace)) 60 | } else { 61 | s.log.Error(err, "ERROR") 62 | } 63 | } else { 64 | s.log.Error(err, "ERROR") 65 | } 66 | } else { 67 | for k, v := range copySecret.Data { 68 | s.log.Info("copy %s for secret %s", k, copySecret.Name) 69 | secret.Data[k] = v 70 | } 71 | err := client.Update(context.TODO(), secret) 72 | if err == nil { 73 | s.log.Info(fmt.Sprintf("successful updated secret %v in namespace %v", secretName, namespace)) 74 | } else { 75 | s.log.Error(err, "ERROR") 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package controllers 17 | 18 | import ( 19 | "os" 20 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/tools/clientcmd" 26 | 27 | "k8s.io/client-go/kubernetes/scheme" 28 | "k8s.io/client-go/rest" 29 | "sigs.k8s.io/controller-runtime/pkg/client" 30 | "sigs.k8s.io/controller-runtime/pkg/envtest" 31 | logf "sigs.k8s.io/controller-runtime/pkg/log" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | // +kubebuilder:scaffold:imports 34 | ) 35 | 36 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 37 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 38 | 39 | var cfg *rest.Config 40 | var k8sClient client.Client 41 | var testEnv *envtest.Environment 42 | 43 | func TestAPIs(t *testing.T) { 44 | RegisterFailHandler(Fail) 45 | 46 | RunSpecsWithDefaultAndCustomReporters(t, 47 | "Controller Suite", 48 | []Reporter{envtest.NewlineReporter{}}) 49 | } 50 | 51 | var _ = BeforeSuite(func(done Done) { 52 | logf.SetLogger(zap.LoggerTo(GinkgoWriter, true)) 53 | 54 | By("bootstrapping test environment") 55 | 56 | if os.Getenv("TEST_EXTERNAL_KUBE") != "" { 57 | existing := true 58 | config, _ := clientcmd.BuildConfigFromFlags("", os.Getenv("KUBECONFIG")) 59 | testEnv = &envtest.Environment{ 60 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 61 | UseExistingCluster: &existing, 62 | Config: config, 63 | } 64 | } else { 65 | testEnv = &envtest.Environment{ 66 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 67 | } 68 | } 69 | 70 | var err error 71 | cfg, err = testEnv.Start() 72 | Expect(err).ToNot(HaveOccurred()) 73 | Expect(cfg).ToNot(BeNil()) 74 | 75 | //err = secretv1.AddToScheme(scheme.Scheme) 76 | //Expect(err).NotTo(HaveOccurred()) 77 | 78 | // +kubebuilder:scaffold:scheme 79 | 80 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 81 | Expect(err).ToNot(HaveOccurred()) 82 | Expect(k8sClient).ToNot(BeNil()) 83 | 84 | close(done) 85 | }, 60) 86 | 87 | var _ = AfterSuite(func() { 88 | By("tearing down the test environment") 89 | err := testEnv.Stop() 90 | Expect(err).ToNot(HaveOccurred()) 91 | }) 92 | -------------------------------------------------------------------------------- /.github/workflows/ci_and_deploy.yaml: -------------------------------------------------------------------------------- 1 | name: CI build and Deploy 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | 9 | env: 10 | CHANGE_MINIKUBE_NONE_USER: true 11 | INSTALL_K8S_TOOLS: 1 12 | IMG: kiwigrid/secret-replicator 13 | jobs: 14 | 15 | build: 16 | name: Build 17 | runs-on: ubuntu-latest 18 | steps: 19 | 20 | - name: Set up Go 1.12 21 | uses: actions/setup-go@v1 22 | with: 23 | go-version: 1.12 24 | id: go 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v2 28 | 29 | - name: Get dependencies 30 | run: go get -v -t -d ./... 31 | 32 | - name: Build 33 | run: make 34 | 35 | - name: Build Docker 36 | run: make docker-build 37 | 38 | install-chart: 39 | name: install-chart 40 | runs-on: ubuntu-latest 41 | needs: 42 | - build 43 | strategy: 44 | matrix: 45 | k8s: 46 | # assume no breaking changes between the version 47 | - v1.12.10 48 | - v1.17.2 49 | steps: 50 | - name: Checkout 51 | uses: actions/checkout@v1 52 | - name: Create kind ${{ matrix.k8s }} cluster 53 | uses: engineerd/setup-kind@v0.3.0 54 | with: 55 | config: .github/kind-config.yaml 56 | image: kindest/node:${{ matrix.k8s }} 57 | - name: Run tests 58 | env: 59 | TEST_EXTERNAL_KUBE: true 60 | run: make test 61 | 62 | deploy: 63 | name: Deploy 64 | runs-on: ubuntu-latest 65 | needs: 66 | - build 67 | - install-chart 68 | steps: 69 | 70 | - name: Set up Go 1.12 71 | uses: actions/setup-go@v1 72 | with: 73 | go-version: 1.12 74 | id: go 75 | 76 | - name: Check out code into the Go module directory 77 | uses: actions/checkout@v2 78 | 79 | - name: Get dependencies 80 | run: go get -v -t -d ./... 81 | 82 | - name: Build 83 | run: make 84 | 85 | - name: Build Docker 86 | run: make docker-build 87 | 88 | - name: Publish to Registry 89 | uses: HurricanKai/Publish-Docker-Github-Action@master 90 | with: 91 | name: kiwigrid/secret-replicator 92 | username: ${{ secrets.DOCKER_USERNAME }} 93 | password: ${{ secrets.DOCKER_PASSWORD }} 94 | 95 | - name: Publish Tag to Registry 96 | if: contains(github.ref, 'refs/tags/v') 97 | uses: HurricanKai/Publish-Docker-Github-Action@master 98 | with: 99 | name: kiwigrid/secret-replicator 100 | username: ${{ secrets.DOCKER_USERNAME }} 101 | password: ${{ secrets.DOCKER_PASSWORD }} 102 | tagging: true -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: secret-replicator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: secret-replicator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 20 | #- ../webhook 21 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 22 | #- ../certmanager 23 | 24 | patchesStrategicMerge: 25 | # Protect the /metrics endpoint by putting it behind auth. 26 | # Only one of manager_auth_proxy_patch.yaml and 27 | # manager_prometheus_metrics_patch.yaml should be enabled. 28 | - manager_auth_proxy_patch.yaml 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, uncomment the following line and 31 | # comment manager_auth_proxy_patch.yaml. 32 | # Only one of manager_auth_proxy_patch.yaml and 33 | # manager_prometheus_metrics_patch.yaml should be enabled. 34 | #- manager_prometheus_metrics_patch.yaml 35 | 36 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in crd/kustomization.yaml 37 | #- manager_webhook_patch.yaml 38 | 39 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 40 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 41 | # 'CERTMANAGER' needs to be enabled to use ca injection 42 | #- webhookcainjection_patch.yaml 43 | 44 | # the following config is for teaching kustomize how to do var substitution 45 | vars: 46 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 47 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 48 | # objref: 49 | # kind: Certificate 50 | # group: certmanager.k8s.io 51 | # version: v1alpha1 52 | # name: serving-cert # this name should match the one in certificate.yaml 53 | # fieldref: 54 | # fieldpath: metadata.namespace 55 | #- name: CERTIFICATE_NAME 56 | # objref: 57 | # kind: Certificate 58 | # group: certmanager.k8s.io 59 | # version: v1alpha1 60 | # name: serving-cert # this name should match the one in certificate.yaml 61 | #- name: SERVICE_NAMESPACE # namespace of the service 62 | # objref: 63 | # kind: Service 64 | # version: v1 65 | # name: webhook-service 66 | # fieldref: 67 | # fieldpath: metadata.namespace 68 | #- name: SERVICE_NAME 69 | # objref: 70 | # kind: Service 71 | # version: v1 72 | # name: webhook-service 73 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package main 17 | 18 | import ( 19 | "flag" 20 | "io/ioutil" 21 | "os" 22 | "strings" 23 | 24 | "github.com/kiwigrid/secret-replicator/controllers" 25 | "github.com/kiwigrid/secret-replicator/service" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 28 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 29 | ctrl "sigs.k8s.io/controller-runtime" 30 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 31 | // +kubebuilder:scaffold:imports 32 | ) 33 | 34 | var ( 35 | scheme = runtime.NewScheme() 36 | setupLog = ctrl.Log.WithName("setup") 37 | ) 38 | 39 | func init() { 40 | _ = clientgoscheme.AddToScheme(scheme) 41 | 42 | // _ = secretv1.AddToScheme(scheme) 43 | // +kubebuilder:scaffold:scheme 44 | } 45 | 46 | func main() { 47 | var metricsAddr string 48 | var enableLeaderElection bool 49 | flag.StringVar(&metricsAddr, "metrics-addr", ":8080", "The address the metric endpoint binds to.") 50 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 51 | "Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager.") 52 | flag.Parse() 53 | 54 | ctrl.SetLogger(zap.Logger(true)) 55 | 56 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 57 | Scheme: scheme, 58 | MetricsBindAddress: metricsAddr, 59 | LeaderElection: enableLeaderElection, 60 | Port: 9443, 61 | }) 62 | if err != nil { 63 | setupLog.Error(err, "unable to start manager") 64 | os.Exit(1) 65 | } 66 | 67 | var currentNamespace string 68 | if os.Getenv("SECRET_NAMESPACE") == "" { 69 | file, err := ioutil.ReadFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") 70 | if err != nil { 71 | //logf.Log.WithName("pull-secret-controller").Error(err, "") 72 | } 73 | currentNamespace = string(file) 74 | } else { 75 | currentNamespace = os.Getenv("SECRET_NAMESPACE") 76 | } 77 | 78 | if err = (&controllers.SecretReconciler{ 79 | Client: mgr.GetClient(), 80 | Log: ctrl.Log.WithName("controllers").WithName("Secret"), 81 | Secrets: strings.Split(os.Getenv("SECRETS_LIST"), ","), 82 | IgnoreNamespaces: strings.Split(os.Getenv("IGNORE_NAMESPACES"), ","), 83 | IncludeNamespaces: strings.Split(os.Getenv("INCLUDE_NAMESPACES"), ","), 84 | CurrentNamespace: currentNamespace, 85 | PullSecretService: service.NewPullSecretService(), 86 | }).SetupWithManager(mgr); err != nil { 87 | setupLog.Error(err, "unable to create controller", "controller", "Secret") 88 | os.Exit(1) 89 | } 90 | // +kubebuilder:scaffold:builder 91 | 92 | setupLog.Info("starting manager") 93 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 94 | setupLog.Error(err, "problem running manager") 95 | os.Exit(1) 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /controllers/secret_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | package controllers 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "regexp" 22 | 23 | "github.com/go-logr/logr" 24 | "github.com/kiwigrid/secret-replicator/service" 25 | v1 "k8s.io/api/core/v1" 26 | "k8s.io/apimachinery/pkg/api/errors" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 30 | ) 31 | 32 | // SecretReconciler reconciles a Secret object 33 | type SecretReconciler struct { 34 | client.Client 35 | Log logr.Logger 36 | *service.PullSecretService 37 | Secrets []string 38 | IgnoreNamespaces []string 39 | IncludeNamespaces []string 40 | CurrentNamespace string 41 | } 42 | 43 | // +kubebuilder:rbac:groups=secret.kiwigrid.com,resources=secrets,verbs=get;list;watch;create;update;patch;delete 44 | // +kubebuilder:rbac:groups=secret.kiwigrid.com,resources=secrets/status,verbs=get;update;patch 45 | 46 | func (r *SecretReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { 47 | ctx := context.Background() 48 | _ = r.Log.WithValues("secret", req.NamespacedName) 49 | 50 | // your logic here 51 | 52 | // only secrets from lookup namespace 53 | if req.Namespace != r.CurrentNamespace { 54 | return reconcile.Result{}, nil 55 | } 56 | 57 | // only pull secrets 58 | if !contains(r.Secrets, req.Name) { 59 | return reconcile.Result{}, nil 60 | } 61 | 62 | instance := &v1.Secret{} 63 | err := r.Get(context.TODO(), req.NamespacedName, instance) 64 | if err != nil { 65 | if errors.IsNotFound(err) { 66 | // Object not found, return. Created objects are automatically garbage collected. 67 | // For additional cleanup logic use finalizers. 68 | return reconcile.Result{}, nil 69 | } 70 | // Error reading the object - requeue the request. 71 | return reconcile.Result{}, err 72 | } 73 | 74 | namespaces := &v1.NamespaceList{} 75 | searchError := r.List(ctx, namespaces) //, client.InNamespace("")) 76 | if searchError != nil { 77 | r.Log.Error(searchError, "ERROR") 78 | } 79 | for _, element := range namespaces.Items { 80 | if contains(r.IgnoreNamespaces, element.Name) { 81 | continue 82 | } 83 | // only use include namespaces if it is not empty 84 | if len(r.IncludeNamespaces) > 0 { 85 | if contains(r.IncludeNamespaces, element.Name) { 86 | r.Log.Info(fmt.Sprintf("Create or update secret %s in namespace %s", instance.Name, element.Name)) 87 | r.PullSecretService.CreateOrUpdateSecret(r.Client, instance, element.Name, instance.Name) 88 | } 89 | } else { 90 | r.Log.Info(fmt.Sprintf("Create or update secret %s in namespace %s", instance.Name, element.Name)) 91 | r.PullSecretService.CreateOrUpdateSecret(r.Client, instance, element.Name, instance.Name) 92 | } 93 | } 94 | return ctrl.Result{}, nil 95 | } 96 | 97 | func (r *SecretReconciler) SetupWithManager(mgr ctrl.Manager) error { 98 | return ctrl.NewControllerManagedBy(mgr). 99 | For(&v1.Secret{}). 100 | Complete(r) 101 | } 102 | 103 | func contains(s []string, e string) bool { 104 | for _, a := range s { 105 | matched, _ := regexp.Match(a, []byte(e)) 106 | if a == e || matched { 107 | return true 108 | } 109 | } 110 | return false 111 | } 112 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0 h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ= 2 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 4 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= 9 | github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= 10 | github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 11 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 12 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 13 | github.com/go-logr/logr v0.1.0 h1:M1Tv3VzNlEHg6uyACnRdtrploV2P7wZqH8BoQMtz0cg= 14 | github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= 15 | github.com/go-logr/zapr v0.1.0 h1:h+WVe9j6HAA01niTJPA/kKH0i7e0rLZBCwauQFcRE54= 16 | github.com/go-logr/zapr v0.1.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk= 17 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 18 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 19 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7 h1:u4bArs140e9+AfE52mFHOXVFnOSBJBRlzTHrOPLOIhE= 20 | github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 21 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 22 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 23 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 24 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= 25 | github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= 26 | github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= 27 | github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= 28 | github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47 h1:UnszMmmmm5vLwWzDjTFVIkfhvWF1NdrmChl8L2NUDCw= 29 | github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 30 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 31 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 32 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 33 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 34 | github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= 35 | github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 36 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 37 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 38 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 39 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 40 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 41 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 42 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 43 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 44 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 45 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= 46 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 47 | github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= 48 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 49 | github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= 50 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 51 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c h1:MUyE44mTvnI5A0xrxIxaMqoWFzPfQvtE2IWUollMDMs= 52 | github.com/pborman/uuid v0.0.0-20170612153648-e790cca94e6c/go.mod h1:VyrYX9gd7irzKovcSS6BIIEwPRkP2Wm2m9ufcdFSJ34= 53 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 54 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 55 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 56 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 57 | github.com/prometheus/client_golang v0.9.0 h1:tXuTFVHC03mW0D+Ua1Q2d1EAVqLTuggX50V0VLICCzY= 58 | github.com/prometheus/client_golang v0.9.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 59 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 60 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 61 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= 62 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 63 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273 h1:agujYaXJSxSo18YNX3jzl+4G6Bstwt+kqv47GS12uL0= 64 | github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 65 | github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 66 | github.com/spf13/pflag v1.0.2 h1:Fy0orTDgHdbnzHcsOgfCN4LtHf0ec3wwtiwJqwvf3Gc= 67 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 68 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 69 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 70 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 71 | go.uber.org/atomic v1.3.2 h1:2Oa65PReHzfn29GpvgsYwloV9AVFHPDk8tYxt2c2tr4= 72 | go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 73 | go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= 74 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 75 | go.uber.org/zap v1.9.1 h1:XCJQEf3W6eZaVwhRBof6ImoYGJSITeKWsyeh3HFu/5o= 76 | go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 77 | golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac h1:7d7lG9fHOLdL6jZPtnV4LpI41SbohIJ1Atq7U991dMg= 78 | golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 79 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= 80 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 81 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= 82 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 83 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 84 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 85 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= 86 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 87 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 88 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 89 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= 90 | golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 91 | gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= 92 | gomodules.xyz/jsonpatch/v2 v2.0.1/go.mod h1:IhYNNY4jnS53ZnfE4PAmpKtDpTCj1JFXc+3mwe7XcUU= 93 | google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= 94 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 95 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 96 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 97 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 98 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 99 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 100 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 101 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 102 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 103 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 104 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 105 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 106 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 107 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b h1:aBGgKJUM9Hk/3AE8WaZIApnTxG35kbuQba2w+SXqezo= 108 | k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= 109 | k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8 h1:q1Qvjzs/iEdXF6A1a8H3AKVFDzJNcJn3nXMs6R6qFtA= 110 | k8s.io/apiextensions-apiserver v0.0.0-20190409022649-727a075fdec8/go.mod h1:IxkesAMoaCRoLrPJdZNZUQp9NfZnzqaVzLhb2VEQzXE= 111 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d h1:Jmdtdt1ZnoGfWWIIik61Z7nKYgO3J+swQJtPYsP9wHA= 112 | k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= 113 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible h1:U5Bt+dab9K8qaUmXINrkXO135kA11/i5Kg1RUydgaMQ= 114 | k8s.io/client-go v11.0.1-0.20190409021438-1a26190bd76a+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= 115 | k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= 116 | k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= 117 | k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c h1:3KSCztE7gPitlZmWbNwue/2U0YruD65DqX3INopDAQM= 118 | k8s.io/kube-openapi v0.0.0-20180731170545-e3762e86a74c/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= 119 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 h1:VBM/0P5TWxwk+Nw6Z+lAw3DKgO76g90ETOiA6rfLV1Y= 120 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew= 121 | sigs.k8s.io/controller-runtime v0.2.2 h1:JT/vJJhUjjL9NZNwnm8AXmqCBUXSCFKmTaNjwDi28N0= 122 | sigs.k8s.io/controller-runtime v0.2.2/go.mod h1:9dyohw3ZtoXQuV1e766PHUn+cmrRCIcBh6XIMFNMZ+I= 123 | sigs.k8s.io/testing_frameworks v0.1.1 h1:cP2l8fkA3O9vekpy5Ks8mmA0NW/F7yBdXf8brkWhVrs= 124 | sigs.k8s.io/testing_frameworks v0.1.1/go.mod h1:VVBKrHmJ6Ekkfz284YKhQePcdycOzNH9qL6ht1zEr/U= 125 | sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= 126 | sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= 127 | --------------------------------------------------------------------------------