├── .dockerignore ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── PROJECT ├── README.md ├── SECURITY.md ├── api └── v1alpha1 │ ├── groupversion_info.go │ ├── nodeconfig_types.go │ └── zz_generated.deepcopy.go ├── config ├── crd │ ├── bases │ │ └── config.snappcloud.io_nodeconfigs.yaml │ ├── kustomization.yaml │ ├── kustomizeconfig.yaml │ └── patches │ │ ├── cainjection_in_nodeconfigs.yaml │ │ └── webhook_in_nodeconfigs.yaml ├── default │ ├── kustomization.yaml │ ├── manager_auth_proxy_patch.yaml │ └── manager_config_patch.yaml ├── manager │ ├── controller_manager_config.yaml │ ├── kustomization.yaml │ └── manager.yaml ├── manifests │ └── kustomization.yaml ├── prometheus │ ├── kustomization.yaml │ └── monitor.yaml ├── rbac │ ├── auth_proxy_client_clusterrole.yaml │ ├── auth_proxy_role.yaml │ ├── auth_proxy_role_binding.yaml │ ├── auth_proxy_service.yaml │ ├── kustomization.yaml │ ├── leader_election_role.yaml │ ├── leader_election_role_binding.yaml │ ├── nodeconfig_editor_role.yaml │ ├── nodeconfig_viewer_role.yaml │ ├── role.yaml │ ├── role_binding.yaml │ └── service_account.yaml ├── samples │ ├── config_v1alpha1_nodeconfig.yaml │ └── kustomization.yaml └── scorecard │ ├── bases │ └── config.yaml │ ├── kustomization.yaml │ └── patches │ ├── basic.config.yaml │ └── olm.config.yaml ├── controllers ├── node_controller.go ├── nodeconfig_controller.go └── suite_test.go ├── go.mod ├── go.sum ├── hack └── boilerplate.go.txt └── main.go /.dockerignore: -------------------------------------------------------------------------------- 1 | # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 | # Ignore build and test binaries. 3 | bin/ 4 | testbin/ 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: monthly 7 | labels: 8 | - dependencies 9 | groups: 10 | dev-dependencies: 11 | patterns: 12 | - "*" 13 | - package-ecosystem: "docker" 14 | directory: "/" 15 | schedule: 16 | interval: "monthly" 17 | labels: 18 | - dependencies 19 | groups: 20 | docker-dependencies: 21 | patterns: 22 | - "*" 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ci 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: [ v* ] 7 | pull_request: 8 | branches: [ main ] 9 | 10 | jobs: 11 | lint: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v2 18 | with: 19 | version: latest 20 | test: 21 | name: test 22 | runs-on: ubuntu-latest 23 | steps: 24 | - uses: actions/checkout@v2 25 | - uses: actions/setup-go@v2 26 | with: 27 | go-version: '^1.16' 28 | - uses: RyanSiu1995/kubebuilder-action@v1.2.1 29 | - run: make test 30 | - uses: codecov/codecov-action@v1 31 | with: 32 | files: coverage.out 33 | docker: 34 | name: docker 35 | runs-on: ubuntu-latest 36 | needs: 37 | - lint 38 | - test 39 | steps: 40 | - uses: actions/checkout@v2 41 | - uses: docker/setup-qemu-action@v1 42 | - uses: docker/setup-buildx-action@v1 43 | - uses: docker/login-action@v1 44 | with: 45 | registry: ghcr.io 46 | username: ${{ github.repository_owner }} 47 | password: ${{ secrets.GITHUB_TOKEN }} 48 | - uses: docker/metadata-action@v3 49 | id: meta 50 | with: 51 | images: ghcr.io/${{ github.repository }} 52 | tags: | 53 | type=ref,event=branch 54 | type=ref,event=pr 55 | type=semver,pattern={{version}} 56 | type=semver,pattern={{major}}.{{minor}} 57 | - uses: docker/build-push-action@v2 58 | with: 59 | file: "Dockerfile" 60 | context: . 61 | platforms: linux/amd64 62 | push: true 63 | tags: ${{ steps.meta.outputs.tags }} 64 | labels: ${{ steps.meta.outputs.labels }} 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Binaries for programs and plugins 3 | *.exe 4 | *.exe~ 5 | *.dll 6 | *.so 7 | *.dylib 8 | bin 9 | testbin/* 10 | node-config-operator 11 | 12 | # Test binary, build with `go test -c` 13 | *.test 14 | 15 | # Output of the go coverage tool, specifically when used with LiteIDE 16 | *.out 17 | 18 | # Kubernetes Generated files - skip generated files, except for vendored files 19 | 20 | !vendor/**/zz_generated.* 21 | 22 | # editor and IDE paraphernalia 23 | .idea 24 | *.swp 25 | *.swo 26 | *~ 27 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Build the manager binary 2 | FROM golang:1.21.3 as builder 3 | 4 | WORKDIR /workspace 5 | 6 | # Copy the Go Modules manifests 7 | COPY go.mod go.mod 8 | COPY go.sum go.sum 9 | # cache deps before building and copying source so that we don't need to re-download as much 10 | # and so that source changes don't invalidate our downloaded layer 11 | RUN go mod download 12 | 13 | # Copy the go source 14 | COPY main.go main.go 15 | COPY api/ api/ 16 | COPY controllers/ controllers/ 17 | 18 | # Build 19 | RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o manager main.go 20 | 21 | FROM docker.io/library/alpine:latest 22 | WORKDIR / 23 | COPY --from=builder /workspace/manager . 24 | USER 65532:65532 25 | 26 | ENTRYPOINT ["/manager"] 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # VERSION defines the project version for the bundle. 2 | # Update this value when you upgrade the version of your project. 3 | # To re-generate a bundle for another specific version without changing the standard setup, you can: 4 | # - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2) 5 | # - use environment variables to overwrite this value (e.g export VERSION=0.0.2) 6 | VERSION ?= 0.0.1 7 | 8 | # CHANNELS define the bundle channels used in the bundle. 9 | # Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable") 10 | # To re-generate a bundle for other specific channels without changing the standard setup, you can: 11 | # - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable) 12 | # - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable") 13 | ifneq ($(origin CHANNELS), undefined) 14 | BUNDLE_CHANNELS := --channels=$(CHANNELS) 15 | endif 16 | 17 | # DEFAULT_CHANNEL defines the default channel used in the bundle. 18 | # Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable") 19 | # To re-generate a bundle for any other default channel without changing the default setup, you can: 20 | # - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable) 21 | # - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable") 22 | ifneq ($(origin DEFAULT_CHANNEL), undefined) 23 | BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL) 24 | endif 25 | BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL) 26 | 27 | # IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images. 28 | # This variable is used to construct full image tags for bundle and catalog images. 29 | # 30 | # For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both 31 | # snappcloud.io/node-config-operator-bundle:$VERSION and snappcloud.io/node-config-operator-catalog:$VERSION. 32 | IMAGE_TAG_BASE ?= snappcloud.io/node-config-operator 33 | 34 | # BUNDLE_IMG defines the image:tag used for the bundle. 35 | # You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=/:) 36 | BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION) 37 | 38 | REG = registry.okd4.teh-1.snappcloud.io 39 | # Image URL to use all building/pushing image targets 40 | IMG ?= ${REG}/public-reg/node-config-operator:latest 41 | # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) 42 | CRD_OPTIONS ?= "crd:trivialVersions=true,preserveUnknownFields=false" 43 | 44 | # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 45 | ifeq (,$(shell go env GOBIN)) 46 | GOBIN=$(shell go env GOPATH)/bin 47 | else 48 | GOBIN=$(shell go env GOBIN) 49 | endif 50 | 51 | # Setting SHELL to bash allows bash commands to be executed by recipes. 52 | # This is a requirement for 'setup-envtest.sh' in the test target. 53 | # Options are set to exit when a recipe line exits non-zero or a piped command fails. 54 | SHELL = /usr/bin/env bash -o pipefail 55 | .SHELLFLAGS = -ec 56 | 57 | all: build 58 | 59 | ##@ General 60 | 61 | # The help target prints out all targets with their descriptions organized 62 | # beneath their categories. The categories are represented by '##@' and the 63 | # target descriptions by '##'. The awk commands is responsible for reading the 64 | # entire set of makefiles included in this invocation, looking for lines of the 65 | # file as xyz: ## something, and then pretty-format the target and help. Then, 66 | # if there's a line with ##@ something, that gets pretty-printed as a category. 67 | # More info on the usage of ANSI control characters for terminal formatting: 68 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 69 | # More info on the awk command: 70 | # http://linuxcommand.org/lc3_adv_awk.php 71 | 72 | help: ## Display this help. 73 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) 74 | 75 | ##@ Development 76 | 77 | manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 78 | $(CONTROLLER_GEN) $(CRD_OPTIONS) rbac:roleName=manager-role webhook paths="./..." output:crd:artifacts:config=config/crd/bases 79 | 80 | generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 81 | $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 82 | 83 | fmt: ## Run go fmt against code. 84 | go fmt ./... 85 | 86 | vet: ## Run go vet against code. 87 | go vet ./... 88 | 89 | tidy: 90 | go mod tidy 91 | 92 | test: manifests generate fmt vet envtest ## Run tests. 93 | go test ./... -covermode=atomic -coverprofile=coverage.out 94 | go tool cover -html=coverage.out -o coverage.html 95 | 96 | ##@ Build 97 | 98 | build: manifests generate fmt vet tidy ## Build manager binary. 99 | go build -race -o bin/manager main.go 100 | 101 | run: manifests generate fmt vet ## Run a controller from your host. 102 | go run ./main.go 103 | 104 | docker-build: test ## Build docker image with the manager. 105 | sudo podman build -t ${IMG} . 106 | 107 | docker-push: ## Push docker image with the manager. 108 | sudo podman push ${IMG} 109 | 110 | docker-login: 111 | sudo podman login ${REG} -u ${REG_USER} -p ${REG_PASSWORD} 112 | 113 | redeploy: docker-build docker-login docker-push 114 | ##@ Deployment 115 | 116 | install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 117 | $(KUSTOMIZE) build config/crd | kubectl apply -f - 118 | 119 | uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. 120 | $(KUSTOMIZE) build config/crd | kubectl delete -f - 121 | 122 | deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 123 | cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 124 | $(KUSTOMIZE) build config/default | kubectl apply -f - 125 | 126 | undeploy: ## Undeploy controller from the K8s cluster specified in ~/.kube/config. 127 | $(KUSTOMIZE) build config/default | kubectl delete -f - 128 | 129 | 130 | CONTROLLER_GEN = $(shell pwd)/bin/controller-gen 131 | controller-gen: ## Download controller-gen locally if necessary. 132 | $(call go-get-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen@v0.6.1) 133 | 134 | KUSTOMIZE = $(shell pwd)/bin/kustomize 135 | kustomize: ## Download kustomize locally if necessary. 136 | $(call go-get-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v3@v3.8.7) 137 | 138 | ENVTEST = $(shell pwd)/bin/setup-envtest 139 | envtest: ## Download envtest-setup locally if necessary. 140 | $(call go-get-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest@latest) 141 | $(ENVTEST) use 1.21 142 | 143 | # go-get-tool will 'go get' any package $2 and install it to $1. 144 | PROJECT_DIR := $(shell dirname $(abspath $(lastword $(MAKEFILE_LIST)))) 145 | define go-get-tool 146 | @[ -f $(1) ] || { \ 147 | set -e ;\ 148 | TMP_DIR=$$(mktemp -d) ;\ 149 | cd $$TMP_DIR ;\ 150 | go mod init tmp ;\ 151 | echo "Downloading $(2)" ;\ 152 | GOBIN=$(PROJECT_DIR)/bin go get $(2) ;\ 153 | rm -rf $$TMP_DIR ;\ 154 | } 155 | endef 156 | 157 | .PHONY: bundle 158 | bundle: manifests kustomize ## Generate bundle manifests and metadata, then validate generated files. 159 | operator-sdk generate kustomize manifests -q 160 | cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG) 161 | $(KUSTOMIZE) build config/manifests | operator-sdk generate bundle -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS) 162 | operator-sdk bundle validate ./bundle 163 | 164 | .PHONY: bundle-build 165 | bundle-build: ## Build the bundle image. 166 | sudo docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . 167 | 168 | .PHONY: bundle-push 169 | bundle-push: ## Push the bundle image. 170 | $(MAKE) docker-push IMG=$(BUNDLE_IMG) 171 | 172 | .PHONY: opm 173 | OPM = ./bin/opm 174 | opm: ## Download opm locally if necessary. 175 | ifeq (,$(wildcard $(OPM))) 176 | ifeq (,$(shell which opm 2>/dev/null)) 177 | @{ \ 178 | set -e ;\ 179 | mkdir -p $(dir $(OPM)) ;\ 180 | OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ 181 | curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.15.1/$${OS}-$${ARCH}-opm ;\ 182 | chmod +x $(OPM) ;\ 183 | } 184 | else 185 | OPM = $(shell which opm) 186 | endif 187 | endif 188 | 189 | # A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0). 190 | # These images MUST exist in a registry and be pull-able. 191 | BUNDLE_IMGS ?= $(BUNDLE_IMG) 192 | 193 | # The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0). 194 | CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION) 195 | 196 | # Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image. 197 | ifneq ($(origin CATALOG_BASE_IMG), undefined) 198 | FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG) 199 | endif 200 | 201 | # Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'. 202 | # This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see: 203 | # https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator 204 | .PHONY: catalog-build 205 | catalog-build: opm ## Build a catalog image. 206 | $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) 207 | 208 | # Push the catalog image. 209 | .PHONY: catalog-push 210 | catalog-push: ## Push a catalog image. 211 | $(MAKE) docker-push IMG=$(CATALOG_IMG) 212 | -------------------------------------------------------------------------------- /PROJECT: -------------------------------------------------------------------------------- 1 | domain: snappcloud.io 2 | layout: 3 | - go.kubebuilder.io/v3 4 | plugins: 5 | manifests.sdk.operatorframework.io/v2: {} 6 | scorecard.sdk.operatorframework.io/v2: {} 7 | projectName: node-config-operator 8 | repo: github.com/snapp-incubator/node-config-operator 9 | resources: 10 | - api: 11 | crdVersion: v1 12 | controller: true 13 | domain: snappcloud.io 14 | group: config 15 | kind: NodeConfig 16 | path: github.com/snapp-incubator/node-config-operator/api/v1alpha1 17 | version: v1alpha1 18 | - controller: true 19 | group: core 20 | kind: Node 21 | path: k8s.io/api/core/v1 22 | version: v1 23 | version: "3" 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NodeConfig Operator 2 | 3 | An operator to manage node labels, annotations and taints based on `NodeConfig` Custom Resource. 4 | 5 | 6 | Comparison to alternatives: 7 | 8 | 1. https://github.com/barpilot/node-labeler-operator: this one does support node taints and labels, but can only select nodes based on labels, not node name pattern (regex). Also it is not an actively maintained project and has no activity since 2018. 9 | 10 | 2. https://github.com/openshift-kni/node-label-operator: this one does support node pattern, but does not support node taints and annotations. Also this operator is deprectated and not maintained anymore. see [this issue](https://github.com/openshift-kni/node-label-operator/issues/13#issuecomment-910010585). 11 | 12 | This operator supports features of both aforementioned operators. 13 | 14 | ## Behavior of the operator 15 | 16 | - If a node matches multiple NodeConfig, and the same label ro `spec.taint` is defined in both, one randomly will be applied (last one in listing). 17 | - No such concept as owned labels. It only adds/modify labels, no label removal. Also only adds/modifies taints based on keys, no removal. (it will be supported in future) 18 | 19 | ## Instructions 20 | 21 | ### Development 22 | 23 | * `make generate` update the generated code for that resource type. 24 | * `make manifests` Generating CRD manifests. 25 | * `make test` Run tests. 26 | 27 | ### Build 28 | 29 | Export your image name: 30 | 31 | ``` 32 | export IMG=ghcr.io/your-repo-path/image-name:latest 33 | ``` 34 | 35 | * `make build` builds golang app locally. 36 | * `make docker-build` build docker image locally. 37 | * `make docker-push` push container image to registry. 38 | 39 | ### Run, Deploy 40 | * `make run` run app locally 41 | * `make deploy` deploy to k8s. 42 | 43 | ### Clean up 44 | 45 | * `make undeploy` delete resouces in k8s. 46 | 47 | ## Configuration 48 | 49 | 50 | Flags: 51 | 52 | ``` 53 | -health-probe-bind-address string 54 | The address the probe endpoint binds to. (default ":8081") 55 | -kubeconfig string 56 | Paths to a kubeconfig. Only required if out-of-cluster. 57 | -leader-elect 58 | Enable leader election for controller manager. Enabling this will ensure there is only one active controller manager. 59 | -metrics-bind-address string 60 | The address the metric endpoint binds to. (default ":8080") 61 | -zap-devel 62 | Development Mode defaults(encoder=consoleEncoder,logLevel=Debug,stackTraceLevel=Warn). Production Mode defaults(encoder=jsonEncoder,logLevel=Info,stackTraceLevel=Error) (default true) 63 | -zap-encoder value 64 | Zap log encoding (one of 'json' or 'console') 65 | -zap-log-level value 66 | Zap Level to configure the verbosity of logging. Can be one of 'debug', 'info', 'error', or any integer value > 0 which corresponds to custom debug levels of increasing verbosity 67 | -zap-stacktrace-level value 68 | Zap Level at and above which stacktraces are captured (one of 'info', 'error', 'panic'). 69 | ``` 70 | 71 | For sample NodeConfig objects, see [config/samples](config/samples) directory. 72 | 73 | 74 | ## Roadmap 75 | 76 | - [ ] OwnedLabels 77 | - [ ] Selecting Nodes based on label 78 | 79 | ## Metrics 80 | 81 | | Metric | Notes 82 | |-----------------------------------------------------|------------------------------------ 83 | | controller_runtime_active_workers | Number of currently used workers per controller 84 | | controller_runtime_max_concurrent_reconciles | Maximum number of concurrent reconciles per controller 85 | | controller_runtime_reconcile_errors_total | Total number of reconciliation errors per controller 86 | | controller_runtime_reconcile_time_seconds | Length of time per reconciliation per controller 87 | | controller_runtime_reconcile_total | Total number of reconciliations per controller 88 | | rest_client_request_latency_seconds | Request latency in seconds. Broken down by verb and URL. 89 | | rest_client_requests_total | Number of HTTP requests, partitioned by status code, method, and host. 90 | | workqueue_adds_total | Total number of adds handled by workqueue 91 | | workqueue_depth | Current depth of workqueue 92 | | workqueue_longest_running_processor_seconds | How many seconds has the longest running processor for workqueue been running. 93 | | workqueue_queue_duration_seconds | How long in seconds an item stays in workqueue before being requested 94 | | workqueue_retries_total | Total number of retries handled by workqueue 95 | | workqueue_unfinished_work_seconds | How many seconds of work has been done that is in progress and hasn't been observed by work_duration. Large values indicate stuck threads. One can deduce the number of stuck threads by observing the rate at which this increases. 96 | | workqueue_work_duration_seconds | How long in seconds processing an item from workqueue takes. 97 | 98 | 99 | ## Security 100 | 101 | ### Reporting security vulnerabilities 102 | 103 | If you find a security vulnerability or any security related issues, please DO NOT file a public issue, instead send your report privately to cloud@snapp.cab. Security reports are greatly appreciated and we will publicly thank you for it. 104 | 105 | ## License 106 | 107 | Apache-2.0 License, see [LICENSE](LICENSE). 108 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | 4 | ## Reporting a Vulnerability 5 | 6 | If you find a security vulnerability or any security related issues, please DO NOT file a public issue. Do not create a Github issue. Instead, send your report privately to cloud@snapp.cab. Security reports are greatly appreciated and we will publicly thank you for it. 7 | -------------------------------------------------------------------------------- /api/v1alpha1/groupversion_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 v1alpha1 contains API Schema definitions for the config v1alpha1 API group 18 | //+kubebuilder:object:generate=true 19 | //+groupName=config.snappcloud.io 20 | package v1alpha1 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: "config.snappcloud.io", Version: "v1alpha1"} 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/v1alpha1/nodeconfig_types.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 v1alpha1 18 | 19 | import ( 20 | corev1 "k8s.io/api/core/v1" 21 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 | ) 23 | 24 | // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! 25 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 26 | 27 | // NodeConfigSpec defines the desired state of NodeConfig 28 | type NodeConfigSpec struct { 29 | // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster 30 | // Important: Run "make" to regenerate code after modifying this file 31 | 32 | Match Match `json:"match,omitempty"` 33 | Merge Merge `json:"merge,omitempty"` 34 | } 35 | 36 | type Match struct { 37 | NodeNamePatterns []string `json:"nodeNamePatterns,omitempty"` 38 | } 39 | 40 | type Merge struct { 41 | Labels map[string]string `json:"labels,omitempty"` 42 | Annotations map[string]string `json:"annotations,omitempty"` 43 | Taints []corev1.Taint `json:"taints,omitempty"` 44 | } 45 | 46 | // NodeConfigStatus defines the observed state of NodeConfig 47 | type NodeConfigStatus struct { 48 | // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster 49 | // Important: Run "make" to regenerate code after modifying this file 50 | } 51 | 52 | //+kubebuilder:object:root=true 53 | //+kubebuilder:subresource:status 54 | //+kubebuilder:resource:scope=Cluster 55 | 56 | // NodeConfig is the Schema for the nodeconfigs API 57 | type NodeConfig struct { 58 | metav1.TypeMeta `json:",inline"` 59 | metav1.ObjectMeta `json:"metadata,omitempty"` 60 | 61 | Spec NodeConfigSpec `json:"spec,omitempty"` 62 | Status NodeConfigStatus `json:"status,omitempty"` 63 | } 64 | 65 | //+kubebuilder:object:root=true 66 | 67 | // NodeConfigList contains a list of NodeConfig 68 | type NodeConfigList struct { 69 | metav1.TypeMeta `json:",inline"` 70 | metav1.ListMeta `json:"metadata,omitempty"` 71 | Items []NodeConfig `json:"items"` 72 | } 73 | 74 | func init() { 75 | SchemeBuilder.Register(&NodeConfig{}, &NodeConfigList{}) 76 | } 77 | -------------------------------------------------------------------------------- /api/v1alpha1/zz_generated.deepcopy.go: -------------------------------------------------------------------------------- 1 | // +build !ignore_autogenerated 2 | 3 | /* 4 | Copyright 2021. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | http://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | // Code generated by controller-gen. DO NOT EDIT. 20 | 21 | package v1alpha1 22 | 23 | import ( 24 | "k8s.io/api/core/v1" 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 *Match) DeepCopyInto(out *Match) { 30 | *out = *in 31 | if in.NodeNamePatterns != nil { 32 | in, out := &in.NodeNamePatterns, &out.NodeNamePatterns 33 | *out = make([]string, len(*in)) 34 | copy(*out, *in) 35 | } 36 | } 37 | 38 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Match. 39 | func (in *Match) DeepCopy() *Match { 40 | if in == nil { 41 | return nil 42 | } 43 | out := new(Match) 44 | in.DeepCopyInto(out) 45 | return out 46 | } 47 | 48 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 49 | func (in *Merge) DeepCopyInto(out *Merge) { 50 | *out = *in 51 | if in.Labels != nil { 52 | in, out := &in.Labels, &out.Labels 53 | *out = make(map[string]string, len(*in)) 54 | for key, val := range *in { 55 | (*out)[key] = val 56 | } 57 | } 58 | if in.Annotations != nil { 59 | in, out := &in.Annotations, &out.Annotations 60 | *out = make(map[string]string, len(*in)) 61 | for key, val := range *in { 62 | (*out)[key] = val 63 | } 64 | } 65 | if in.Taints != nil { 66 | in, out := &in.Taints, &out.Taints 67 | *out = make([]v1.Taint, len(*in)) 68 | for i := range *in { 69 | (*in)[i].DeepCopyInto(&(*out)[i]) 70 | } 71 | } 72 | } 73 | 74 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Merge. 75 | func (in *Merge) DeepCopy() *Merge { 76 | if in == nil { 77 | return nil 78 | } 79 | out := new(Merge) 80 | in.DeepCopyInto(out) 81 | return out 82 | } 83 | 84 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 85 | func (in *NodeConfig) DeepCopyInto(out *NodeConfig) { 86 | *out = *in 87 | out.TypeMeta = in.TypeMeta 88 | in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) 89 | in.Spec.DeepCopyInto(&out.Spec) 90 | out.Status = in.Status 91 | } 92 | 93 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeConfig. 94 | func (in *NodeConfig) DeepCopy() *NodeConfig { 95 | if in == nil { 96 | return nil 97 | } 98 | out := new(NodeConfig) 99 | in.DeepCopyInto(out) 100 | return out 101 | } 102 | 103 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 104 | func (in *NodeConfig) DeepCopyObject() runtime.Object { 105 | if c := in.DeepCopy(); c != nil { 106 | return c 107 | } 108 | return nil 109 | } 110 | 111 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 112 | func (in *NodeConfigList) DeepCopyInto(out *NodeConfigList) { 113 | *out = *in 114 | out.TypeMeta = in.TypeMeta 115 | in.ListMeta.DeepCopyInto(&out.ListMeta) 116 | if in.Items != nil { 117 | in, out := &in.Items, &out.Items 118 | *out = make([]NodeConfig, len(*in)) 119 | for i := range *in { 120 | (*in)[i].DeepCopyInto(&(*out)[i]) 121 | } 122 | } 123 | } 124 | 125 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeConfigList. 126 | func (in *NodeConfigList) DeepCopy() *NodeConfigList { 127 | if in == nil { 128 | return nil 129 | } 130 | out := new(NodeConfigList) 131 | in.DeepCopyInto(out) 132 | return out 133 | } 134 | 135 | // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. 136 | func (in *NodeConfigList) DeepCopyObject() runtime.Object { 137 | if c := in.DeepCopy(); c != nil { 138 | return c 139 | } 140 | return nil 141 | } 142 | 143 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 144 | func (in *NodeConfigSpec) DeepCopyInto(out *NodeConfigSpec) { 145 | *out = *in 146 | in.Match.DeepCopyInto(&out.Match) 147 | in.Merge.DeepCopyInto(&out.Merge) 148 | } 149 | 150 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeConfigSpec. 151 | func (in *NodeConfigSpec) DeepCopy() *NodeConfigSpec { 152 | if in == nil { 153 | return nil 154 | } 155 | out := new(NodeConfigSpec) 156 | in.DeepCopyInto(out) 157 | return out 158 | } 159 | 160 | // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. 161 | func (in *NodeConfigStatus) DeepCopyInto(out *NodeConfigStatus) { 162 | *out = *in 163 | } 164 | 165 | // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeConfigStatus. 166 | func (in *NodeConfigStatus) DeepCopy() *NodeConfigStatus { 167 | if in == nil { 168 | return nil 169 | } 170 | out := new(NodeConfigStatus) 171 | in.DeepCopyInto(out) 172 | return out 173 | } 174 | -------------------------------------------------------------------------------- /config/crd/bases/config.snappcloud.io_nodeconfigs.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: apiextensions.k8s.io/v1 4 | kind: CustomResourceDefinition 5 | metadata: 6 | annotations: 7 | controller-gen.kubebuilder.io/version: v0.6.1 8 | creationTimestamp: null 9 | name: nodeconfigs.config.snappcloud.io 10 | spec: 11 | group: config.snappcloud.io 12 | names: 13 | kind: NodeConfig 14 | listKind: NodeConfigList 15 | plural: nodeconfigs 16 | singular: nodeconfig 17 | scope: Cluster 18 | versions: 19 | - name: v1alpha1 20 | schema: 21 | openAPIV3Schema: 22 | description: NodeConfig is the Schema for the nodeconfigs API 23 | properties: 24 | apiVersion: 25 | description: 'APIVersion defines the versioned schema of this representation 26 | of an object. Servers should convert recognized schemas to the latest 27 | internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' 28 | type: string 29 | kind: 30 | description: 'Kind is a string value representing the REST resource this 31 | object represents. Servers may infer this from the endpoint the client 32 | submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' 33 | type: string 34 | metadata: 35 | type: object 36 | spec: 37 | description: NodeConfigSpec defines the desired state of NodeConfig 38 | properties: 39 | match: 40 | properties: 41 | nodeNamePatterns: 42 | items: 43 | type: string 44 | type: array 45 | type: object 46 | merge: 47 | properties: 48 | annotations: 49 | additionalProperties: 50 | type: string 51 | type: object 52 | labels: 53 | additionalProperties: 54 | type: string 55 | type: object 56 | taints: 57 | items: 58 | description: The node this Taint is attached to has the "effect" 59 | on any pod that does not tolerate the Taint. 60 | properties: 61 | effect: 62 | description: Required. The effect of the taint on pods that 63 | do not tolerate the taint. Valid effects are NoSchedule, 64 | PreferNoSchedule and NoExecute. 65 | type: string 66 | key: 67 | description: Required. The taint key to be applied to a 68 | node. 69 | type: string 70 | timeAdded: 71 | description: TimeAdded represents the time at which the 72 | taint was added. It is only written for NoExecute taints. 73 | format: date-time 74 | type: string 75 | value: 76 | description: The taint value corresponding to the taint 77 | key. 78 | type: string 79 | required: 80 | - effect 81 | - key 82 | type: object 83 | type: array 84 | type: object 85 | type: object 86 | status: 87 | description: NodeConfigStatus defines the observed state of NodeConfig 88 | type: object 89 | type: object 90 | served: true 91 | storage: true 92 | subresources: 93 | status: {} 94 | status: 95 | acceptedNames: 96 | kind: "" 97 | plural: "" 98 | conditions: [] 99 | storedVersions: [] 100 | -------------------------------------------------------------------------------- /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/config.snappcloud.io_nodeconfigs.yaml 6 | #+kubebuilder:scaffold:crdkustomizeresource 7 | 8 | patchesStrategicMerge: 9 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. 10 | # patches here are for enabling the conversion webhook for each CRD 11 | #- patches/webhook_in_nodeconfigs.yaml 12 | #+kubebuilder:scaffold:crdkustomizewebhookpatch 13 | 14 | # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. 15 | # patches here are for enabling the CA injection for each CRD 16 | #- patches/cainjection_in_nodeconfigs.yaml 17 | #+kubebuilder:scaffold:crdkustomizecainjectionpatch 18 | 19 | # the following config is for teaching kustomize how to do kustomization for CRDs. 20 | configurations: 21 | - kustomizeconfig.yaml 22 | -------------------------------------------------------------------------------- /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 | version: v1 8 | group: apiextensions.k8s.io 9 | path: spec/conversion/webhook/clientConfig/service/name 10 | 11 | namespace: 12 | - kind: CustomResourceDefinition 13 | version: v1 14 | group: apiextensions.k8s.io 15 | path: spec/conversion/webhook/clientConfig/service/namespace 16 | create: false 17 | 18 | varReference: 19 | - path: metadata/annotations 20 | -------------------------------------------------------------------------------- /config/crd/patches/cainjection_in_nodeconfigs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch adds a directive for certmanager to inject CA into the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | annotations: 6 | cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) 7 | name: nodeconfigs.config.snappcloud.io 8 | -------------------------------------------------------------------------------- /config/crd/patches/webhook_in_nodeconfigs.yaml: -------------------------------------------------------------------------------- 1 | # The following patch enables a conversion webhook for the CRD 2 | apiVersion: apiextensions.k8s.io/v1 3 | kind: CustomResourceDefinition 4 | metadata: 5 | name: nodeconfigs.config.snappcloud.io 6 | spec: 7 | conversion: 8 | strategy: Webhook 9 | webhook: 10 | clientConfig: 11 | service: 12 | namespace: system 13 | name: webhook-service 14 | path: /convert 15 | conversionReviewVersions: 16 | - v1 17 | -------------------------------------------------------------------------------- /config/default/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # Adds namespace to all resources. 2 | namespace: node-config-operator-system 3 | 4 | # Value of this field is prepended to the 5 | # names of all resources, e.g. a deployment named 6 | # "wordpress" becomes "alices-wordpress". 7 | # Note that it should also match with the prefix (text before '-') of the namespace 8 | # field above. 9 | namePrefix: node-config-operator- 10 | 11 | # Labels to add to all resources and selectors. 12 | #commonLabels: 13 | # someName: someValue 14 | 15 | bases: 16 | - ../crd 17 | - ../rbac 18 | - ../manager 19 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 20 | # crd/kustomization.yaml 21 | #- ../webhook 22 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 23 | #- ../certmanager 24 | # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 25 | #- ../prometheus 26 | 27 | patchesStrategicMerge: 28 | # Protect the /metrics endpoint by putting it behind auth. 29 | # If you want your controller-manager to expose the /metrics 30 | # endpoint w/o any authn/z, please comment the following line. 31 | - manager_auth_proxy_patch.yaml 32 | 33 | # Mount the controller config file for loading manager configurations 34 | # through a ComponentConfig type 35 | #- manager_config_patch.yaml 36 | 37 | # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 38 | # crd/kustomization.yaml 39 | #- manager_webhook_patch.yaml 40 | 41 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 42 | # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. 43 | # 'CERTMANAGER' needs to be enabled to use ca injection 44 | #- webhookcainjection_patch.yaml 45 | 46 | # the following config is for teaching kustomize how to do var substitution 47 | vars: 48 | # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 49 | #- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR 50 | # objref: 51 | # kind: Certificate 52 | # group: cert-manager.io 53 | # version: v1 54 | # name: serving-cert # this name should match the one in certificate.yaml 55 | # fieldref: 56 | # fieldpath: metadata.namespace 57 | #- name: CERTIFICATE_NAME 58 | # objref: 59 | # kind: Certificate 60 | # group: cert-manager.io 61 | # version: v1 62 | # name: serving-cert # this name should match the one in certificate.yaml 63 | #- name: SERVICE_NAMESPACE # namespace of the service 64 | # objref: 65 | # kind: Service 66 | # version: v1 67 | # name: webhook-service 68 | # fieldref: 69 | # fieldpath: metadata.namespace 70 | #- name: SERVICE_NAME 71 | # objref: 72 | # kind: Service 73 | # version: v1 74 | # name: webhook-service 75 | -------------------------------------------------------------------------------- /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: controller-manager 7 | namespace: system 8 | spec: 9 | template: 10 | spec: 11 | containers: 12 | - name: kube-rbac-proxy 13 | image: gcr.io/kubebuilder/kube-rbac-proxy:v0.8.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 | - "--health-probe-bind-address=:8081" 25 | - "--metrics-bind-address=127.0.0.1:8080" 26 | - "--leader-elect" 27 | -------------------------------------------------------------------------------- /config/default/manager_config_patch.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | spec: 7 | template: 8 | spec: 9 | containers: 10 | - name: manager 11 | args: 12 | - "--config=controller_manager_config.yaml" 13 | volumeMounts: 14 | - name: manager-config 15 | mountPath: /controller_manager_config.yaml 16 | subPath: controller_manager_config.yaml 17 | volumes: 18 | - name: manager-config 19 | configMap: 20 | name: manager-config 21 | -------------------------------------------------------------------------------- /config/manager/controller_manager_config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: controller-runtime.sigs.k8s.io/v1alpha1 2 | kind: ControllerManagerConfig 3 | health: 4 | healthProbeBindAddress: :8081 5 | metrics: 6 | bindAddress: 127.0.0.1:8080 7 | webhook: 8 | port: 9443 9 | leaderElection: 10 | leaderElect: true 11 | resourceName: 97a91c42.snappcloud.io 12 | -------------------------------------------------------------------------------- /config/manager/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - manager.yaml 3 | 4 | generatorOptions: 5 | disableNameSuffixHash: true 6 | 7 | configMapGenerator: 8 | - name: manager-config 9 | files: 10 | - controller_manager_config.yaml 11 | -------------------------------------------------------------------------------- /config/manager/manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: system 7 | --- 8 | apiVersion: apps/v1 9 | kind: Deployment 10 | metadata: 11 | name: controller-manager 12 | namespace: system 13 | labels: 14 | control-plane: controller-manager 15 | spec: 16 | selector: 17 | matchLabels: 18 | control-plane: controller-manager 19 | replicas: 1 20 | template: 21 | metadata: 22 | labels: 23 | control-plane: controller-manager 24 | spec: 25 | securityContext: 26 | runAsNonRoot: true 27 | containers: 28 | - command: 29 | - /manager 30 | args: 31 | - --leader-elect 32 | image: ghcr.io/snapp-incubator/node-config-operator:main 33 | name: manager 34 | securityContext: 35 | allowPrivilegeEscalation: false 36 | livenessProbe: 37 | httpGet: 38 | path: /healthz 39 | port: 8081 40 | initialDelaySeconds: 15 41 | periodSeconds: 20 42 | readinessProbe: 43 | httpGet: 44 | path: /readyz 45 | port: 8081 46 | initialDelaySeconds: 5 47 | periodSeconds: 10 48 | resources: 49 | limits: 50 | cpu: 100m 51 | memory: 30Mi 52 | requests: 53 | cpu: 100m 54 | memory: 20Mi 55 | serviceAccountName: controller-manager 56 | terminationGracePeriodSeconds: 10 57 | -------------------------------------------------------------------------------- /config/manifests/kustomization.yaml: -------------------------------------------------------------------------------- 1 | # These resources constitute the fully configured set of manifests 2 | # used to generate the 'manifests/' directory in a bundle. 3 | resources: 4 | - bases/node-config-operator.clusterserviceversion.yaml 5 | - ../default 6 | - ../samples 7 | - ../scorecard 8 | 9 | # [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix. 10 | # Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager. 11 | # These patches remove the unnecessary "cert" volume and its manager container volumeMount. 12 | #patchesJson6902: 13 | #- target: 14 | # group: apps 15 | # version: v1 16 | # kind: Deployment 17 | # name: controller-manager 18 | # namespace: system 19 | # patch: |- 20 | # # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs. 21 | # # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment. 22 | # - op: remove 23 | # path: /spec/template/spec/containers/1/volumeMounts/0 24 | # # Remove the "cert" volume, since OLM will create and mount a set of certs. 25 | # # Update the indices in this path if adding or removing volumes in the manager's Deployment. 26 | # - op: remove 27 | # path: /spec/template/spec/volumes/0 28 | -------------------------------------------------------------------------------- /config/prometheus/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - monitor.yaml 3 | -------------------------------------------------------------------------------- /config/prometheus/monitor.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Prometheus Monitor Service (Metrics) 3 | apiVersion: monitoring.coreos.com/v1 4 | kind: ServiceMonitor 5 | metadata: 6 | labels: 7 | control-plane: controller-manager 8 | name: controller-manager-metrics-monitor 9 | namespace: system 10 | spec: 11 | endpoints: 12 | - path: /metrics 13 | port: https 14 | scheme: https 15 | bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 16 | tlsConfig: 17 | insecureSkipVerify: true 18 | selector: 19 | matchLabels: 20 | control-plane: controller-manager 21 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_client_clusterrole.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: metrics-reader 5 | rules: 6 | - nonResourceURLs: 7 | - "/metrics" 8 | verbs: 9 | - get 10 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRole 3 | metadata: 4 | name: proxy-role 5 | rules: 6 | - apiGroups: 7 | - authentication.k8s.io 8 | resources: 9 | - tokenreviews 10 | verbs: 11 | - create 12 | - apiGroups: 13 | - authorization.k8s.io 14 | resources: 15 | - subjectaccessreviews 16 | verbs: 17 | - create 18 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: proxy-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: proxy-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/auth_proxy_service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | labels: 5 | control-plane: controller-manager 6 | name: controller-manager-metrics-service 7 | namespace: system 8 | spec: 9 | ports: 10 | - name: https 11 | port: 8443 12 | targetPort: https 13 | selector: 14 | control-plane: controller-manager 15 | -------------------------------------------------------------------------------- /config/rbac/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | # All RBAC will be applied under this service account in 3 | # the deployment namespace. You may comment out this resource 4 | # if your manager will use a service account that exists at 5 | # runtime. Be sure to update RoleBinding and ClusterRoleBinding 6 | # subjects if changing service account names. 7 | - service_account.yaml 8 | - role.yaml 9 | - role_binding.yaml 10 | - leader_election_role.yaml 11 | - leader_election_role_binding.yaml 12 | # Comment the following 4 lines if you want to disable 13 | # the auth proxy (https://github.com/brancz/kube-rbac-proxy) 14 | # which protects your /metrics endpoint. 15 | - auth_proxy_service.yaml 16 | - auth_proxy_role.yaml 17 | - auth_proxy_role_binding.yaml 18 | - auth_proxy_client_clusterrole.yaml 19 | -------------------------------------------------------------------------------- /config/rbac/leader_election_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions to do leader election. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: Role 4 | metadata: 5 | name: leader-election-role 6 | rules: 7 | - apiGroups: 8 | - "" 9 | resources: 10 | - configmaps 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - create 16 | - update 17 | - patch 18 | - delete 19 | - apiGroups: 20 | - coordination.k8s.io 21 | resources: 22 | - leases 23 | verbs: 24 | - get 25 | - list 26 | - watch 27 | - create 28 | - update 29 | - patch 30 | - delete 31 | - apiGroups: 32 | - "" 33 | resources: 34 | - events 35 | verbs: 36 | - create 37 | - patch 38 | -------------------------------------------------------------------------------- /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: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/nodeconfig_editor_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to edit nodeconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: nodeconfig-editor-role 6 | rules: 7 | - apiGroups: 8 | - config.snappcloud.io 9 | resources: 10 | - nodeconfigs 11 | verbs: 12 | - create 13 | - delete 14 | - get 15 | - list 16 | - patch 17 | - update 18 | - watch 19 | - apiGroups: 20 | - config.snappcloud.io 21 | resources: 22 | - nodeconfigs/status 23 | verbs: 24 | - get 25 | -------------------------------------------------------------------------------- /config/rbac/nodeconfig_viewer_role.yaml: -------------------------------------------------------------------------------- 1 | # permissions for end users to view nodeconfigs. 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | kind: ClusterRole 4 | metadata: 5 | name: nodeconfig-viewer-role 6 | rules: 7 | - apiGroups: 8 | - config.snappcloud.io 9 | resources: 10 | - nodeconfigs 11 | verbs: 12 | - get 13 | - list 14 | - watch 15 | - apiGroups: 16 | - config.snappcloud.io 17 | resources: 18 | - nodeconfigs/status 19 | verbs: 20 | - get 21 | -------------------------------------------------------------------------------- /config/rbac/role.yaml: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | apiVersion: rbac.authorization.k8s.io/v1 4 | kind: ClusterRole 5 | metadata: 6 | creationTimestamp: null 7 | name: manager-role 8 | rules: 9 | - apiGroups: 10 | - config.snappcloud.io 11 | resources: 12 | - nodeconfigs 13 | verbs: 14 | - create 15 | - delete 16 | - get 17 | - list 18 | - patch 19 | - update 20 | - watch 21 | - apiGroups: 22 | - config.snappcloud.io 23 | resources: 24 | - nodeconfigs/finalizers 25 | verbs: 26 | - update 27 | - apiGroups: 28 | - config.snappcloud.io 29 | resources: 30 | - nodeconfigs/status 31 | verbs: 32 | - get 33 | - patch 34 | - update 35 | - apiGroups: 36 | - "" 37 | resources: 38 | - nodes 39 | verbs: 40 | - create 41 | - delete 42 | - get 43 | - list 44 | - patch 45 | - update 46 | - watch 47 | - apiGroups: 48 | - "" 49 | resources: 50 | - nodes/finalizers 51 | verbs: 52 | - update 53 | - apiGroups: 54 | - "" 55 | resources: 56 | - nodes/status 57 | verbs: 58 | - get 59 | - patch 60 | - update 61 | -------------------------------------------------------------------------------- /config/rbac/role_binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: manager-rolebinding 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: manager-role 9 | subjects: 10 | - kind: ServiceAccount 11 | name: controller-manager 12 | namespace: system 13 | -------------------------------------------------------------------------------- /config/rbac/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: controller-manager 5 | namespace: system 6 | -------------------------------------------------------------------------------- /config/samples/config_v1alpha1_nodeconfig.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: config.snappcloud.io/v1alpha1 3 | kind: NodeConfig 4 | metadata: 5 | name: nodeconfig-edge 6 | spec: 7 | match: 8 | nodeNamePatterns: 9 | - okd4-edge-edge-.* 10 | # nodeSelectorTerms: 11 | # - matchExpressions: 12 | # - key: kubernetes.io/hostname 13 | # operator: In 14 | # values: 15 | # - minikube 16 | # - key: beta.kubernetes.io/os 17 | # operator: In 18 | # values: 19 | # - linux 20 | # - matchExpressions: 21 | # - key: another-node-label-key 22 | # operator: Exists 23 | merge: 24 | labels: 25 | node-role.kubernetes.io/edge: "true" 26 | annotations: 27 | nodeconfig.config.snappcloud.io/managed: "true" 28 | taints: 29 | - key: node-role.kubernetes.io/edge 30 | effect: NoExecute 31 | --- 32 | apiVersion: config.snappcloud.io/v1alpha1 33 | kind: NodeConfig 34 | metadata: 35 | name: nodeconfig-build 36 | spec: 37 | match: 38 | nodeNamePatterns: 39 | - okd4-worker-build 40 | merge: 41 | labels: 42 | node-role.kubernetes.io/build: "true" 43 | taints: 44 | - key: node-role.kubernetes.io/build 45 | effect: NoExecute 46 | --- 47 | apiVersion: config.snappcloud.io/v1alpha1 48 | kind: NodeConfig 49 | metadata: 50 | name: spare-prefer-taint 51 | spec: 52 | match: 53 | nodeNamePatterns: 54 | - okd4-worker-worker-0 55 | - okd4-worker-worker-1 56 | merge: 57 | taints: 58 | - key: node-role.kubernetes.io/spare 59 | effect: PreferNoSchedule 60 | 61 | -------------------------------------------------------------------------------- /config/samples/kustomization.yaml: -------------------------------------------------------------------------------- 1 | ## Append samples you want in your CSV to this file as resources ## 2 | resources: 3 | - config_v1alpha1_nodeconfig.yaml 4 | - core_v1_node.yaml 5 | #+kubebuilder:scaffold:manifestskustomizesamples 6 | -------------------------------------------------------------------------------- /config/scorecard/bases/config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: scorecard.operatorframework.io/v1alpha3 2 | kind: Configuration 3 | metadata: 4 | name: config 5 | stages: 6 | - parallel: true 7 | tests: [] 8 | -------------------------------------------------------------------------------- /config/scorecard/kustomization.yaml: -------------------------------------------------------------------------------- 1 | resources: 2 | - bases/config.yaml 3 | patchesJson6902: 4 | - path: patches/basic.config.yaml 5 | target: 6 | group: scorecard.operatorframework.io 7 | version: v1alpha3 8 | kind: Configuration 9 | name: config 10 | - path: patches/olm.config.yaml 11 | target: 12 | group: scorecard.operatorframework.io 13 | version: v1alpha3 14 | kind: Configuration 15 | name: config 16 | #+kubebuilder:scaffold:patchesJson6902 17 | -------------------------------------------------------------------------------- /config/scorecard/patches/basic.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - basic-check-spec 7 | image: quay.io/operator-framework/scorecard-test:v1.10.0 8 | labels: 9 | suite: basic 10 | test: basic-check-spec-test 11 | -------------------------------------------------------------------------------- /config/scorecard/patches/olm.config.yaml: -------------------------------------------------------------------------------- 1 | - op: add 2 | path: /stages/0/tests/- 3 | value: 4 | entrypoint: 5 | - scorecard-test 6 | - olm-bundle-validation 7 | image: quay.io/operator-framework/scorecard-test:v1.10.0 8 | labels: 9 | suite: olm 10 | test: olm-bundle-validation-test 11 | - op: add 12 | path: /stages/0/tests/- 13 | value: 14 | entrypoint: 15 | - scorecard-test 16 | - olm-crds-have-validation 17 | image: quay.io/operator-framework/scorecard-test:v1.10.0 18 | labels: 19 | suite: olm 20 | test: olm-crds-have-validation-test 21 | - op: add 22 | path: /stages/0/tests/- 23 | value: 24 | entrypoint: 25 | - scorecard-test 26 | - olm-crds-have-resources 27 | image: quay.io/operator-framework/scorecard-test:v1.10.0 28 | labels: 29 | suite: olm 30 | test: olm-crds-have-resources-test 31 | - op: add 32 | path: /stages/0/tests/- 33 | value: 34 | entrypoint: 35 | - scorecard-test 36 | - olm-spec-descriptors 37 | image: quay.io/operator-framework/scorecard-test:v1.10.0 38 | labels: 39 | suite: olm 40 | test: olm-spec-descriptors-test 41 | - op: add 42 | path: /stages/0/tests/- 43 | value: 44 | entrypoint: 45 | - scorecard-test 46 | - olm-status-descriptors 47 | image: quay.io/operator-framework/scorecard-test:v1.10.0 48 | labels: 49 | suite: olm 50 | test: olm-status-descriptors-test 51 | -------------------------------------------------------------------------------- /controllers/node_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 | "context" 21 | "regexp" 22 | 23 | configv1alpha1 "github.com/snapp-incubator/node-config-operator/api/v1alpha1" 24 | corev1 "k8s.io/api/core/v1" 25 | "k8s.io/apimachinery/pkg/api/errors" 26 | "k8s.io/apimachinery/pkg/runtime" 27 | ctrl "sigs.k8s.io/controller-runtime" 28 | "sigs.k8s.io/controller-runtime/pkg/client" 29 | "sigs.k8s.io/controller-runtime/pkg/log" 30 | ) 31 | 32 | // NodeReconciler reconciles a Node object 33 | type NodeReconciler struct { 34 | client.Client 35 | Scheme *runtime.Scheme 36 | } 37 | 38 | //+kubebuilder:rbac:groups=core,resources=nodes,verbs=get;list;watch;create;update;patch;delete 39 | //+kubebuilder:rbac:groups=core,resources=nodes/status,verbs=get;update;patch 40 | //+kubebuilder:rbac:groups=core,resources=nodes/finalizers,verbs=update 41 | 42 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 43 | // move the current state of the cluster closer to the desired state. 44 | // TODO(user): Modify the Reconcile function to compare the state specified by 45 | // the Node object against the actual cluster state, and then 46 | // perform operations to make the cluster state reflect the state specified by 47 | // the user. 48 | // 49 | // For more details, check Reconcile and its Result here: 50 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile 51 | func (r *NodeReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 52 | logger := log.FromContext(ctx) 53 | 54 | // Lookup the route instance for this reconcile request 55 | node := &corev1.Node{} 56 | err := r.Get(ctx, req.NamespacedName, node) 57 | if err != nil { 58 | if errors.IsNotFound(err) { 59 | // Request object not found, could have been deleted after reconcile request. 60 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 61 | // Return and don't requeue 62 | logger.Info("Resource not found. Ignoring since object must be deleted") 63 | return ctrl.Result{}, nil 64 | } 65 | // Error reading the object - requeue the request. 66 | logger.Error(err, "Failed to get Objet") 67 | return ctrl.Result{}, err 68 | } 69 | 70 | // get the list of NodeConfigs 71 | ncList := &configv1alpha1.NodeConfigList{} 72 | err = r.List(ctx, ncList, &client.ListOptions{}) 73 | if err != nil { 74 | logger.Error(err, "Failed to list NodeConfigs") 75 | return ctrl.Result{}, err 76 | } 77 | // for NodeConfig in list, check if node matches any NodeConfig 78 | for _, nc := range ncList.Items { 79 | if nodeMatchNodeConfig(*node, nc.Spec.Match) { 80 | // Update the Node with the NodeConfig 81 | updateNode, match := nodeMergeNodeConfig(*node, nc.Spec.Merge) 82 | // if they are already the same, continue 83 | if match { 84 | logger.Info("Node has desired NodeConfig", "nodeconfig.Name", nc.Name) 85 | continue 86 | } 87 | logger.Info("Updating Node with NodeConfig", "nodeconfig.Name", nc.Name) 88 | err = r.Update(ctx, &updateNode) 89 | if err != nil { 90 | logger.Error(err, "Failed to update Node", "nodeconfig.Name", nc.Name) 91 | return ctrl.Result{}, err 92 | } 93 | 94 | } 95 | } 96 | 97 | return ctrl.Result{}, nil 98 | } 99 | 100 | // SetupWithManager sets up the controller with the Manager. 101 | func (r *NodeReconciler) SetupWithManager(mgr ctrl.Manager) error { 102 | return ctrl.NewControllerManagedBy(mgr). 103 | For(&corev1.Node{}). 104 | Complete(r) 105 | } 106 | 107 | func nodeMatchNodeConfig(node corev1.Node, m configv1alpha1.Match) bool { 108 | for _, nodeNamePattern := range m.NodeNamePatterns { 109 | pattern := "^" + nodeNamePattern + "$" 110 | match, _ := regexp.MatchString(pattern, node.Name) 111 | // Invalid regular expression, moving on to next rule 112 | if match { 113 | return true 114 | } 115 | } 116 | return false 117 | } 118 | 119 | func nodeMergeNodeConfig(node corev1.Node, m configv1alpha1.Merge) (updatedNode corev1.Node, match bool) { 120 | match = true 121 | if m.Labels != nil { 122 | for k, v := range m.Labels { 123 | if rv, ok := node.Labels[k]; !ok || rv != v { 124 | match = false 125 | node.Labels[k] = v 126 | } 127 | } 128 | } 129 | if m.Annotations != nil { 130 | for k, v := range m.Annotations { 131 | if rv, ok := node.Annotations[k]; !ok || rv != v { 132 | match = false 133 | node.Annotations[k] = v 134 | } 135 | } 136 | } 137 | 138 | if m.Taints != nil { 139 | newTaints := node.Spec.Taints 140 | for _, taint := range m.Taints { 141 | found := false 142 | for i, nodeTaint := range node.Spec.Taints { 143 | if nodeTaint.Key == taint.Key { 144 | found = true 145 | if nodeTaint.Value != taint.Value || nodeTaint.Effect != taint.Effect { 146 | match = false 147 | newTaints[i] = taint 148 | } 149 | } 150 | } 151 | if !found { 152 | match = false 153 | newTaints = append(newTaints, taint) 154 | } 155 | } 156 | node.Spec.Taints = newTaints 157 | } 158 | 159 | return node, match 160 | } 161 | -------------------------------------------------------------------------------- /controllers/nodeconfig_controller.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 | "context" 21 | "time" 22 | 23 | corev1 "k8s.io/api/core/v1" 24 | "k8s.io/apimachinery/pkg/api/errors" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | ctrl "sigs.k8s.io/controller-runtime" 27 | "sigs.k8s.io/controller-runtime/pkg/client" 28 | "sigs.k8s.io/controller-runtime/pkg/log" 29 | 30 | configv1alpha1 "github.com/snapp-incubator/node-config-operator/api/v1alpha1" 31 | ) 32 | 33 | // NodeConfigReconciler reconciles a NodeConfig object 34 | type NodeConfigReconciler struct { 35 | client.Client 36 | Scheme *runtime.Scheme 37 | } 38 | 39 | //+kubebuilder:rbac:groups=config.snappcloud.io,resources=nodeconfigs,verbs=get;list;watch;create;update;patch;delete 40 | //+kubebuilder:rbac:groups=config.snappcloud.io,resources=nodeconfigs/status,verbs=get;update;patch 41 | //+kubebuilder:rbac:groups=config.snappcloud.io,resources=nodeconfigs/finalizers,verbs=update 42 | 43 | // Reconcile is part of the main kubernetes reconciliation loop which aims to 44 | // move the current state of the cluster closer to the desired state. 45 | // TODO(user): Modify the Reconcile function to compare the state specified by 46 | // the NodeConfig object against the actual cluster state, and then 47 | // perform operations to make the cluster state reflect the state specified by 48 | // the user. 49 | // 50 | // For more details, check Reconcile and its Result here: 51 | // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.9.2/pkg/reconcile 52 | func (r *NodeConfigReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { 53 | logger := log.FromContext(ctx) 54 | 55 | // Lookup the route instance for this reconcile request 56 | nc := &configv1alpha1.NodeConfig{} 57 | err := r.Get(ctx, req.NamespacedName, nc) 58 | if err != nil { 59 | if errors.IsNotFound(err) { 60 | // Request object not found, could have been deleted after reconcile request. 61 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 62 | // Return and don't requeue 63 | logger.Info("Resource not found. Ignoring since object must be deleted") 64 | return ctrl.Result{}, nil 65 | } 66 | // Error reading the object - requeue the request. 67 | logger.Error(err, "Failed to get Objet") 68 | return ctrl.Result{}, err 69 | } 70 | 71 | // Trigger the node reconcile with a fake update on affected nodes 72 | nodeList := &corev1.NodeList{} 73 | err = r.List(ctx, nodeList, &client.ListOptions{}) 74 | if err != nil { 75 | logger.Error(err, "Failed to list Nodes") 76 | return ctrl.Result{}, err 77 | } 78 | for _, node := range nodeList.Items { 79 | if nodeMatchNodeConfig(node, nc.Spec.Match) { 80 | logger.Info("Updating Node", "node", node.Name) 81 | err = r.fakeUpdateNode(ctx, node) 82 | if err != nil { 83 | logger.Error(err, "Failed to update Node", "node", node.Name) 84 | return ctrl.Result{}, err 85 | } 86 | } 87 | } 88 | return ctrl.Result{}, nil 89 | } 90 | 91 | // SetupWithManager sets up the controller with the Manager. 92 | func (r *NodeConfigReconciler) SetupWithManager(mgr ctrl.Manager) error { 93 | return ctrl.NewControllerManagedBy(mgr). 94 | For(&configv1alpha1.NodeConfig{}). 95 | Complete(r) 96 | } 97 | 98 | func (r *NodeConfigReconciler) fakeUpdateNode(ctx context.Context, node corev1.Node) error { 99 | 100 | node.Annotations["nodeconfig.config.snappcloud.io/timestamp"] = time.Now().String() 101 | err := r.Update(ctx, &node) 102 | if err != nil { 103 | return err 104 | } 105 | return nil 106 | } 107 | -------------------------------------------------------------------------------- /controllers/suite_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 | "path/filepath" 21 | "testing" 22 | 23 | . "github.com/onsi/ginkgo" 24 | . "github.com/onsi/gomega" 25 | "k8s.io/client-go/kubernetes/scheme" 26 | "sigs.k8s.io/controller-runtime/pkg/client" 27 | "sigs.k8s.io/controller-runtime/pkg/envtest" 28 | "sigs.k8s.io/controller-runtime/pkg/envtest/printer" 29 | logf "sigs.k8s.io/controller-runtime/pkg/log" 30 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 31 | 32 | corev1 "k8s.io/api/core/v1" 33 | 34 | configv1alpha1 "github.com/snapp-incubator/node-config-operator/api/v1alpha1" 35 | //+kubebuilder:scaffold:imports 36 | ) 37 | 38 | // These tests use Ginkgo (BDD-style Go testing framework). Refer to 39 | // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. 40 | 41 | var k8sClient client.Client 42 | var testEnv *envtest.Environment 43 | 44 | func TestAPIs(t *testing.T) { 45 | RegisterFailHandler(Fail) 46 | 47 | RunSpecsWithDefaultAndCustomReporters(t, 48 | "Controller Suite", 49 | []Reporter{printer.NewlineReporter{}}) 50 | } 51 | 52 | var _ = BeforeSuite(func() { 53 | logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) 54 | 55 | By("bootstrapping test environment") 56 | testEnv = &envtest.Environment{ 57 | CRDDirectoryPaths: []string{filepath.Join("..", "config", "crd", "bases")}, 58 | ErrorIfCRDPathMissing: true, 59 | } 60 | 61 | cfg, err := testEnv.Start() 62 | Expect(err).NotTo(HaveOccurred()) 63 | Expect(cfg).NotTo(BeNil()) 64 | 65 | err = configv1alpha1.AddToScheme(scheme.Scheme) 66 | Expect(err).NotTo(HaveOccurred()) 67 | 68 | err = corev1.AddToScheme(scheme.Scheme) 69 | Expect(err).NotTo(HaveOccurred()) 70 | 71 | //+kubebuilder:scaffold:scheme 72 | 73 | k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) 74 | Expect(err).NotTo(HaveOccurred()) 75 | Expect(k8sClient).NotTo(BeNil()) 76 | 77 | }, 60) 78 | 79 | var _ = AfterSuite(func() { 80 | By("tearing down the test environment") 81 | err := testEnv.Stop() 82 | Expect(err).NotTo(HaveOccurred()) 83 | }) 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/snapp-incubator/node-config-operator 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/imdario/mergo v0.3.12 // indirect 7 | github.com/onsi/ginkgo v1.16.5 8 | github.com/onsi/gomega v1.33.1 9 | github.com/rogpeppe/go-internal v1.11.0 // indirect 10 | k8s.io/api v0.28.3 11 | k8s.io/apimachinery v0.28.3 12 | k8s.io/client-go v0.28.3 13 | sigs.k8s.io/controller-runtime v0.16.3 14 | ) 15 | -------------------------------------------------------------------------------- /hack/boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 | */ -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021. 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 main 18 | 19 | import ( 20 | "flag" 21 | "os" 22 | 23 | // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 24 | // to ensure that exec-entrypoint and run can make use of them. 25 | _ "k8s.io/client-go/plugin/pkg/client/auth" 26 | 27 | "k8s.io/apimachinery/pkg/runtime" 28 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 29 | clientgoscheme "k8s.io/client-go/kubernetes/scheme" 30 | ctrl "sigs.k8s.io/controller-runtime" 31 | "sigs.k8s.io/controller-runtime/pkg/healthz" 32 | "sigs.k8s.io/controller-runtime/pkg/log/zap" 33 | 34 | configv1alpha1 "github.com/snapp-incubator/node-config-operator/api/v1alpha1" 35 | "github.com/snapp-incubator/node-config-operator/controllers" 36 | //+kubebuilder:scaffold:imports 37 | ) 38 | 39 | var ( 40 | scheme = runtime.NewScheme() 41 | setupLog = ctrl.Log.WithName("setup") 42 | ) 43 | 44 | func init() { 45 | utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 46 | 47 | utilruntime.Must(configv1alpha1.AddToScheme(scheme)) 48 | //+kubebuilder:scaffold:scheme 49 | } 50 | 51 | func main() { 52 | var metricsAddr string 53 | var enableLeaderElection bool 54 | var probeAddr string 55 | flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.") 56 | flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 57 | flag.BoolVar(&enableLeaderElection, "leader-elect", false, 58 | "Enable leader election for controller manager. "+ 59 | "Enabling this will ensure there is only one active controller manager.") 60 | opts := zap.Options{ 61 | Development: true, 62 | } 63 | opts.BindFlags(flag.CommandLine) 64 | flag.Parse() 65 | 66 | ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 67 | 68 | mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 69 | Scheme: scheme, 70 | MetricsBindAddress: metricsAddr, 71 | Port: 9443, 72 | HealthProbeBindAddress: probeAddr, 73 | LeaderElection: enableLeaderElection, 74 | LeaderElectionID: "97a91c42.snappcloud.io", 75 | }) 76 | if err != nil { 77 | setupLog.Error(err, "unable to start manager") 78 | os.Exit(1) 79 | } 80 | 81 | if err = (&controllers.NodeConfigReconciler{ 82 | Client: mgr.GetClient(), 83 | Scheme: mgr.GetScheme(), 84 | }).SetupWithManager(mgr); err != nil { 85 | setupLog.Error(err, "unable to create controller", "controller", "NodeConfig") 86 | os.Exit(1) 87 | } 88 | if err = (&controllers.NodeReconciler{ 89 | Client: mgr.GetClient(), 90 | Scheme: mgr.GetScheme(), 91 | }).SetupWithManager(mgr); err != nil { 92 | setupLog.Error(err, "unable to create controller", "controller", "Node") 93 | os.Exit(1) 94 | } 95 | //+kubebuilder:scaffold:builder 96 | 97 | if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 98 | setupLog.Error(err, "unable to set up health check") 99 | os.Exit(1) 100 | } 101 | if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 102 | setupLog.Error(err, "unable to set up ready check") 103 | os.Exit(1) 104 | } 105 | 106 | setupLog.Info("starting manager") 107 | if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 108 | setupLog.Error(err, "problem running manager") 109 | os.Exit(1) 110 | } 111 | } 112 | --------------------------------------------------------------------------------