├── .DEREK.yml ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── build.yaml │ └── publish.yaml ├── .gitignore ├── Dockerfile ├── GUIDE.md ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── ROADMAP.md ├── api └── v1 │ ├── clusterpullsecret_types.go │ ├── groupversion_info.go │ └── zz_generated.deepcopy.go ├── config ├── controller │ ├── controller.yaml │ └── kustomization.yaml ├── crd │ ├── bases │ │ └── ops.alexellis.io_clusterpullsecrets.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_clusterpullsecrets.yaml │ │ └── webhook_in_clusterpullsecrets.yaml ├── default │ ├── kustomization.yaml │ └── manager_auth_proxy_patch.yaml ├── rbac │ ├── clusterpullsecret_editor_role.yaml │ ├── clusterpullsecret_viewer_role.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── role.yaml │ └── role_binding.yaml └── samples │ └── dockerhub.yaml ├── controllers ├── clusterpullsecret_controller.go ├── namespace_watcher.go ├── secret_reconciler.go └── serviceaccount_watcher.go ├── diagram.jpg ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt ├── main.go └── manifest.yaml /.DEREK.yml: -------------------------------------------------------------------------------- 1 | curators: 2 | - alexellis 3 | - Waterdrips 4 | 5 | features: 6 | - dco_check 7 | - comments 8 | - pr_description_required 9 | - release_notes 10 | 11 | custom_messages: 12 | - name: slack 13 | value: | 14 | -- 15 | Join Slack to connect with the community using the `#general` channel 16 | within the OpenFaaS Slack workspace. 17 | https://docs.openfaas.com/community 18 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /registry-creds 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | 24 | **Are you a GitHub Sponsor (Yes/No?)** 25 | 26 | Issues from sponsors get prioritised and a quicker response. 27 | 28 | Check at: https://github.com/sponsors/alexellis 29 | - [ ] Yes 30 | - [ ] No 31 | 32 | **Screenshots or console output** 33 | If applicable, add screenshots to help explain your problem. 34 | 35 | **Operating system and version:** 36 | 37 | 38 | **List all possible solutions, and your suggested option** 39 | 40 | 41 | **Additional context** 42 | Add any other context about the problem here. 43 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe all possible solutions, and which you suggest** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives and workarounds that you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Are you a GitHub Sponsor (Yes/No?)** 20 | 21 | Requests from sponsors get prioritised and a quicker response. 22 | 23 | Check at: https://github.com/sponsors/alexellis 24 | - [ ] Yes 25 | - [ ] No 26 | 27 | **Additional context** 28 | Add any other context or screenshots about the feature request here. 29 | 30 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## Which issue # does this fix? And was it approved before you worked on it? 5 | 6 | 7 | 8 | Checklist: 9 | 10 | - [ ] Fixes issue: # 11 | - [ ] I waited for approval before creating this PR 12 | 13 | Note: if the PR is for a typo, please close it and raise an issue instead. 14 | 15 | ## Are you a GitHub Sponsor (Yes/No?) 16 | 17 | Pull Requests from sponsors get prioritised and a quicker response. 18 | 19 | Check at: https://github.com/sponsors/alexellis 20 | - [ ] Yes 21 | - [ ] No 22 | 23 | ## How Has This Been Tested? 24 | 25 | 26 | 27 | 28 | 29 | ## How are existing users impacted? What migration steps/scripts do we need? 30 | 31 | 32 | ## Checklist: 33 | 34 | I have: 35 | 36 | - [ ] updated the documentation and/or roadmap (if required) 37 | - [ ] read the [CONTRIBUTION](https://github.com/inlets/inlets/blob/master/CONTRIBUTING.md) guide 38 | - [ ] signed-off my commits with `git commit -s` 39 | - [ ] added unit tests 40 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | with: 15 | fetch-depth: 1 16 | 17 | - name: Set up QEMU 18 | uses: docker/setup-qemu-action@v3 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v3 21 | 22 | - name: Local build 23 | id: local_build 24 | uses: docker/build-push-action@v5 25 | with: 26 | build-args: | 27 | Version=dev 28 | GitCommit=${{ github.sha }} 29 | outputs: "type=docker,push=false" 30 | platforms: linux/amd64 31 | tags: ghcr.io/alexellis/registry-creds:${{ github.sha }} 32 | 33 | - name: Setup Kubernetes 34 | uses: engineerd/setup-kind@v0.6.2 35 | with: 36 | # This is the KinD version, not the Kubernetes version 37 | version: "v0.25.0" 38 | 39 | - name: Load test image 40 | run: kind load --loglevel trace docker-image ghcr.io/alexellis/registry-creds:${{ github.sha }} 41 | - name: Apply manifests 42 | run: | 43 | TAG=${{ github.sha }} make shrinkwrap 44 | cat ./manifest.yaml | grep ghcr 45 | kubectl apply -f ./manifest.yaml 46 | 47 | - name: Check deployment 48 | run: 49 | kubectl rollout status -n registry-creds-system deploy/registry-creds-registry-creds-controller --timeout=2m || kubectl describe -n registry-creds-system deploy/registry-creds-registry-creds-controller && kubectl get events -n registry-creds-system 50 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | permissions: 9 | actions: read 10 | checks: write 11 | contents: read 12 | packages: write 13 | 14 | jobs: 15 | publish: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@master 19 | with: 20 | fetch-depth: 1 21 | 22 | - name: Get TAG 23 | id: get_tag 24 | run: echo TAG=${GITHUB_REF#refs/tags/} >> $GITHUB_ENV 25 | 26 | - name: Get Repo Owner 27 | id: get_repo_owner 28 | run: echo "REPO_OWNER=$(echo ${{ github.repository_owner }} | tr '[:upper:]' '[:lower:]')" > $GITHUB_ENV 29 | 30 | - name: Set up QEMU 31 | uses: docker/setup-qemu-action@v3 32 | - name: Set up Docker Buildx 33 | uses: docker/setup-buildx-action@v3 34 | 35 | - name: Login to Docker Registry 36 | uses: docker/login-action@v3 37 | with: 38 | username: ${{ github.repository_owner }} 39 | password: ${{ secrets.GITHUB_TOKEN }} 40 | registry: ghcr.io 41 | 42 | - name: Local build 43 | id: local_build 44 | uses: docker/build-push-action@v5 45 | with: 46 | build-args: | 47 | Version=${{ env.TAG }} 48 | GitCommit=${{ github.sha }} 49 | outputs: "type=registry,push=true" 50 | platforms: linux/amd64,linux/arm/v7,linux/arm64 51 | tags: | 52 | ghcr.io/${{ env.REPO_OWNER }}/registry-creds:${{ github.sha }} 53 | ghcr.io/${{ env.REPO_OWNER }}/registry-creds:${{ env.TAG }} 54 | ghcr.io/${{ env.REPO_OWNER }}/registry-creds:latest 55 | -------------------------------------------------------------------------------- /.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 | /registry-creds 26 | /kubeconfig 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.22 as builder 2 | 3 | ARG TARGETPLATFORM 4 | ARG BUILDPLATFORM 5 | ARG TARGETOS 6 | ARG TARGETARCH 7 | ARG Version 8 | ARG GitCommit 9 | 10 | WORKDIR /workspace 11 | 12 | COPY go.mod go.mod 13 | COPY go.sum go.sum 14 | 15 | RUN go mod download 16 | 17 | # Copy the go source 18 | COPY main.go main.go 19 | COPY api/ api/ 20 | COPY controllers/ controllers/ 21 | 22 | # Build 23 | RUN echo flags=${Version} ${GitCommit} 24 | RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ 25 | GO111MODULE=on go build -ldflags "-s -w -X main.Release=${Version} -X main.SHA=${GitCommit}" -o /usr/bin/controller 26 | 27 | # Refer to https://github.com/GoogleContainerTools/distroless for more details 28 | FROM --platform=${BUILDPLATFORM:-linux/amd64} gcr.io/distroless/static:nonroot 29 | WORKDIR / 30 | COPY --from=builder /usr/bin/controller . 31 | USER nonroot:nonroot 32 | 33 | ENTRYPOINT ["/controller"] 34 | -------------------------------------------------------------------------------- /GUIDE.md: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | ![Diagram](./diagram.jpg) 4 | 5 | > Conceptual diagram: registry-creds operator 6 | 7 | * The operator requires CRUD permission on secrets within all namespaces, and uses a ClusterRole. 8 | * A custom resource is also installed called `ClusterPullSecret`, scoped to the global cluster level. 9 | * You'll create a "seed secret", which the ClusterPullSecret will point to 10 | * The operator will clone your seed secret to each namespace, and append it to the default ServiceAccount's imagePullSecrets list 11 | 12 | ## Usage 13 | 14 | ### Option A) Configuration with kubectl 15 | 16 | Apply the YAML for the manifest. 17 | 18 | ```bash 19 | kubectl apply -f https://raw.githubusercontent.com/alexellis/registry-creds/master/manifest.yaml 20 | ``` 21 | 22 | Next, you'll need to create a seed secret and `ClusterPullSecret` referencing it. 23 | 24 | Create a "seed" secret so that it can be referenced by the ClusterPullSecret. You can customise the name, and namespace as per your own preference. 25 | 26 | ```bash 27 | export DOCKER_USERNAME=username 28 | export DOCKER_PASSWORD=mypassword 29 | export DOCKER_EMAIL=me@example.com 30 | 31 | kubectl create secret docker-registry registry-creds \ 32 | --namespace kube-system \ 33 | --docker-username=$DOCKER_USERNAME \ 34 | --docker-password=$DOCKER_PASSWORD \ 35 | --docker-email=$DOCKER_EMAIL 36 | ``` 37 | 38 | If you're not using the Docker Hub, then add `--docker-server` 39 | 40 | Now create a `ClusterPullSecret` YAML file. This is a cluster-scoped resource, so you cannot specify a namespace for it. Populate `secretRef` with the secret name and namespace from above. This is the secret that will be copied to each namespace. 41 | 42 | ```yaml 43 | apiVersion: ops.alexellis.io/v1 44 | kind: ClusterPullSecret 45 | metadata: 46 | name: dockerhub-registry-creds 47 | spec: 48 | secretRef: 49 | name: registry-creds 50 | namespace: kube-system 51 | ``` 52 | 53 | ### Option B) Configuration with arkade 54 | 55 | Create an environment file i.e. `~/.docker-creds`, so that you are not having to keep typing passwords in. 56 | 57 | ```bash 58 | export DOCKER_USERNAME=username 59 | export DOCKER_PASSWORD=password 60 | export DOCKER_EMAIL=email 61 | 62 | # Optional 63 | export DOCKER_SERVER="" 64 | ``` 65 | 66 | Then run this command on any of your clusters 67 | 68 | ```bash 69 | source ~/.docker-creds 70 | arkade install registry-creds --from-env 71 | ``` 72 | 73 | Or specify each flag: 74 | 75 | ```bash 76 | arkade install registry-creds \ 77 | --username "${DOCKER_USERNAME}" \ 78 | --password "${DOCKER_PASSWORD}" \ 79 | --email "${DOCKER_EMAIL}" 80 | ``` 81 | 82 | > Optionally, you can also pass `--server` 83 | 84 | ### Running the tool locally for development 85 | 86 | You can use the [arkade project](https://get-arkade.dev) to get CLIs the easy way, or find your way to the releases page of each application required. 87 | 88 | If you don't have a local Kubernetes cluster, you can create one with k3d, or KinD 89 | 90 | ```bash 91 | arkade get kind 92 | kind create cluster 93 | ``` 94 | 95 | Get the pre-reqs: kubectl and kustomize 96 | 97 | ```bash 98 | arkade get kubectl 99 | arkade get kustomize 100 | ``` 101 | 102 | Install with: 103 | 104 | ```bash 105 | git clone https://github.com/alexellis/registry-creds 106 | cd registry-creds 107 | make install 108 | make run 109 | ``` 110 | 111 | > Note, you can also run `make install deploy` to try running in-cluster. 112 | 113 | ### Rotate your seed secret and `ClusterPullSecret` 114 | 115 | If you want to update your `ClusterPullSecret`, then update your main "seed" secret, delete the `ClusterPullSecret` entry, and create it again. The owner references will garbage collect the older secrets and re-create them again. 116 | 117 | ### Exclude a namespace from being updated 118 | 119 | Disable: 120 | 121 | ```bash 122 | kubectl create ns alex 123 | kubectl annotate ns alex alexellis.io/registry-creds.ignore=1 124 | ``` 125 | 126 | Enable: 127 | 128 | ```bash 129 | kubectl annotate ns alex alexellis.io/registry-creds.ignore=0 --overwrite 130 | ``` 131 | 132 | ## Testing it out 133 | 134 | Do you want to see it all in action, but don't have time to waste? You're in luck, [OpenFaaS](https://www.openfaas.com/) provides a very easy to use workflow for creating a quick Docker image that servers HTTP traffic, and that can be deployed to Kubernetes. 135 | 136 | The easiest way to test the controller is with a private image on the Docker Hub, but you can also use the [arkade project](https://get-arkade.dev) to install a self-hosted registry, with authentication enabled and TLS. 137 | 138 | ```bash 139 | arkade install ingress-nginx 140 | arkade install cert-manager 141 | 142 | arkade install docker-registry 143 | arkade install docker-registry-ingress \ 144 | --email me@example.com \ 145 | --domain reg.example.com 146 | ``` 147 | 148 | Run the above on a computer with a public IP, or use the [inlets-operator](https://github.com/inlets/inlets-operator) to expose your local registry on the Internet, and to get a TLS certificate for it. 149 | 150 | Then go ahead and deploy something like OpenFaaS, create a function, and push it to your registry. 151 | 152 | ```bash 153 | # Get the OpenFaaS CLI, and put it in `PATH` 154 | arkade get faas-cli 155 | 156 | # Install openfaas, and follow the login instructions 157 | arkade install openfaas 158 | 159 | # Create a new function and push it to your registry 160 | faas-cli new --lang go --prefix reg.example.com/functions awesome-api 161 | faas-cli up -f awesome-api.yml 162 | ``` 163 | 164 | You'll see `reg.example.com/functions/awesome-api:latest` being built, pushed and deployed to your cluster. 165 | 166 | Check the event-stream to see the image being pulled and started: 167 | 168 | ```bash 169 | kubectl get event -n openfaas-fn -w 170 | 171 | LAST SEEN TYPE REASON OBJECT MESSAGE 172 | 8s Normal Scheduled pod/api-577d87c687-knd54 Successfully assigned openfaas-fn/api-577d87c687-knd54 to kind-control-plane 173 | 7s Normal Pulling pod/api-577d87c687-knd54 Pulling image "alexellis2/http-api" 174 | 8s Normal SuccessfulCreate replicaset/api-577d87c687 Created pod: api-577d87c687-knd54 175 | 8s Normal ScalingReplicaSet deployment/api Scaled up replica set api-577d87c687 to 1 176 | 0s Normal Pulled pod/api-577d87c687-knd54 Successfully pulled image "alexellis2/http-api" 177 | 0s Normal Created pod/api-577d87c687-knd54 Created container api 178 | 0s Normal Started pod/api-577d87c687-knd54 Started container api 179 | ``` 180 | 181 | ## Appendix 182 | 183 | Terraform: 184 | 185 | * [Snippet to imperatively apply a secret throughout your cluster](https://gist.github.com/phumberdroz/81885c01c2207d578c17635afce1b033) by Pierre Humberdroz 186 | 187 | Tools to manage Kubernetes configuration, declaratively: 188 | 189 | * [ArgoCD](https://argoproj.github.io/argo-cd/) - install via `arkade install argocd` 190 | * [Flux](https://fluxcd.io) 191 | 192 | Pull-through caching registries: 193 | 194 | * [Preparing Google Cloud deployments for Docker Hub pull request limits](https://cloud.google.com/blog/products/containers-kubernetes/mitigating-the-impact-of-new-docker-hub-pull-request-limits) 195 | * [Registry as a pull through cache](https://docs.docker.com/registry/recipes/mirror/) 196 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OpenFaaS Ltd, Alex Ellis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | # Image URL to use all building/pushing image targets 3 | TAG?=latest 4 | 5 | export DOCKER_CLI_EXPERIMENTAL=enabled 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: controller 15 | 16 | # Run tests 17 | test: generate fmt vet manifests 18 | go test ./... -coverprofile cover.out 19 | 20 | # Build controller binary 21 | controller: generate fmt vet 22 | go build -o bin/controller 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 | # Uninstall CRDs from a cluster 33 | uninstall: manifests 34 | kustomize build config/crd | kubectl delete -f - 35 | 36 | # Deploy controller in the configured Kubernetes cluster in ~/.kube/config 37 | deploy: manifests 38 | cd config/controller && kustomize edit set image controller=ghcr.io/ghcr.io/alexellis/registry-creds:$(TAG) 39 | kustomize build config/default | kubectl apply -f - 40 | 41 | .PHONY: shrinkwrap 42 | shrinkwrap: 43 | cd config/default && \ 44 | kustomize edit set image ghcr.io/ghcr.io/alexellis/registry-creds:$(TAG) && \ 45 | kustomize build > ../../manifest.yaml 46 | # Generate manifests e.g. CRD, RBAC etc. 47 | manifests: controller-gen 48 | $(CONTROLLER_GEN) rbac:roleName=registry-creds-role paths="./..." output:crd:artifacts:config=config/crd/bases +crd 49 | 50 | # Run go fmt against code 51 | fmt: 52 | go fmt ./... 53 | 54 | # Run go vet against code 55 | vet: 56 | go vet ./... 57 | 58 | # Generate code 59 | generate: controller-gen 60 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 61 | 62 | .PHONY: docker-build # Build the docker image 63 | docker-build: 64 | @docker buildx create --use --name=multiarch --node=multiarch && \ 65 | docker buildx build \ 66 | --output "type=docker,push=false" \ 67 | --tag ghcr.io/alexellis/registry-creds:$(TAG) \ 68 | . 69 | 70 | .PHONY: docker-publish # Push the docker image to the remote registry 71 | docker-publish: 72 | @docker buildx create --use --name=multiarch --node=multiarch && \ 73 | docker buildx build \ 74 | --platform linux/amd64,linux/arm64,linux/arm/v7 \ 75 | --output "type=image,push=true" \ 76 | --tag ghcr.io/alexellis/registry-creds:$(TAG) . 77 | 78 | # find or download controller-gen 79 | # download controller-gen if necessary 80 | controller-gen: 81 | ifeq (, $(shell which controller-gen)) 82 | @{ \ 83 | set -e ;\ 84 | CONTROLLER_GEN_TMP_DIR=$$(mktemp -d) ;\ 85 | cd $$CONTROLLER_GEN_TMP_DIR ;\ 86 | go mod init tmp ;\ 87 | go get sigs.k8s.io/controller-tools/cmd/controller-gen@v0.13.0;\ 88 | rm -rf $$CONTROLLER_GEN_TMP_DIR ;\ 89 | } 90 | CONTROLLER_GEN=$(GOBIN)/controller-gen 91 | else 92 | CONTROLLER_GEN=$(shell which controller-gen) 93 | endif 94 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: alexellis.io 2 | repo: alexellis/registry-creds 3 | resources: 4 | - group: ops 5 | kind: ClusterPullSecret 6 | version: v1 7 | version: "2" 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## registry-creds operator 2 | 3 | [![Sponsor this](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/alexellis)](https://github.com/sponsors/alexellis) [![build](https://github.com/alexellis/registry-creds/actions/workflows/build.yaml/badge.svg)](https://github.com/alexellis/registry-creds/actions/workflows/build.yaml) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 4 | 5 | 6 | This operator can be used to propagate a single ImagePullSecret to all namespaces within your cluster, so that images are pulled using authentication. 7 | 8 | See also: [ROADMAP.md](/ROADMAP.md) 9 | 10 | ### Use-case 1: Propagate a private registry secret to all namespaces 11 | 12 | The second use-case for this operator is to take an authentication token which is required to pull images from a private registry, and to make sure it's available and configured for each and every namespace. 13 | 14 | For example, if you were running a multi-tenant service, where each tenant has their own namespaces, and every image is sourced from a common private registry. You could use this operator to propagate the pull secret for each namespace. 15 | 16 | ### Use-case 2: Docker Hub Rate Limits 17 | 18 | The original need for this operator, was to make it easier for users of Kubernetes to consume images from the Docker Hub after [recent pricing and rate-limiting changes](https://www.docker.com/pricing) were brought in, an authenticated account is now required to pull images. 19 | 20 | These are the limits as understood at time of writing: 21 | 22 | * Unauthenticated users: 100 pulls / 6 hours 23 | * Authenticated users: 200 pulls / 6 hours 24 | * Paying, authenticated users: unlimited downloads 25 | 26 | Read also: [Docker Hub rate limits & pricing](https://www.docker.com/pricing) 27 | 28 | Pulling images with authentication is required in two scenarios: 29 | * To extend the Docker Hub anonymous pull limits to a practical number 30 | * To access private registries or repos on the Docker Hub 31 | 32 | The normal process is as follows, which becomes tedious and repetitive when you have more than one namespace in a cluster. 33 | 34 | * Create a secret 35 | * Edit your service account, and add the name of the secret to `imagePullSecrets` 36 | 37 | ## Getting Started 38 | 39 | * [Install the tool](GUIDE.md) 40 | 41 | ## Do you use `registry-creds`? 42 | 43 | `k3sup` was created by [Alex Ellis](https://github.com/users/alexellis/sponsorship) - the founder of [OpenFaaS ®](https://www.openfaas.com/) & [inlets](https://inlets.dev/). 44 | 45 | 46 | Sponsor this project 47 | 48 | 49 | Want to see continued development? [Sponsor alexellis on GitHub](https://github.com/users/alexellis/sponsorship) 50 | 51 | ## License 52 | 53 | MIT 54 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | ## Roadmap & status 2 | 3 | The primary purpose of this tool is to ease the every-day lives of developers and new-comers to Kubernetes. When you move to production, you can use something like Flux, Argo or Terraform (see appendix) for managing secrets across namespaces. 4 | 5 | Disclaimer: see the [license of this project](/LICENSE) before deploying or using it. 6 | 7 | You can create a test cluster very quickly with something like [KinD](https://kind.sigs.k8s.io/docs/user/quick-start/) to try it out. 8 | 9 | Backlog (done): 10 | - [x] Create secrets in each namespace at start-up 11 | - [x] Use a "seed" secret via an object reference 12 | - [x] Watch new namespaces and create new secrets 13 | - [x] Update the ImagePullSecret list for the default ServiceAccount in each namespace 14 | - [x] Add an exclude annotation for certain namespaces `alexellis.io/registry-creds.ignore` 15 | - [x] Add Docker image for `x86_64` 16 | - [x] Test and update kustomize 17 | - [x] Add multi-arch Docker image for `x86_64` and arm 18 | - [x] Add one-liner with an arkade app - `arkade install registry-creds --username $DOCKER_USERNAME --password $PASSWORD` 19 | - [x] ~~Add helm chart~~ - static manifest available instead 20 | - [x] Use `apierrors.IsNotFound(err)` everywhere instead of assuming an error means not found 21 | - [x] Support additional ServiceAccounts beyond the `default` account in each namespace 22 | 23 | Todo: 24 | - [ ] Remove pull secret reference from ServiceAccounts upon ClusterPullSecret deletion 25 | - [ ] Propagate alterations/updates to the primary `ClusterPullSecret` in each namespace when the secret value changes (the work-around is to delete and re-create the ClusterPullSecret) 26 | 27 | -------------------------------------------------------------------------------- /api/v1/clusterpullsecret_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package v1 18 | 19 | import ( 20 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 21 | ) 22 | 23 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 24 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 25 | 26 | // ClusterPullSecretStatus defines the observed state of ClusterPullSecret 27 | type ClusterPullSecretStatus struct { 28 | } 29 | 30 | //+kubebuilder:object:root=true 31 | //+kubebuilder:resource:scope=Cluster 32 | //+kubebuilder:printcolumn:name="SecretName",type=string,JSONPath=`.spec.secretRef.name` 33 | //+kubebuilder:printcolumn:name="SecretNamespace",type=string,JSONPath=`.spec.secretRef.namespace` 34 | 35 | // ClusterPullSecret is the Schema for the clusterpullsecrets API 36 | type ClusterPullSecret struct { 37 | metav1.TypeMeta `json:",inline"` 38 | metav1.ObjectMeta `json:"metadata,omitempty"` 39 | 40 | Spec ClusterPullSecretSpec `json:"spec,omitempty"` 41 | Status ClusterPullSecretStatus `json:"status,omitempty"` 42 | } 43 | 44 | // ObjectMeta contains enough information to locate the referenced Kubernetes resource object in any 45 | // namespace. 46 | type ObjectMeta struct { 47 | // Name of the referent. 48 | // +required 49 | Name string `json:"name"` 50 | 51 | // Namespace of the referent, when not specified it acts as LocalObjectReference. 52 | // +optional 53 | Namespace string `json:"namespace,omitempty"` 54 | } 55 | 56 | // ClusterPullSecretSpec defines the desired state of ClusterPullSecret 57 | type ClusterPullSecretSpec struct { 58 | SecretRef *ObjectMeta `json:"secretRef,omitempty"` 59 | } 60 | 61 | //+kubebuilder:object:root=true 62 | 63 | // ClusterPullSecretList contains a list of ClusterPullSecret 64 | type ClusterPullSecretList struct { 65 | metav1.TypeMeta `json:",inline"` 66 | metav1.ListMeta `json:"metadata,omitempty"` 67 | Items []ClusterPullSecret `json:"items"` 68 | } 69 | 70 | func init() { 71 | SchemeBuilder.Register(&ClusterPullSecret{}, &ClusterPullSecretList{}) 72 | } 73 | -------------------------------------------------------------------------------- /api/v1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Package v1 contains API Schema definitions for the ops v1 API group 18 | // +kubebuilder:object:generate=true 19 | // +groupName=ops.alexellis.io 20 | package v1 21 | 22 | import ( 23 | "k8s.io/apimachinery/pkg/runtime/schema" 24 | "sigs.k8s.io/controller-runtime/pkg/scheme" 25 | ) 26 | 27 | var ( 28 | // GroupVersion is group version used to register these objects 29 | GroupVersion = schema.GroupVersion{Group: "ops.alexellis.io", Version: "v1"} 30 | 31 | // SchemeBuilder is used to add go types to the GroupVersionKind scheme 32 | SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion} 33 | 34 | // AddToScheme adds the types in this group-version to the given scheme. 35 | AddToScheme = SchemeBuilder.AddToScheme 36 | ) 37 | -------------------------------------------------------------------------------- /api/v1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | //go:build !ignore_autogenerated 2 | 3 | /* 4 | 5 | Copyright OpenFaaS Ltd 2023 6 | 7 | Licensed under the Apache License, Version 2.0 (the "License"); 8 | you may not use this file except in compliance with the License. 9 | You may obtain a copy of the License at 10 | 11 | http://www.apache.org/licenses/LICENSE-2.0 12 | 13 | Unless required by applicable law or agreed to in writing, software 14 | distributed under the License is distributed on an "AS IS" BASIS, 15 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | See the License for the specific language governing permissions and 17 | limitations under the License. 18 | */ 19 | 20 | // Code generated by controller-gen. DO NOT EDIT. 21 | 22 | package v1 23 | 24 | import ( 25 | runtime "k8s.io/apimachinery/pkg/runtime" 26 | ) 27 | 28 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 29 | func (in *ClusterPullSecret) DeepCopyInto(out *ClusterPullSecret) { 30 | *out = *in 31 | out.TypeMeta = in.TypeMeta 32 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 33 | in.Spec.DeepCopyInto(&out.Spec) 34 | out.Status = in.Status 35 | } 36 | 37 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPullSecret. 38 | func (in *ClusterPullSecret) DeepCopy() *ClusterPullSecret { 39 | if in == nil { 40 | return nil 41 | } 42 | out := new(ClusterPullSecret) 43 | in.DeepCopyInto(out) 44 | return out 45 | } 46 | 47 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 48 | func (in *ClusterPullSecret) DeepCopyObject() runtime.Object { 49 | if c := in.DeepCopy(); c != nil { 50 | return c 51 | } 52 | return nil 53 | } 54 | 55 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 56 | func (in *ClusterPullSecretList) DeepCopyInto(out *ClusterPullSecretList) { 57 | *out = *in 58 | out.TypeMeta = in.TypeMeta 59 | in.ListMeta.DeepCopyInto(&out.ListMeta) 60 | if in.Items != nil { 61 | in, out := &in.Items, &out.Items 62 | *out = make([]ClusterPullSecret, len(*in)) 63 | for i := range *in { 64 | (*in)[i].DeepCopyInto(&(*out)[i]) 65 | } 66 | } 67 | } 68 | 69 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPullSecretList. 70 | func (in *ClusterPullSecretList) DeepCopy() *ClusterPullSecretList { 71 | if in == nil { 72 | return nil 73 | } 74 | out := new(ClusterPullSecretList) 75 | in.DeepCopyInto(out) 76 | return out 77 | } 78 | 79 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 80 | func (in *ClusterPullSecretList) DeepCopyObject() runtime.Object { 81 | if c := in.DeepCopy(); c != nil { 82 | return c 83 | } 84 | return nil 85 | } 86 | 87 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 88 | func (in *ClusterPullSecretSpec) DeepCopyInto(out *ClusterPullSecretSpec) { 89 | *out = *in 90 | if in.SecretRef != nil { 91 | in, out := &in.SecretRef, &out.SecretRef 92 | *out = new(ObjectMeta) 93 | **out = **in 94 | } 95 | } 96 | 97 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPullSecretSpec. 98 | func (in *ClusterPullSecretSpec) DeepCopy() *ClusterPullSecretSpec { 99 | if in == nil { 100 | return nil 101 | } 102 | out := new(ClusterPullSecretSpec) 103 | in.DeepCopyInto(out) 104 | return out 105 | } 106 | 107 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 108 | func (in *ClusterPullSecretStatus) DeepCopyInto(out *ClusterPullSecretStatus) { 109 | *out = *in 110 | } 111 | 112 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterPullSecretStatus. 113 | func (in *ClusterPullSecretStatus) DeepCopy() *ClusterPullSecretStatus { 114 | if in == nil { 115 | return nil 116 | } 117 | out := new(ClusterPullSecretStatus) 118 | in.DeepCopyInto(out) 119 | return out 120 | } 121 | 122 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 123 | func (in *ObjectMeta) DeepCopyInto(out *ObjectMeta) { 124 | *out = *in 125 | } 126 | 127 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectMeta. 128 | func (in *ObjectMeta) DeepCopy() *ObjectMeta { 129 | if in == nil { 130 | return nil 131 | } 132 | out := new(ObjectMeta) 133 | in.DeepCopyInto(out) 134 | return out 135 | } 136 | -------------------------------------------------------------------------------- /config/controller/controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: registry-creds-controller 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: registry-creds-controller 12 | namespace: system 13 | labels: 14 | control-plane: registry-creds-controller 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: registry-creds-controller 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: registry-creds-controller 24 | spec: 25 | containers: 26 | - command: 27 | - /controller 28 | args: 29 | - --enable-leader-election 30 | image: ghcr.io/alexellis/registry-creds:0.3.2 31 | name: controller 32 | imagePullPolicy: IfNotPresent 33 | resources: 34 | limits: 35 | cpu: 100m 36 | memory: 128Mi 37 | requests: 38 | cpu: 100m 39 | memory: 45Mi 40 | terminationGracePeriodSeconds: 10 41 | -------------------------------------------------------------------------------- /config/controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - controller.yaml 3 | apiVersion: kustomize.config.k8s.io/v1beta1 4 | kind: Kustomization 5 | images: 6 | - name: controller 7 | newName: ghcr.io/alexellis/registry-creds 8 | newTag: 0.3.5 9 | -------------------------------------------------------------------------------- /config/crd/bases/ops.alexellis.io_clusterpullsecrets.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | controller-gen.kubebuilder.io/version: v0.13.0 7 | name: clusterpullsecrets.ops.alexellis.io 8 | spec: 9 | group: ops.alexellis.io 10 | names: 11 | kind: ClusterPullSecret 12 | listKind: ClusterPullSecretList 13 | plural: clusterpullsecrets 14 | singular: clusterpullsecret 15 | scope: Cluster 16 | versions: 17 | - additionalPrinterColumns: 18 | - jsonPath: .spec.secretRef.name 19 | name: SecretName 20 | type: string 21 | - jsonPath: .spec.secretRef.namespace 22 | name: SecretNamespace 23 | type: string 24 | name: v1 25 | schema: 26 | openAPIV3Schema: 27 | description: ClusterPullSecret is the Schema for the clusterpullsecrets API 28 | properties: 29 | apiVersion: 30 | description: 'APIVersion defines the versioned schema of this representation 31 | of an object. Servers should convert recognized schemas to the latest 32 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 33 | type: string 34 | kind: 35 | description: 'Kind is a string value representing the REST resource this 36 | object represents. Servers may infer this from the endpoint the client 37 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 38 | type: string 39 | metadata: 40 | type: object 41 | spec: 42 | description: ClusterPullSecretSpec defines the desired state of ClusterPullSecret 43 | properties: 44 | secretRef: 45 | description: ObjectMeta contains enough information to locate the 46 | referenced Kubernetes resource object in any namespace. 47 | properties: 48 | name: 49 | description: Name of the referent. 50 | type: string 51 | namespace: 52 | description: Namespace of the referent, when not specified it 53 | acts as LocalObjectReference. 54 | type: string 55 | required: 56 | - name 57 | type: object 58 | type: object 59 | status: 60 | description: ClusterPullSecretStatus defines the observed state of ClusterPullSecret 61 | type: object 62 | type: object 63 | served: true 64 | storage: true 65 | subresources: {} 66 | -------------------------------------------------------------------------------- /config/crd/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # This kustomization.yaml is not intended to be run by itself, 2 | # since it depends on service name and namespace that are out of this kustomize package. 3 | # It should be run by config/default 4 | resources: 5 | - bases/ops.alexellis.io_clusterpullsecrets.yaml 6 | # +kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | 10 | # the following config is for teaching kustomize how to do kustomization for CRDs. 11 | configurations: 12 | - kustomizeconfig.yaml 13 | -------------------------------------------------------------------------------- /config/crd/kustomizeconfig.yaml: -------------------------------------------------------------------------------- 1 | # This file is for teaching kustomize how to substitute name and namespace reference in CRD 2 | nameReference: 3 | - kind: Service 4 | version: v1 5 | fieldSpecs: 6 | - kind: CustomResourceDefinition 7 | group: apiextensions.k8s.io 8 | path: spec/conversion/webhookClientConfig/service/name 9 | 10 | namespace: 11 | - kind: CustomResourceDefinition 12 | group: apiextensions.k8s.io 13 | path: spec/conversion/webhookClientConfig/service/namespace 14 | create: false 15 | 16 | varReference: 17 | - path: metadata/annotations 18 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_clusterpullsecrets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 8 | name: clusterpullsecrets.ops.alexellis.io 9 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_clusterpullsecrets.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables conversion webhook for CRD 2 | # CRD conversion requires k8s 1.13 or later. 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | name: clusterpullsecrets.ops.alexellis.io 7 | spec: 8 | conversion: 9 | strategy: Webhook 10 | webhookClientConfig: 11 | # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, 12 | # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) 13 | caBundle: Cg== 14 | service: 15 | namespace: system 16 | name: webhook-service 17 | path: /convert 18 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | namespace: registry-creds-system 2 | namePrefix: registry-creds- 3 | 4 | apiVersion: kustomize.config.k8s.io/v1beta1 5 | kind: Kustomization 6 | resources: 7 | - ../crd 8 | - ../rbac 9 | - ../controller 10 | images: 11 | - name: ghcr.io/alexellis/registry-creds-controller 12 | newTag: 0.3.5 13 | - name: ghcr.io/ghcr.io/alexellis/registry-creds 14 | newTag: latest 15 | -------------------------------------------------------------------------------- /config/default/manager_auth_proxy_patch.yaml: -------------------------------------------------------------------------------- 1 | # This patch inject a sidecar container which is a HTTP proxy for the 2 | # controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews. 3 | apiVersion: apps/v1 4 | kind: Deployment 5 | metadata: 6 | name: registry-creds-controller 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.5.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/rbac/clusterpullsecret_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit clusterpullsecrets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: clusterpullsecret-editor-role 6 | rules: 7 | - apiGroups: 8 | - ops.alexellis.io 9 | resources: 10 | - clusterpullsecrets 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - ops.alexellis.io 21 | resources: 22 | - clusterpullsecrets/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/clusterpullsecret_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view clusterpullsecrets. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: clusterpullsecret-viewer-role 6 | rules: 7 | - apiGroups: 8 | - ops.alexellis.io 9 | resources: 10 | - clusterpullsecrets 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - ops.alexellis.io 17 | resources: 18 | - clusterpullsecrets/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: Role 3 | metadata: 4 | name: leader-election-role 5 | rules: 6 | - apiGroups: 7 | - "" 8 | resources: 9 | - configmaps 10 | verbs: 11 | - get 12 | - create 13 | - update 14 | - patch 15 | - apiGroups: 16 | - coordination.k8s.io 17 | verbs: 18 | - get 19 | - create 20 | - update 21 | - patch 22 | resources: 23 | - leases 24 | -------------------------------------------------------------------------------- /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/role.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: registry-creds-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - namespaces 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - "" 17 | resources: 18 | - namespaces/status 19 | verbs: 20 | - get 21 | - apiGroups: 22 | - "" 23 | resources: 24 | - secrets 25 | verbs: 26 | - create 27 | - delete 28 | - get 29 | - list 30 | - patch 31 | - update 32 | - watch 33 | - apiGroups: 34 | - "" 35 | resources: 36 | - secrets/status 37 | verbs: 38 | - get 39 | - patch 40 | - update 41 | - apiGroups: 42 | - "" 43 | resources: 44 | - serviceaccounts 45 | verbs: 46 | - create 47 | - delete 48 | - get 49 | - list 50 | - patch 51 | - update 52 | - watch 53 | - apiGroups: 54 | - "" 55 | resources: 56 | - serviceaccounts/status 57 | verbs: 58 | - get 59 | - patch 60 | - update 61 | - apiGroups: 62 | - ops.alexellis.io 63 | resources: 64 | - clusterpullsecrets 65 | verbs: 66 | - get 67 | - list 68 | - watch 69 | - apiGroups: 70 | - ops.alexellis.io 71 | resources: 72 | - clusterpullsecrets/status 73 | verbs: 74 | - get 75 | - patch 76 | - update 77 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: registry-creds-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: registry-creds-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/samples/dockerhub.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: ops.alexellis.io/v1 2 | kind: ClusterPullSecret 3 | metadata: 4 | name: dockerhub 5 | spec: 6 | # See README.md for how to create the secret below 7 | secretRef: 8 | name: registry-creds-secret 9 | namespace: kube-system 10 | -------------------------------------------------------------------------------- /controllers/clusterpullsecret_controller.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | ctrl "sigs.k8s.io/controller-runtime" 8 | 9 | "github.com/go-logr/logr" 10 | "k8s.io/apimachinery/pkg/runtime" 11 | 12 | "sigs.k8s.io/controller-runtime/pkg/client" 13 | 14 | opsv1 "alexellis/registry-creds/api/v1" 15 | v1 "alexellis/registry-creds/api/v1" 16 | 17 | corev1 "k8s.io/api/core/v1" 18 | ) 19 | 20 | // ClusterPullSecretReconciler reconciles a ClusterPullSecret 21 | // object to the default ServiceAccount of each namespace 22 | type ClusterPullSecretReconciler struct { 23 | client.Client 24 | Log logr.Logger 25 | Scheme *runtime.Scheme 26 | SecretReconciler *SecretReconciler 27 | } 28 | 29 | // +kubebuilder:rbac:groups=ops.alexellis.io,resources=clusterpullsecrets,verbs=get;list;watch 30 | // +kubebuilder:rbac:groups=ops.alexellis.io,resources=clusterpullsecrets/status,verbs=get;update;patch 31 | 32 | // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete 33 | // +kubebuilder:rbac:groups=core,resources=secrets/status,verbs=get;update;patch 34 | 35 | // +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;update;patch 36 | // +kubebuilder:rbac:groups=core,resources=serviceaccounts/status,verbs=get;update;patch 37 | 38 | // Reconcile applies a number of ClusterPullSecrets to the default ServiceAccount 39 | // within various valid namespaces. Namespaces can be ignored as required. 40 | func (r *ClusterPullSecretReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 41 | _ = r.Log.WithValues("clusterpullsecret", req.NamespacedName) 42 | 43 | var pullSecret v1.ClusterPullSecret 44 | if err := r.Get(ctx, req.NamespacedName, &pullSecret); err != nil { 45 | r.Log.Info(fmt.Sprintf("unable to fetch pullSecret %s, error: %s", req.NamespacedName, err)) 46 | } else { 47 | 48 | r.Log.Info(fmt.Sprintf("Found: %s\n", pullSecret.Name)) 49 | 50 | namespaces := &corev1.NamespaceList{} 51 | r.Client.List(ctx, namespaces) 52 | 53 | r.Log.V(10).Info(fmt.Sprintf("Found %d namespaces", len(namespaces.Items))) 54 | 55 | for _, namespace := range namespaces.Items { 56 | namespaceName := namespace.Name 57 | err := r.SecretReconciler.Reconcile(pullSecret, namespaceName) 58 | if err != nil { 59 | r.Log.Info(fmt.Sprintf("Found error: %s", err.Error())) 60 | } 61 | } 62 | 63 | } 64 | 65 | return ctrl.Result{}, nil 66 | } 67 | 68 | func (r *ClusterPullSecretReconciler) SetupWithManager(mgr ctrl.Manager) error { 69 | return ctrl.NewControllerManagedBy(mgr). 70 | For(&opsv1.ClusterPullSecret{}). 71 | Complete(r) 72 | } 73 | -------------------------------------------------------------------------------- /controllers/namespace_watcher.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | opsv1 "alexellis/registry-creds/api/v1" 8 | 9 | ctrl "sigs.k8s.io/controller-runtime" 10 | 11 | "github.com/go-logr/logr" 12 | "k8s.io/apimachinery/pkg/api/errors" 13 | "k8s.io/apimachinery/pkg/runtime" 14 | 15 | "sigs.k8s.io/controller-runtime/pkg/client" 16 | 17 | corev1 "k8s.io/api/core/v1" 18 | ) 19 | 20 | // NamespaceWatcher watches namespaces for changes to 21 | // trigger ClusterPullSecret reconciliation. 22 | type NamespaceWatcher struct { 23 | client.Client 24 | Log logr.Logger 25 | Scheme *runtime.Scheme 26 | SecretReconciler *SecretReconciler 27 | } 28 | 29 | // +kubebuilder:rbac:groups=core,resources=namespaces,verbs=get;list;watch 30 | // +kubebuilder:rbac:groups=core,resources=namespaces/status,verbs=get 31 | 32 | func (r *NamespaceWatcher) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 33 | r.Log.WithValues("namespace", req.NamespacedName) 34 | 35 | var namespace corev1.Namespace 36 | if err := r.Get(ctx, req.NamespacedName, &namespace); err != nil { 37 | r.Log.Info(fmt.Sprintf("unable to fetch pullSecret %s, error: %s", req.NamespacedName, err)) 38 | return ctrl.Result{}, nil 39 | } 40 | 41 | r.Log.V(10).Info(fmt.Sprintf("detected a change in namespace: %s", namespace.Name)) 42 | 43 | pullSecretList := &opsv1.ClusterPullSecretList{} 44 | err := r.Client.List(ctx, pullSecretList) 45 | if err != nil { 46 | r.Log.Info(fmt.Sprintf("unable to list ClusterPullSecrets, %s", err.Error())) 47 | return ctrl.Result{}, nil 48 | } 49 | 50 | for _, pullSecret := range pullSecretList.Items { 51 | err := r.SecretReconciler.Reconcile(pullSecret, namespace.Name) 52 | if err != nil { 53 | if !errors.IsConflict(err) { 54 | r.Log.Info(fmt.Sprintf("error reconciling namespace: %s with cluster pull secret: %s, error: %s", 55 | namespace.Name, 56 | pullSecret.Name, 57 | err.Error())) 58 | } 59 | } 60 | } 61 | 62 | return ctrl.Result{}, nil 63 | } 64 | 65 | func (r *NamespaceWatcher) SetupWithManager(mgr ctrl.Manager) error { 66 | return ctrl.NewControllerManagedBy(mgr). 67 | For(&corev1.Namespace{}). 68 | Complete(r) 69 | } 70 | -------------------------------------------------------------------------------- /controllers/secret_reconciler.go: -------------------------------------------------------------------------------- 1 | package controllers 2 | 3 | import ( 4 | v1 "alexellis/registry-creds/api/v1" 5 | "context" 6 | "fmt" 7 | "strings" 8 | 9 | ctrl "sigs.k8s.io/controller-runtime" 10 | 11 | "github.com/go-logr/logr" 12 | "github.com/pkg/errors" 13 | corev1 "k8s.io/api/core/v1" 14 | apierrors "k8s.io/apimachinery/pkg/api/errors" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/apimachinery/pkg/runtime" 17 | "sigs.k8s.io/controller-runtime/pkg/client" 18 | ) 19 | 20 | // SecretReconciler adds a secret to the default 21 | // ServiceAccount in each namespace, unless the namespace 22 | // has an ignore annotation 23 | type SecretReconciler struct { 24 | client.Client 25 | Log logr.Logger 26 | Scheme *runtime.Scheme 27 | } 28 | 29 | // secretSuffix was: -registrycreds 30 | const secretSuffix = "" 31 | 32 | const ignoreAnnotation = "alexellis.io/registry-creds.ignore" 33 | 34 | func ignoredNamespace(ns *corev1.Namespace) bool { 35 | return ns.Annotations[ignoreAnnotation] == "1" || strings.ToLower(ns.Annotations[ignoreAnnotation]) == "true" 36 | } 37 | 38 | // Reconcile applies a number of ClusterPullSecrets to ServiceAccounts within 39 | // various valid namespaces. Namespaces can be ignored as required. 40 | func (r *SecretReconciler) Reconcile(clusterPullSecret v1.ClusterPullSecret, ns string) error { 41 | ctx := context.Background() 42 | 43 | targetNS := &corev1.Namespace{} 44 | if err := r.Get(ctx, client.ObjectKey{Name: ns}, targetNS); err != nil { 45 | wrappedErr := errors.Wrapf(err, "unable to fetch namespace: %s", ns) 46 | r.Log.Info(wrappedErr.Error()) 47 | return wrappedErr 48 | } 49 | 50 | if ignoredNamespace(targetNS) { 51 | r.Log.Info(fmt.Sprintf("ignoring namespace %s due to annotation: %s ", ns, ignoreAnnotation)) 52 | return nil 53 | } 54 | 55 | r.Log.V(10).Info(fmt.Sprintf("Getting SA for: %s", ns)) 56 | 57 | if clusterPullSecret.Spec.SecretRef == nil || 58 | clusterPullSecret.Spec.SecretRef.Name == "" || 59 | clusterPullSecret.Spec.SecretRef.Namespace == "" { 60 | return fmt.Errorf("no valid secretRef found on ClusterPullSecret: %s.%s", 61 | clusterPullSecret.Name, 62 | clusterPullSecret.Namespace) 63 | } 64 | 65 | pullSecret := &corev1.Secret{} 66 | if err := r.Get(ctx, 67 | client.ObjectKey{ 68 | Name: clusterPullSecret.Spec.SecretRef.Name, 69 | Namespace: clusterPullSecret.Spec.SecretRef.Namespace}, 70 | pullSecret); err != nil { 71 | wrappedErr := errors.Wrapf(err, "unable to fetch seedSecret %s.%s", clusterPullSecret.Spec.SecretRef.Name, clusterPullSecret.Spec.SecretRef.Namespace) 72 | r.Log.Info(fmt.Sprintf("%s", wrappedErr.Error())) 73 | return wrappedErr 74 | } 75 | 76 | err := r.createSecret(clusterPullSecret, pullSecret, ns) 77 | if err != nil { 78 | r.Log.Info(err.Error()) 79 | return err 80 | } 81 | 82 | SAs, err := r.listWithin(ns) 83 | if err != nil { 84 | wrappedErr := errors.Wrapf(err, "failed to list service accounts in %s namespace", ns) 85 | r.Log.Info(wrappedErr.Error()) 86 | return wrappedErr 87 | } 88 | 89 | for _, sa := range SAs.Items { 90 | err = r.appendSecretToSA(clusterPullSecret, pullSecret, ns, sa.Name) 91 | if err != nil { 92 | r.Log.Info(err.Error()) 93 | return err 94 | } 95 | } 96 | 97 | return nil 98 | } 99 | 100 | func (r *SecretReconciler) listWithin(ns string) (*corev1.ServiceAccountList, error) { 101 | ctx := context.Background() 102 | SAs := &corev1.ServiceAccountList{} 103 | err := r.Client.List(ctx, SAs, client.InNamespace(ns)) 104 | if err != nil { 105 | return nil, err 106 | } 107 | return SAs, nil 108 | } 109 | 110 | func (r *SecretReconciler) createSecret(clusterPullSecret v1.ClusterPullSecret, pullSecret *corev1.Secret, ns string) error { 111 | ctx := context.Background() 112 | 113 | secretKey := clusterPullSecret.Name + secretSuffix 114 | 115 | nsSecret := &corev1.Secret{} 116 | err := r.Client.Get(ctx, client.ObjectKey{Name: secretKey, Namespace: ns}, nsSecret) 117 | if err != nil { 118 | notFound := apierrors.IsNotFound(err) 119 | if !notFound { 120 | return errors.Wrap(err, "unexpected error checking for the namespaced pull secret") 121 | } 122 | 123 | nsSecret = &corev1.Secret{ 124 | ObjectMeta: metav1.ObjectMeta{ 125 | Name: secretKey, 126 | Namespace: ns, 127 | }, 128 | Data: pullSecret.Data, 129 | Type: corev1.SecretTypeDockerConfigJson, 130 | } 131 | 132 | err = ctrl.SetControllerReference(&clusterPullSecret, nsSecret, r.Scheme) 133 | if err != nil { 134 | r.Log.Info(fmt.Sprintf("can't create owner reference: %s.%s, %s", secretKey, ns, err.Error())) 135 | } 136 | 137 | err = r.Client.Create(ctx, nsSecret) 138 | if err != nil { 139 | r.Log.Info(fmt.Sprintf("can't create secret: %s.%s, %s", secretKey, ns, err.Error())) 140 | return err 141 | } 142 | r.Log.Info(fmt.Sprintf("created secret: %s.%s", secretKey, ns)) 143 | } 144 | 145 | return nil 146 | } 147 | 148 | func (r *SecretReconciler) appendSecretToSA(clusterPullSecret v1.ClusterPullSecret, pullSecret *corev1.Secret, ns, serviceAccountName string) error { 149 | ctx := context.Background() 150 | 151 | secretKey := clusterPullSecret.Name + secretSuffix 152 | 153 | sa := &corev1.ServiceAccount{} 154 | err := r.Client.Get(ctx, client.ObjectKey{Name: serviceAccountName, Namespace: ns}, sa) 155 | if err != nil { 156 | if !apierrors.IsConflict(err) { 157 | r.Log.Info(fmt.Sprintf("error getting SA in namespace: %s, %s", ns, err.Error())) 158 | wrappedErr := fmt.Errorf("unable to append pull secret to service account: %s", err) 159 | r.Log.Info(wrappedErr.Error()) 160 | return wrappedErr 161 | } 162 | return err 163 | } 164 | 165 | r.Log.V(10).Info(fmt.Sprintf("Pull secrets: %v", sa.ImagePullSecrets)) 166 | 167 | hasSecret := hasImagePullSecret(sa, secretKey) 168 | 169 | if !hasSecret { 170 | sa.ImagePullSecrets = append(sa.ImagePullSecrets, corev1.LocalObjectReference{ 171 | Name: secretKey, 172 | }) 173 | 174 | err = r.Update(ctx, sa.DeepCopy()) 175 | if err != nil { 176 | if !apierrors.IsConflict(err) { 177 | 178 | wrappedErr := fmt.Errorf("unable to append pull secret to service account: %s", err) 179 | r.Log.Info(wrappedErr.Error()) 180 | return err 181 | } 182 | return nil 183 | } 184 | } 185 | 186 | return nil 187 | } 188 | 189 | func hasImagePullSecret(sa *corev1.ServiceAccount, secretKey string) bool { 190 | found := false 191 | if len(sa.ImagePullSecrets) > 0 { 192 | for _, s := range sa.ImagePullSecrets { 193 | if s.Name == secretKey { 194 | found = true 195 | break 196 | } 197 | } 198 | } 199 | return found 200 | } 201 | -------------------------------------------------------------------------------- /controllers/serviceaccount_watcher.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package controllers 18 | 19 | import ( 20 | v1 "alexellis/registry-creds/api/v1" 21 | "context" 22 | "fmt" 23 | 24 | "github.com/pkg/errors" 25 | kerrors "k8s.io/apimachinery/pkg/api/errors" 26 | 27 | "github.com/go-logr/logr" 28 | corev1 "k8s.io/api/core/v1" 29 | "k8s.io/apimachinery/pkg/runtime" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/client" 32 | ) 33 | 34 | // ServiceAccountWatcher reconciles a ServiceAccount object 35 | type ServiceAccountWatcher struct { 36 | client.Client 37 | Log logr.Logger 38 | Scheme *runtime.Scheme 39 | } 40 | 41 | // +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete 42 | // +kubebuilder:rbac:groups=core,resources=serviceaccounts/status,verbs=get;update;patch 43 | 44 | func (r *ServiceAccountWatcher) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 45 | r.Log.WithValues("serviceaccount", req.NamespacedName) 46 | 47 | var sa corev1.ServiceAccount 48 | if err := r.Get(ctx, req.NamespacedName, &sa); err != nil { 49 | r.Log.Info(fmt.Sprintf("%s", errors.Wrap(err, "unable to fetch serviceaccount"))) 50 | return ctrl.Result{}, nil 51 | } 52 | 53 | r.Log.V(10).Info(fmt.Sprintf("detected a change in serviceaccount: %s", sa.Name)) 54 | 55 | pullSecretList := &v1.ClusterPullSecretList{} 56 | err := r.Client.List(ctx, pullSecretList) 57 | if err != nil { 58 | r.Log.Info(fmt.Sprintf("unable to list ClusterPullSecrets, %s", err.Error())) 59 | return ctrl.Result{}, nil 60 | } 61 | 62 | for _, clusterPullSecret := range pullSecretList.Items { 63 | err = r.appendSecretToSA(clusterPullSecret, sa.Namespace, sa.Name) 64 | if err != nil { 65 | r.Log.Info(err.Error()) 66 | return ctrl.Result{}, nil 67 | } 68 | } 69 | 70 | return ctrl.Result{}, nil 71 | } 72 | 73 | func (r *ServiceAccountWatcher) appendSecretToSA(clusterPullSecret v1.ClusterPullSecret, ns, serviceAccountName string) error { 74 | ctx := context.Background() 75 | 76 | secretKey := clusterPullSecret.Name + secretSuffix 77 | 78 | sa := &corev1.ServiceAccount{} 79 | if err := r.Client.Get(ctx, client.ObjectKey{Name: serviceAccountName, Namespace: ns}, sa); err != nil { 80 | if !kerrors.IsConflict(err) { 81 | 82 | r.Log.Info(fmt.Sprintf("error getting SA in namespace: %s, %s", ns, err.Error())) 83 | wrappedErr := fmt.Errorf("unable to append pull secret to service account: %s", err) 84 | r.Log.Info(wrappedErr.Error()) 85 | return wrappedErr 86 | } 87 | return nil 88 | 89 | } 90 | 91 | r.Log.V(10).Info(fmt.Sprintf("Pull secrets: %v", sa.ImagePullSecrets)) 92 | 93 | hasSecret := hasImagePullSecret(sa, secretKey) 94 | 95 | if !hasSecret { 96 | sa.ImagePullSecrets = append(sa.ImagePullSecrets, corev1.LocalObjectReference{ 97 | Name: secretKey, 98 | }) 99 | 100 | if err := r.Update(ctx, sa.DeepCopy()); err != nil { 101 | if !kerrors.IsConflict(err) { 102 | 103 | wrappedErr := fmt.Errorf("unable to append pull secret to service account: %s", err) 104 | r.Log.Info(wrappedErr.Error()) 105 | return err 106 | } 107 | return nil 108 | } 109 | } 110 | 111 | return nil 112 | } 113 | 114 | func (r *ServiceAccountWatcher) SetupWithManager(mgr ctrl.Manager) error { 115 | return ctrl.NewControllerManagedBy(mgr). 116 | For(&corev1.ServiceAccount{}). 117 | Complete(r) 118 | } 119 | -------------------------------------------------------------------------------- /diagram.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexellis/registry-creds/b8bece0fa04247b22f7f8bfb14e509794416a88d/diagram.jpg -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module alexellis/registry-creds 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/go-logr/logr v1.2.4 7 | github.com/pkg/errors v0.9.1 8 | k8s.io/api v0.28.3 9 | k8s.io/apimachinery v0.28.3 10 | k8s.io/client-go v0.28.3 11 | sigs.k8s.io/controller-runtime v0.16.3 12 | ) 13 | 14 | require ( 15 | github.com/beorn7/perks v1.0.1 // indirect 16 | github.com/cespare/xxhash/v2 v2.2.0 // indirect 17 | github.com/davecgh/go-spew v1.1.1 // indirect 18 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 19 | github.com/evanphx/json-patch/v5 v5.6.0 // indirect 20 | github.com/fatih/color v1.15.0 // indirect 21 | github.com/fsnotify/fsnotify v1.6.0 // indirect 22 | github.com/go-logr/zapr v1.2.4 // indirect 23 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 24 | github.com/go-openapi/jsonreference v0.20.2 // indirect 25 | github.com/go-openapi/swag v0.22.3 // indirect 26 | github.com/gobuffalo/flect v1.0.2 // indirect 27 | github.com/gogo/protobuf v1.3.2 // indirect 28 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect 29 | github.com/golang/protobuf v1.5.3 // indirect 30 | github.com/google/gnostic-models v0.6.8 // indirect 31 | github.com/google/go-cmp v0.5.9 // indirect 32 | github.com/google/gofuzz v1.2.0 // indirect 33 | github.com/google/uuid v1.3.0 // indirect 34 | github.com/imdario/mergo v0.3.6 // indirect 35 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 36 | github.com/josharian/intern v1.0.0 // indirect 37 | github.com/json-iterator/go v1.1.12 // indirect 38 | github.com/mailru/easyjson v0.7.7 // indirect 39 | github.com/mattn/go-colorable v0.1.13 // indirect 40 | github.com/mattn/go-isatty v0.0.17 // indirect 41 | github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 43 | github.com/modern-go/reflect2 v1.0.2 // indirect 44 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 45 | github.com/prometheus/client_golang v1.16.0 // indirect 46 | github.com/prometheus/client_model v0.4.0 // indirect 47 | github.com/prometheus/common v0.44.0 // indirect 48 | github.com/prometheus/procfs v0.10.1 // indirect 49 | github.com/spf13/cobra v1.7.0 // indirect 50 | github.com/spf13/pflag v1.0.5 // indirect 51 | go.uber.org/multierr v1.11.0 // indirect 52 | go.uber.org/zap v1.25.0 // indirect 53 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect 54 | golang.org/x/mod v0.12.0 // indirect 55 | golang.org/x/net v0.17.0 // indirect 56 | golang.org/x/oauth2 v0.8.0 // indirect 57 | golang.org/x/sys v0.13.0 // indirect 58 | golang.org/x/term v0.13.0 // indirect 59 | golang.org/x/text v0.13.0 // indirect 60 | golang.org/x/time v0.3.0 // indirect 61 | golang.org/x/tools v0.12.0 // indirect 62 | gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 63 | google.golang.org/appengine v1.6.7 // indirect 64 | google.golang.org/protobuf v1.30.0 // indirect 65 | gopkg.in/inf.v0 v0.9.1 // indirect 66 | gopkg.in/yaml.v2 v2.4.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | k8s.io/apiextensions-apiserver v0.28.3 // indirect 69 | k8s.io/component-base v0.28.3 // indirect 70 | k8s.io/klog/v2 v2.100.1 // indirect 71 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 // indirect 72 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 // indirect 73 | sigs.k8s.io/controller-tools v0.13.0 // indirect 74 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 75 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect 76 | sigs.k8s.io/yaml v1.3.0 // indirect 77 | ) 78 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 2 | github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= 6 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 11 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 12 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 13 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 14 | github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= 15 | github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= 16 | github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= 17 | github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= 18 | github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= 19 | github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= 20 | github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= 21 | github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 22 | github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= 23 | github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 24 | github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= 25 | github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= 26 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 27 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 28 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 29 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 30 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 31 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 32 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 33 | github.com/gobuffalo/flect v1.0.2 h1:eqjPGSo2WmjgY2XlpGwo2NXgL3RucAKo4k4qQMNA5sA= 34 | github.com/gobuffalo/flect v1.0.2/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= 35 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 36 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 37 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= 38 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 39 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 40 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 41 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 42 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 43 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 44 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 45 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 46 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 47 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 48 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 49 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 50 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 51 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 52 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 53 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 54 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 55 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 56 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 57 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 58 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 59 | github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= 60 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 61 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 62 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 63 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 64 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 65 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 66 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 67 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 68 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 69 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 70 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 71 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 72 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 73 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 74 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 75 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 76 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 77 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 78 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= 79 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 80 | github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= 81 | github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= 82 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 83 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 84 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 85 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 86 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 87 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 88 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 89 | github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= 90 | github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= 91 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 92 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 93 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 94 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 95 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 96 | github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= 97 | github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= 98 | github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= 99 | github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= 100 | github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= 101 | github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= 102 | github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= 103 | github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= 104 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 105 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 106 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 107 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 108 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 109 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 110 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 111 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 112 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 113 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 114 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 115 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 116 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 117 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 118 | github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= 119 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 120 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 121 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 122 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 123 | go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 124 | go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= 125 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 126 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 127 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 128 | go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= 129 | go.uber.org/zap v1.25.0 h1:4Hvk6GtkucQ790dqmj7l1eEnRdKm3k3ZUrUMS2d5+5c= 130 | go.uber.org/zap v1.25.0/go.mod h1:JIAUzQIH94IC4fOJQm7gMmBJP5k7wQfdcnYdPoEXJYk= 131 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 132 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 133 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 134 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= 135 | golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= 136 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 137 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 138 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 139 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 140 | golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= 141 | golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 142 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 143 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 144 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 145 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 146 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 147 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 148 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 149 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 150 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 151 | golang.org/x/oauth2 v0.8.0 h1:6dkIjl3j3LtZ/O3sTgZTMsLKSftL/B8Zgq4huOIIUu8= 152 | golang.org/x/oauth2 v0.8.0/go.mod h1:yr7u4HXZRm1R1kBWqr/xKNqewf0plRYoB7sla+BCIXE= 153 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 154 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 155 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 156 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 157 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 158 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 159 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 160 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 161 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 162 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 163 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 164 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 165 | golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 166 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 167 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 168 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 169 | golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= 170 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 171 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 172 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 173 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 174 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 175 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 176 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 177 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 178 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 179 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 180 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 181 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 182 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 183 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 184 | golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= 185 | golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= 186 | golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= 187 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 188 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 189 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 190 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 191 | gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 192 | gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 193 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 194 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 195 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 196 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 197 | google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= 198 | google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 199 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 200 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 201 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 202 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 203 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 204 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 205 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 206 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 207 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 208 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 209 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 210 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 211 | k8s.io/api v0.28.3 h1:Gj1HtbSdB4P08C8rs9AR94MfSGpRhJgsS+GF9V26xMM= 212 | k8s.io/api v0.28.3/go.mod h1:MRCV/jr1dW87/qJnZ57U5Pak65LGmQVkKTzf3AtKFHc= 213 | k8s.io/apiextensions-apiserver v0.28.3 h1:Od7DEnhXHnHPZG+W9I97/fSQkVpVPQx2diy+2EtmY08= 214 | k8s.io/apiextensions-apiserver v0.28.3/go.mod h1:NE1XJZ4On0hS11aWWJUTNkmVB03j9LM7gJSisbRt8Lc= 215 | k8s.io/apimachinery v0.28.3 h1:B1wYx8txOaCQG0HmYF6nbpU8dg6HvA06x5tEffvOe7A= 216 | k8s.io/apimachinery v0.28.3/go.mod h1:uQTKmIqs+rAYaq+DFaoD2X7pcjLOqbQX2AOiO0nIpb8= 217 | k8s.io/client-go v0.28.3 h1:2OqNb72ZuTZPKCl+4gTKvqao0AMOl9f3o2ijbAj3LI4= 218 | k8s.io/client-go v0.28.3/go.mod h1:LTykbBp9gsA7SwqirlCXBWtK0guzfhpoW4qSm7i9dxo= 219 | k8s.io/component-base v0.28.3 h1:rDy68eHKxq/80RiMb2Ld/tbH8uAE75JdCqJyi6lXMzI= 220 | k8s.io/component-base v0.28.3/go.mod h1:fDJ6vpVNSk6cRo5wmDa6eKIG7UlIQkaFmZN2fYgIUD8= 221 | k8s.io/klog/v2 v2.100.1 h1:7WCHKK6K8fNhTqfBhISHQ97KrnJNFZMcQvKp7gP/tmg= 222 | k8s.io/klog/v2 v2.100.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= 223 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9 h1:LyMgNKD2P8Wn1iAwQU5OhxCKlKJy0sHc+PcDwFB24dQ= 224 | k8s.io/kube-openapi v0.0.0-20230717233707-2695361300d9/go.mod h1:wZK2AVp1uHCp4VamDVgBP2COHZjqD1T68Rf0CM3YjSM= 225 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2 h1:qY1Ad8PODbnymg2pRbkyMT/ylpTrCM8P2RJ0yroCyIk= 226 | k8s.io/utils v0.0.0-20230406110748-d93618cff8a2/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 227 | sigs.k8s.io/controller-runtime v0.16.3 h1:2TuvuokmfXvDUamSx1SuAOO3eTyye+47mJCigwG62c4= 228 | sigs.k8s.io/controller-runtime v0.16.3/go.mod h1:j7bialYoSn142nv9sCOJmQgDXQXxnroFU4VnX/brVJ0= 229 | sigs.k8s.io/controller-tools v0.13.0 h1:NfrvuZ4bxyolhDBt/rCZhDnx3M2hzlhgo5n3Iv2RykI= 230 | sigs.k8s.io/controller-tools v0.13.0/go.mod h1:5vw3En2NazbejQGCeWKRrE7q4P+CW8/klfVqP8QZkgA= 231 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 232 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 233 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= 234 | sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= 235 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 236 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 237 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Copyright OpenFaaS Ltd 2023 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 4 | Licensed under the Apache License, Release 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "os" 23 | 24 | "k8s.io/apimachinery/pkg/runtime" 25 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 26 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 29 | "sigs.k8s.io/controller-runtime/pkg/metrics/server" 30 | 31 | corev1 "k8s.io/api/core/v1" 32 | 33 | opsv1 "alexellis/registry-creds/api/v1" 34 | "alexellis/registry-creds/controllers" 35 | // +kubebuilder:scaffold:imports 36 | ) 37 | 38 | var ( 39 | scheme = runtime.NewScheme() 40 | setupLog = ctrl.Log.WithName("setup") 41 | Release string 42 | SHA string 43 | ) 44 | 45 | func init() { 46 | _ = clientgoscheme.AddToScheme(scheme) 47 | 48 | _ = opsv1.AddToScheme(scheme) 49 | _ = corev1.AddToScheme(scheme) 50 | // +kubebuilder:scaffold:scheme 51 | } 52 | 53 | func main() { 54 | var metricsAddr string 55 | var enableLeaderElection bool 56 | flag.StringVar(&metricsAddr, "metrics-addr", ":9443", "The address the metric endpoint binds to.") 57 | flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, 58 | "Enable leader election for controller manager. "+ 59 | "Enabling this will ensure there is only one active controller manager.") 60 | flag.Parse() 61 | 62 | z := zap.New(zap.UseDevMode(true)).V(2) 63 | ctrl.SetLogger(z) 64 | 65 | fmt.Printf("registry-creds - Copyright Alex Ellis, OpenFaaS Ltd 2024\n\n") 66 | 67 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 68 | Scheme: scheme, 69 | Metrics: server.Options{ 70 | 71 | BindAddress: metricsAddr, 72 | }, 73 | LeaderElection: enableLeaderElection, 74 | LeaderElectionID: "8bdecb1a.alexellis.io", 75 | }) 76 | if err != nil { 77 | setupLog.Error(err, "unable to start manager") 78 | os.Exit(1) 79 | } 80 | 81 | secretReconciler := &controllers.SecretReconciler{ 82 | Client: mgr.GetClient(), 83 | Log: ctrl.Log.WithName("controllers").WithName("ClusterPullSecret"), 84 | Scheme: mgr.GetScheme(), 85 | } 86 | 87 | if err = (&controllers.ClusterPullSecretReconciler{ 88 | Client: mgr.GetClient(), 89 | Log: ctrl.Log.WithName("controllers").WithName("ClusterPullSecret"), 90 | Scheme: mgr.GetScheme(), 91 | SecretReconciler: secretReconciler, 92 | }).SetupWithManager(mgr); err != nil { 93 | setupLog.Error(err, "unable to create controller", "controller", "ClusterPullSecret") 94 | os.Exit(1) 95 | } 96 | if err = (&controllers.ServiceAccountWatcher{ 97 | Client: mgr.GetClient(), 98 | Log: ctrl.Log.WithName("controllers").WithName("ServiceAccount"), 99 | Scheme: mgr.GetScheme(), 100 | }).SetupWithManager(mgr); err != nil { 101 | setupLog.Error(err, "unable to create controller", "controller", "ServiceAccount") 102 | os.Exit(1) 103 | } 104 | 105 | // +kubebuilder:scaffold:builder 106 | if err = (&controllers.NamespaceWatcher{ 107 | Client: mgr.GetClient(), 108 | Log: ctrl.Log.WithName("controllers").WithName("Namespace"), 109 | Scheme: mgr.GetScheme(), 110 | SecretReconciler: secretReconciler, 111 | }).SetupWithManager(mgr); err != nil { 112 | setupLog.Error(err, "unable to create watcher", "watcher", "Namespace") 113 | os.Exit(1) 114 | } 115 | if err = (&controllers.ServiceAccountWatcher{ 116 | Client: mgr.GetClient(), 117 | Log: ctrl.Log.WithName("controllers").WithName("ServiceAccount"), 118 | Scheme: mgr.GetScheme(), 119 | }).SetupWithManager(mgr); err != nil { 120 | setupLog.Error(err, "unable to create controller", "controller", "ServiceAccount") 121 | os.Exit(1) 122 | } 123 | 124 | // +kubebuilder:scaffold:builder 125 | setupLog.Info("Starting manager", "release", Release, "sha", SHA) 126 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 127 | setupLog.Error(err, "problem running manager") 128 | os.Exit(1) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /manifest.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: registry-creds-controller 6 | name: registry-creds-system 7 | --- 8 | apiVersion: apiextensions.k8s.io/v1 9 | kind: CustomResourceDefinition 10 | metadata: 11 | annotations: 12 | controller-gen.kubebuilder.io/version: v0.13.0 13 | name: clusterpullsecrets.ops.alexellis.io 14 | spec: 15 | group: ops.alexellis.io 16 | names: 17 | kind: ClusterPullSecret 18 | listKind: ClusterPullSecretList 19 | plural: clusterpullsecrets 20 | singular: clusterpullsecret 21 | scope: Cluster 22 | versions: 23 | - additionalPrinterColumns: 24 | - jsonPath: .spec.secretRef.name 25 | name: SecretName 26 | type: string 27 | - jsonPath: .spec.secretRef.namespace 28 | name: SecretNamespace 29 | type: string 30 | name: v1 31 | schema: 32 | openAPIV3Schema: 33 | description: ClusterPullSecret is the Schema for the clusterpullsecrets API 34 | properties: 35 | apiVersion: 36 | description: 'APIVersion defines the versioned schema of this representation 37 | of an object. Servers should convert recognized schemas to the latest 38 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 39 | type: string 40 | kind: 41 | description: 'Kind is a string value representing the REST resource this 42 | object represents. Servers may infer this from the endpoint the client 43 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 44 | type: string 45 | metadata: 46 | type: object 47 | spec: 48 | description: ClusterPullSecretSpec defines the desired state of ClusterPullSecret 49 | properties: 50 | secretRef: 51 | description: ObjectMeta contains enough information to locate the 52 | referenced Kubernetes resource object in any namespace. 53 | properties: 54 | name: 55 | description: Name of the referent. 56 | type: string 57 | namespace: 58 | description: Namespace of the referent, when not specified it 59 | acts as LocalObjectReference. 60 | type: string 61 | required: 62 | - name 63 | type: object 64 | type: object 65 | status: 66 | description: ClusterPullSecretStatus defines the observed state of ClusterPullSecret 67 | type: object 68 | type: object 69 | served: true 70 | storage: true 71 | subresources: {} 72 | --- 73 | apiVersion: rbac.authorization.k8s.io/v1 74 | kind: Role 75 | metadata: 76 | name: registry-creds-leader-election-role 77 | namespace: registry-creds-system 78 | rules: 79 | - apiGroups: 80 | - "" 81 | resources: 82 | - configmaps 83 | verbs: 84 | - get 85 | - create 86 | - update 87 | - patch 88 | - apiGroups: 89 | - coordination.k8s.io 90 | resources: 91 | - leases 92 | verbs: 93 | - get 94 | - create 95 | - update 96 | - patch 97 | --- 98 | apiVersion: rbac.authorization.k8s.io/v1 99 | kind: ClusterRole 100 | metadata: 101 | name: registry-creds-registry-creds-role 102 | rules: 103 | - apiGroups: 104 | - "" 105 | resources: 106 | - namespaces 107 | verbs: 108 | - get 109 | - list 110 | - watch 111 | - apiGroups: 112 | - "" 113 | resources: 114 | - namespaces/status 115 | verbs: 116 | - get 117 | - apiGroups: 118 | - "" 119 | resources: 120 | - secrets 121 | verbs: 122 | - create 123 | - delete 124 | - get 125 | - list 126 | - patch 127 | - update 128 | - watch 129 | - apiGroups: 130 | - "" 131 | resources: 132 | - secrets/status 133 | verbs: 134 | - get 135 | - patch 136 | - update 137 | - apiGroups: 138 | - "" 139 | resources: 140 | - serviceaccounts 141 | verbs: 142 | - create 143 | - delete 144 | - get 145 | - list 146 | - patch 147 | - update 148 | - watch 149 | - apiGroups: 150 | - "" 151 | resources: 152 | - serviceaccounts/status 153 | verbs: 154 | - get 155 | - patch 156 | - update 157 | - apiGroups: 158 | - ops.alexellis.io 159 | resources: 160 | - clusterpullsecrets 161 | verbs: 162 | - get 163 | - list 164 | - watch 165 | - apiGroups: 166 | - ops.alexellis.io 167 | resources: 168 | - clusterpullsecrets/status 169 | verbs: 170 | - get 171 | - patch 172 | - update 173 | --- 174 | apiVersion: rbac.authorization.k8s.io/v1 175 | kind: RoleBinding 176 | metadata: 177 | name: registry-creds-leader-election-rolebinding 178 | namespace: registry-creds-system 179 | roleRef: 180 | apiGroup: rbac.authorization.k8s.io 181 | kind: Role 182 | name: registry-creds-leader-election-role 183 | subjects: 184 | - kind: ServiceAccount 185 | name: default 186 | namespace: registry-creds-system 187 | --- 188 | apiVersion: rbac.authorization.k8s.io/v1 189 | kind: ClusterRoleBinding 190 | metadata: 191 | name: registry-creds-registry-creds-rolebinding 192 | roleRef: 193 | apiGroup: rbac.authorization.k8s.io 194 | kind: ClusterRole 195 | name: registry-creds-registry-creds-role 196 | subjects: 197 | - kind: ServiceAccount 198 | name: default 199 | namespace: registry-creds-system 200 | --- 201 | apiVersion: apps/v1 202 | kind: Deployment 203 | metadata: 204 | labels: 205 | control-plane: registry-creds-controller 206 | name: registry-creds-registry-creds-controller 207 | namespace: registry-creds-system 208 | spec: 209 | replicas: 1 210 | selector: 211 | matchLabels: 212 | control-plane: registry-creds-controller 213 | template: 214 | metadata: 215 | labels: 216 | control-plane: registry-creds-controller 217 | spec: 218 | containers: 219 | - args: 220 | - --enable-leader-election 221 | command: 222 | - /controller 223 | image: ghcr.io/alexellis/registry-creds:0.3.2 224 | imagePullPolicy: IfNotPresent 225 | name: controller 226 | resources: 227 | limits: 228 | cpu: 100m 229 | memory: 128Mi 230 | requests: 231 | cpu: 100m 232 | memory: 45Mi 233 | terminationGracePeriodSeconds: 10 234 | --------------------------------------------------------------------------------