this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Init

Louis Taylor 3e4cffd4

+2644
+25
.devcontainer/devcontainer.json
··· 1 + { 2 + "name": "Kubebuilder DevContainer", 3 + "image": "golang:1.24", 4 + "features": { 5 + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, 6 + "ghcr.io/devcontainers/features/git:1": {} 7 + }, 8 + 9 + "runArgs": ["--network=host"], 10 + 11 + "customizations": { 12 + "vscode": { 13 + "settings": { 14 + "terminal.integrated.shell.linux": "/bin/bash" 15 + }, 16 + "extensions": [ 17 + "ms-kubernetes-tools.vscode-kubernetes-tools", 18 + "ms-azuretools.vscode-docker" 19 + ] 20 + } 21 + }, 22 + 23 + "onCreateCommand": "bash .devcontainer/post-install.sh" 24 + } 25 +
+23
.devcontainer/post-install.sh
··· 1 + #!/bin/bash 2 + set -x 3 + 4 + curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 5 + chmod +x ./kind 6 + mv ./kind /usr/local/bin/kind 7 + 8 + curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/linux/amd64 9 + chmod +x kubebuilder 10 + mv kubebuilder /usr/local/bin/ 11 + 12 + KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt) 13 + curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl" 14 + chmod +x kubectl 15 + mv kubectl /usr/local/bin/kubectl 16 + 17 + docker network create -d=bridge --subnet=172.19.0.0/24 kind 18 + 19 + kind version 20 + kubebuilder version 21 + docker --version 22 + go version 23 + kubectl version --client
+3
.dockerignore
··· 1 + # More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file 2 + # Ignore build and test binaries. 3 + bin/
+23
.github/workflows/lint.yml
··· 1 + name: Lint 2 + 3 + on: 4 + push: 5 + pull_request: 6 + 7 + jobs: 8 + lint: 9 + name: Run on Ubuntu 10 + runs-on: ubuntu-latest 11 + steps: 12 + - name: Clone the code 13 + uses: actions/checkout@v4 14 + 15 + - name: Setup Go 16 + uses: actions/setup-go@v5 17 + with: 18 + go-version-file: go.mod 19 + 20 + - name: Run linter 21 + uses: golangci/golangci-lint-action@v8 22 + with: 23 + version: v2.1.6
+32
.github/workflows/test-e2e.yml
··· 1 + name: E2E Tests 2 + 3 + on: 4 + push: 5 + pull_request: 6 + 7 + jobs: 8 + test-e2e: 9 + name: Run on Ubuntu 10 + runs-on: ubuntu-latest 11 + steps: 12 + - name: Clone the code 13 + uses: actions/checkout@v4 14 + 15 + - name: Setup Go 16 + uses: actions/setup-go@v5 17 + with: 18 + go-version-file: go.mod 19 + 20 + - name: Install the latest version of kind 21 + run: | 22 + curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 23 + chmod +x ./kind 24 + sudo mv ./kind /usr/local/bin/kind 25 + 26 + - name: Verify kind installation 27 + run: kind version 28 + 29 + - name: Running Test e2e 30 + run: | 31 + go mod tidy 32 + make test-e2e
+23
.github/workflows/test.yml
··· 1 + name: Tests 2 + 3 + on: 4 + push: 5 + pull_request: 6 + 7 + jobs: 8 + test: 9 + name: Run on Ubuntu 10 + runs-on: ubuntu-latest 11 + steps: 12 + - name: Clone the code 13 + uses: actions/checkout@v4 14 + 15 + - name: Setup Go 16 + uses: actions/setup-go@v5 17 + with: 18 + go-version-file: go.mod 19 + 20 + - name: Running Tests 21 + run: | 22 + go mod tidy 23 + make test
+27
.gitignore
··· 1 + # Binaries for programs and plugins 2 + *.exe 3 + *.exe~ 4 + *.dll 5 + *.so 6 + *.dylib 7 + bin/* 8 + Dockerfile.cross 9 + 10 + # Test binary, built with `go test -c` 11 + *.test 12 + 13 + # Output of the go coverage tool, specifically when used with LiteIDE 14 + *.out 15 + 16 + # Go workspace file 17 + go.work 18 + 19 + # Kubernetes Generated files - skip generated files, except for vendored files 20 + !vendor/**/zz_generated.* 21 + 22 + # editor and IDE paraphernalia 23 + .idea 24 + .vscode 25 + *.swp 26 + *.swo 27 + *~
+52
.golangci.yml
··· 1 + version: "2" 2 + run: 3 + allow-parallel-runners: true 4 + linters: 5 + default: none 6 + enable: 7 + - copyloopvar 8 + - dupl 9 + - errcheck 10 + - ginkgolinter 11 + - goconst 12 + - gocyclo 13 + - govet 14 + - ineffassign 15 + - lll 16 + - misspell 17 + - nakedret 18 + - prealloc 19 + - revive 20 + - staticcheck 21 + - unconvert 22 + - unparam 23 + - unused 24 + settings: 25 + revive: 26 + rules: 27 + - name: comment-spacings 28 + - name: import-shadowing 29 + exclusions: 30 + generated: lax 31 + rules: 32 + - linters: 33 + - lll 34 + path: api/* 35 + - linters: 36 + - dupl 37 + - lll 38 + path: internal/* 39 + paths: 40 + - third_party$ 41 + - builtin$ 42 + - examples$ 43 + formatters: 44 + enable: 45 + - gofmt 46 + - goimports 47 + exclusions: 48 + generated: lax 49 + paths: 50 + - third_party$ 51 + - builtin$ 52 + - examples$
+33
Dockerfile
··· 1 + # Build the manager binary 2 + FROM golang:1.24 AS builder 3 + ARG TARGETOS 4 + ARG TARGETARCH 5 + 6 + WORKDIR /workspace 7 + # Copy the Go Modules manifests 8 + COPY go.mod go.mod 9 + COPY go.sum go.sum 10 + # cache deps before building and copying source so that we don't need to re-download as much 11 + # and so that source changes don't invalidate our downloaded layer 12 + RUN go mod download 13 + 14 + # Copy the go source 15 + COPY cmd/main.go cmd/main.go 16 + COPY api/ api/ 17 + COPY internal/ internal/ 18 + 19 + # Build 20 + # the GOARCH has not a default value to allow the binary be built according to the host where the command 21 + # was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO 22 + # the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore, 23 + # by leaving it empty we can ensure that the container and binary shipped on it will have the same platform. 24 + RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go 25 + 26 + # Use distroless as minimal base image to package the manager binary 27 + # Refer to https://github.com/GoogleContainerTools/distroless for more details 28 + FROM gcr.io/distroless/static:nonroot 29 + WORKDIR / 30 + COPY --from=builder /workspace/manager . 31 + USER 65532:65532 32 + 33 + ENTRYPOINT ["/manager"]
+238
Makefile
··· 1 + # Image URL to use all building/pushing image targets 2 + IMG ?= controller:latest 3 + 4 + # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) 5 + ifeq (,$(shell go env GOBIN)) 6 + GOBIN=$(shell go env GOPATH)/bin 7 + else 8 + GOBIN=$(shell go env GOBIN) 9 + endif 10 + 11 + # CONTAINER_TOOL defines the container tool to be used for building images. 12 + # Be aware that the target commands are only tested with Docker which is 13 + # scaffolded by default. However, you might want to replace it to use other 14 + # tools. (i.e. podman) 15 + CONTAINER_TOOL ?= docker 16 + 17 + # Setting SHELL to bash allows bash commands to be executed by recipes. 18 + # Options are set to exit when a recipe line exits non-zero or a piped command fails. 19 + SHELL = /usr/bin/env bash -o pipefail 20 + .SHELLFLAGS = -ec 21 + 22 + .PHONY: all 23 + all: build 24 + 25 + ##@ General 26 + 27 + # The help target prints out all targets with their descriptions organized 28 + # beneath their categories. The categories are represented by '##@' and the 29 + # target descriptions by '##'. The awk command is responsible for reading the 30 + # entire set of makefiles included in this invocation, looking for lines of the 31 + # file as xyz: ## something, and then pretty-format the target and help. Then, 32 + # if there's a line with ##@ something, that gets pretty-printed as a category. 33 + # More info on the usage of ANSI control characters for terminal formatting: 34 + # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters 35 + # More info on the awk command: 36 + # http://linuxcommand.org/lc3_adv_awk.php 37 + 38 + .PHONY: help 39 + help: ## Display this help. 40 + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\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) 41 + 42 + ##@ Development 43 + 44 + .PHONY: manifests 45 + manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. 46 + $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases 47 + 48 + .PHONY: generate 49 + generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. 50 + $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." 51 + 52 + .PHONY: fmt 53 + fmt: ## Run go fmt against code. 54 + go fmt ./... 55 + 56 + .PHONY: vet 57 + vet: ## Run go vet against code. 58 + go vet ./... 59 + 60 + .PHONY: test 61 + test: manifests generate fmt vet setup-envtest ## Run tests. 62 + KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out 63 + 64 + # TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'. 65 + # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. 66 + # CertManager is installed by default; skip with: 67 + # - CERT_MANAGER_INSTALL_SKIP=true 68 + KIND_CLUSTER ?= pist-test-e2e 69 + 70 + .PHONY: setup-test-e2e 71 + setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist 72 + @command -v $(KIND) >/dev/null 2>&1 || { \ 73 + echo "Kind is not installed. Please install Kind manually."; \ 74 + exit 1; \ 75 + } 76 + @case "$$($(KIND) get clusters)" in \ 77 + *"$(KIND_CLUSTER)"*) \ 78 + echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \ 79 + *) \ 80 + echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \ 81 + $(KIND) create cluster --name $(KIND_CLUSTER) ;; \ 82 + esac 83 + 84 + .PHONY: test-e2e 85 + test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. 86 + KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v 87 + $(MAKE) cleanup-test-e2e 88 + 89 + .PHONY: cleanup-test-e2e 90 + cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests 91 + @$(KIND) delete cluster --name $(KIND_CLUSTER) 92 + 93 + .PHONY: lint 94 + lint: golangci-lint ## Run golangci-lint linter 95 + $(GOLANGCI_LINT) run 96 + 97 + .PHONY: lint-fix 98 + lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes 99 + $(GOLANGCI_LINT) run --fix 100 + 101 + .PHONY: lint-config 102 + lint-config: golangci-lint ## Verify golangci-lint linter configuration 103 + $(GOLANGCI_LINT) config verify 104 + 105 + ##@ Build 106 + 107 + .PHONY: build 108 + build: manifests generate fmt vet ## Build manager binary. 109 + go build -o bin/manager cmd/main.go 110 + 111 + .PHONY: run 112 + run: manifests generate fmt vet ## Run a controller from your host. 113 + go run ./cmd/main.go 114 + 115 + # If you wish to build the manager image targeting other platforms you can use the --platform flag. 116 + # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. 117 + # More info: https://docs.docker.com/develop/develop-images/build_enhancements/ 118 + .PHONY: docker-build 119 + docker-build: ## Build docker image with the manager. 120 + $(CONTAINER_TOOL) build -t ${IMG} . 121 + 122 + .PHONY: docker-push 123 + docker-push: ## Push docker image with the manager. 124 + $(CONTAINER_TOOL) push ${IMG} 125 + 126 + # PLATFORMS defines the target platforms for the manager image be built to provide support to multiple 127 + # architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: 128 + # - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ 129 + # - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ 130 + # - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=<myregistry/image:<tag>> then the export will fail) 131 + # To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. 132 + PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le 133 + .PHONY: docker-buildx 134 + docker-buildx: ## Build and push docker image for the manager for cross-platform support 135 + # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile 136 + sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross 137 + - $(CONTAINER_TOOL) buildx create --name pist-builder 138 + $(CONTAINER_TOOL) buildx use pist-builder 139 + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . 140 + - $(CONTAINER_TOOL) buildx rm pist-builder 141 + rm Dockerfile.cross 142 + 143 + .PHONY: build-installer 144 + build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. 145 + mkdir -p dist 146 + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 147 + $(KUSTOMIZE) build config/default > dist/install.yaml 148 + 149 + ##@ Deployment 150 + 151 + ifndef ignore-not-found 152 + ignore-not-found = false 153 + endif 154 + 155 + .PHONY: install 156 + install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. 157 + $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - 158 + 159 + .PHONY: uninstall 160 + uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 161 + $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - 162 + 163 + .PHONY: deploy 164 + deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. 165 + cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} 166 + $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - 167 + 168 + .PHONY: undeploy 169 + undeploy: kustomize ## Undeploy controller from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion. 170 + $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - 171 + 172 + ##@ Dependencies 173 + 174 + ## Location to install dependencies to 175 + LOCALBIN ?= $(shell pwd)/bin 176 + $(LOCALBIN): 177 + mkdir -p $(LOCALBIN) 178 + 179 + ## Tool Binaries 180 + KUBECTL ?= kubectl 181 + KIND ?= kind 182 + KUSTOMIZE ?= $(LOCALBIN)/kustomize 183 + CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen 184 + ENVTEST ?= $(LOCALBIN)/setup-envtest 185 + GOLANGCI_LINT = $(LOCALBIN)/golangci-lint 186 + 187 + ## Tool Versions 188 + KUSTOMIZE_VERSION ?= v5.6.0 189 + CONTROLLER_TOOLS_VERSION ?= v0.18.0 190 + #ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) 191 + ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') 192 + #ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) 193 + ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') 194 + GOLANGCI_LINT_VERSION ?= v2.1.6 195 + 196 + .PHONY: kustomize 197 + kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. 198 + $(KUSTOMIZE): $(LOCALBIN) 199 + $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) 200 + 201 + .PHONY: controller-gen 202 + controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. 203 + $(CONTROLLER_GEN): $(LOCALBIN) 204 + $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) 205 + 206 + .PHONY: setup-envtest 207 + setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory. 208 + @echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..." 209 + @$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \ 210 + echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \ 211 + exit 1; \ 212 + } 213 + 214 + .PHONY: envtest 215 + envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. 216 + $(ENVTEST): $(LOCALBIN) 217 + $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) 218 + 219 + .PHONY: golangci-lint 220 + golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. 221 + $(GOLANGCI_LINT): $(LOCALBIN) 222 + $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/v2/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) 223 + 224 + # go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist 225 + # $1 - target path with name of binary 226 + # $2 - package url which can be installed 227 + # $3 - specific version of package 228 + define go-install-tool 229 + @[ -f "$(1)-$(3)" ] || { \ 230 + set -e; \ 231 + package=$(2)@$(3) ;\ 232 + echo "Downloading $${package}" ;\ 233 + rm -f $(1) || true ;\ 234 + GOBIN=$(LOCALBIN) go install $${package} ;\ 235 + mv $(1) $(1)-$(3) ;\ 236 + } ;\ 237 + ln -sf $(1)-$(3) $(1) 238 + endef
+11
PROJECT
··· 1 + # Code generated by tool. DO NOT EDIT. 2 + # This file is used to track the info used to scaffold your project 3 + # and allow the plugins properly work. 4 + # More info: https://book.kubebuilder.io/reference/project-config.html 5 + cliVersion: v4.7.1 6 + domain: kgz.sh 7 + layout: 8 + - go.kubebuilder.io/v4 9 + projectName: pist 10 + repo: github.com/kragniz/pist 11 + version: "3"
+135
README.md
··· 1 + # pist 2 + // TODO(user): Add simple overview of use/purpose 3 + 4 + ## Description 5 + // TODO(user): An in-depth paragraph about your project and overview of use 6 + 7 + ## Getting Started 8 + 9 + ### Prerequisites 10 + - go version v1.24.0+ 11 + - docker version 17.03+. 12 + - kubectl version v1.11.3+. 13 + - Access to a Kubernetes v1.11.3+ cluster. 14 + 15 + ### To Deploy on the cluster 16 + **Build and push your image to the location specified by `IMG`:** 17 + 18 + ```sh 19 + make docker-build docker-push IMG=<some-registry>/pist:tag 20 + ``` 21 + 22 + **NOTE:** This image ought to be published in the personal registry you specified. 23 + And it is required to have access to pull the image from the working environment. 24 + Make sure you have the proper permission to the registry if the above commands don’t work. 25 + 26 + **Install the CRDs into the cluster:** 27 + 28 + ```sh 29 + make install 30 + ``` 31 + 32 + **Deploy the Manager to the cluster with the image specified by `IMG`:** 33 + 34 + ```sh 35 + make deploy IMG=<some-registry>/pist:tag 36 + ``` 37 + 38 + > **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin 39 + privileges or be logged in as admin. 40 + 41 + **Create instances of your solution** 42 + You can apply the samples (examples) from the config/sample: 43 + 44 + ```sh 45 + kubectl apply -k config/samples/ 46 + ``` 47 + 48 + >**NOTE**: Ensure that the samples has default values to test it out. 49 + 50 + ### To Uninstall 51 + **Delete the instances (CRs) from the cluster:** 52 + 53 + ```sh 54 + kubectl delete -k config/samples/ 55 + ``` 56 + 57 + **Delete the APIs(CRDs) from the cluster:** 58 + 59 + ```sh 60 + make uninstall 61 + ``` 62 + 63 + **UnDeploy the controller from the cluster:** 64 + 65 + ```sh 66 + make undeploy 67 + ``` 68 + 69 + ## Project Distribution 70 + 71 + Following the options to release and provide this solution to the users. 72 + 73 + ### By providing a bundle with all YAML files 74 + 75 + 1. Build the installer for the image built and published in the registry: 76 + 77 + ```sh 78 + make build-installer IMG=<some-registry>/pist:tag 79 + ``` 80 + 81 + **NOTE:** The makefile target mentioned above generates an 'install.yaml' 82 + file in the dist directory. This file contains all the resources built 83 + with Kustomize, which are necessary to install this project without its 84 + dependencies. 85 + 86 + 2. Using the installer 87 + 88 + Users can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install 89 + the project, i.e.: 90 + 91 + ```sh 92 + kubectl apply -f https://raw.githubusercontent.com/<org>/pist/<tag or branch>/dist/install.yaml 93 + ``` 94 + 95 + ### By providing a Helm Chart 96 + 97 + 1. Build the chart using the optional helm plugin 98 + 99 + ```sh 100 + kubebuilder edit --plugins=helm/v1-alpha 101 + ``` 102 + 103 + 2. See that a chart was generated under 'dist/chart', and users 104 + can obtain this solution from there. 105 + 106 + **NOTE:** If you change the project, you need to update the Helm Chart 107 + using the same command above to sync the latest changes. Furthermore, 108 + if you create webhooks, you need to use the above command with 109 + the '--force' flag and manually ensure that any custom configuration 110 + previously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml' 111 + is manually re-applied afterwards. 112 + 113 + ## Contributing 114 + // TODO(user): Add detailed information on how you would like others to contribute to this project 115 + 116 + **NOTE:** Run `make help` for more information on all potential `make` targets 117 + 118 + More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html) 119 + 120 + ## License 121 + 122 + Copyright 2026. 123 + 124 + Licensed under the Apache License, Version 2.0 (the "License"); 125 + you may not use this file except in compliance with the License. 126 + You may obtain a copy of the License at 127 + 128 + http://www.apache.org/licenses/LICENSE-2.0 129 + 130 + Unless required by applicable law or agreed to in writing, software 131 + distributed under the License is distributed on an "AS IS" BASIS, 132 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133 + See the License for the specific language governing permissions and 134 + limitations under the License. 135 +
+233
cmd/main.go
··· 1 + /* 2 + Copyright 2026. 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 + "crypto/tls" 21 + "flag" 22 + "os" 23 + "path/filepath" 24 + 25 + // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) 26 + // to ensure that exec-entrypoint and run can make use of them. 27 + _ "k8s.io/client-go/plugin/pkg/client/auth" 28 + 29 + "k8s.io/apimachinery/pkg/runtime" 30 + utilruntime "k8s.io/apimachinery/pkg/util/runtime" 31 + clientgoscheme "k8s.io/client-go/kubernetes/scheme" 32 + ctrl "sigs.k8s.io/controller-runtime" 33 + "sigs.k8s.io/controller-runtime/pkg/certwatcher" 34 + "sigs.k8s.io/controller-runtime/pkg/healthz" 35 + "sigs.k8s.io/controller-runtime/pkg/log/zap" 36 + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" 37 + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 38 + "sigs.k8s.io/controller-runtime/pkg/webhook" 39 + // +kubebuilder:scaffold:imports 40 + ) 41 + 42 + var ( 43 + scheme = runtime.NewScheme() 44 + setupLog = ctrl.Log.WithName("setup") 45 + ) 46 + 47 + func init() { 48 + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) 49 + 50 + // +kubebuilder:scaffold:scheme 51 + } 52 + 53 + // nolint:gocyclo 54 + func main() { 55 + var metricsAddr string 56 + var metricsCertPath, metricsCertName, metricsCertKey string 57 + var webhookCertPath, webhookCertName, webhookCertKey string 58 + var enableLeaderElection bool 59 + var probeAddr string 60 + var secureMetrics bool 61 + var enableHTTP2 bool 62 + var tlsOpts []func(*tls.Config) 63 + flag.StringVar(&metricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+ 64 + "Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.") 65 + flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.") 66 + flag.BoolVar(&enableLeaderElection, "leader-elect", false, 67 + "Enable leader election for controller manager. "+ 68 + "Enabling this will ensure there is only one active controller manager.") 69 + flag.BoolVar(&secureMetrics, "metrics-secure", true, 70 + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.") 71 + flag.StringVar(&webhookCertPath, "webhook-cert-path", "", "The directory that contains the webhook certificate.") 72 + flag.StringVar(&webhookCertName, "webhook-cert-name", "tls.crt", "The name of the webhook certificate file.") 73 + flag.StringVar(&webhookCertKey, "webhook-cert-key", "tls.key", "The name of the webhook key file.") 74 + flag.StringVar(&metricsCertPath, "metrics-cert-path", "", 75 + "The directory that contains the metrics server certificate.") 76 + flag.StringVar(&metricsCertName, "metrics-cert-name", "tls.crt", "The name of the metrics server certificate file.") 77 + flag.StringVar(&metricsCertKey, "metrics-cert-key", "tls.key", "The name of the metrics server key file.") 78 + flag.BoolVar(&enableHTTP2, "enable-http2", false, 79 + "If set, HTTP/2 will be enabled for the metrics and webhook servers") 80 + opts := zap.Options{ 81 + Development: true, 82 + } 83 + opts.BindFlags(flag.CommandLine) 84 + flag.Parse() 85 + 86 + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) 87 + 88 + // if the enable-http2 flag is false (the default), http/2 should be disabled 89 + // due to its vulnerabilities. More specifically, disabling http/2 will 90 + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and 91 + // Rapid Reset CVEs. For more information see: 92 + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 93 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 94 + disableHTTP2 := func(c *tls.Config) { 95 + setupLog.Info("disabling http/2") 96 + c.NextProtos = []string{"http/1.1"} 97 + } 98 + 99 + if !enableHTTP2 { 100 + tlsOpts = append(tlsOpts, disableHTTP2) 101 + } 102 + 103 + // Create watchers for metrics and webhooks certificates 104 + var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher 105 + 106 + // Initial webhook TLS options 107 + webhookTLSOpts := tlsOpts 108 + 109 + if len(webhookCertPath) > 0 { 110 + setupLog.Info("Initializing webhook certificate watcher using provided certificates", 111 + "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) 112 + 113 + var err error 114 + webhookCertWatcher, err = certwatcher.New( 115 + filepath.Join(webhookCertPath, webhookCertName), 116 + filepath.Join(webhookCertPath, webhookCertKey), 117 + ) 118 + if err != nil { 119 + setupLog.Error(err, "Failed to initialize webhook certificate watcher") 120 + os.Exit(1) 121 + } 122 + 123 + webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { 124 + config.GetCertificate = webhookCertWatcher.GetCertificate 125 + }) 126 + } 127 + 128 + webhookServer := webhook.NewServer(webhook.Options{ 129 + TLSOpts: webhookTLSOpts, 130 + }) 131 + 132 + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. 133 + // More info: 134 + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/server 135 + // - https://book.kubebuilder.io/reference/metrics.html 136 + metricsServerOptions := metricsserver.Options{ 137 + BindAddress: metricsAddr, 138 + SecureServing: secureMetrics, 139 + TLSOpts: tlsOpts, 140 + } 141 + 142 + if secureMetrics { 143 + // FilterProvider is used to protect the metrics endpoint with authn/authz. 144 + // These configurations ensure that only authorized users and service accounts 145 + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: 146 + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.21.0/pkg/metrics/filters#WithAuthenticationAndAuthorization 147 + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization 148 + } 149 + 150 + // If the certificate is not specified, controller-runtime will automatically 151 + // generate self-signed certificates for the metrics server. While convenient for development and testing, 152 + // this setup is not recommended for production. 153 + // 154 + // TODO(user): If you enable certManager, uncomment the following lines: 155 + // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates 156 + // managed by cert-manager for the metrics server. 157 + // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. 158 + if len(metricsCertPath) > 0 { 159 + setupLog.Info("Initializing metrics certificate watcher using provided certificates", 160 + "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey) 161 + 162 + var err error 163 + metricsCertWatcher, err = certwatcher.New( 164 + filepath.Join(metricsCertPath, metricsCertName), 165 + filepath.Join(metricsCertPath, metricsCertKey), 166 + ) 167 + if err != nil { 168 + setupLog.Error(err, "to initialize metrics certificate watcher", "error", err) 169 + os.Exit(1) 170 + } 171 + 172 + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { 173 + config.GetCertificate = metricsCertWatcher.GetCertificate 174 + }) 175 + } 176 + 177 + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ 178 + Scheme: scheme, 179 + Metrics: metricsServerOptions, 180 + WebhookServer: webhookServer, 181 + HealthProbeBindAddress: probeAddr, 182 + LeaderElection: enableLeaderElection, 183 + LeaderElectionID: "28c041a5.kgz.sh", 184 + // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily 185 + // when the Manager ends. This requires the binary to immediately end when the 186 + // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly 187 + // speeds up voluntary leader transitions as the new leader don't have to wait 188 + // LeaseDuration time first. 189 + // 190 + // In the default scaffold provided, the program ends immediately after 191 + // the manager stops, so would be fine to enable this option. However, 192 + // if you are doing or is intended to do any operation such as perform cleanups 193 + // after the manager stops then its usage might be unsafe. 194 + // LeaderElectionReleaseOnCancel: true, 195 + }) 196 + if err != nil { 197 + setupLog.Error(err, "unable to start manager") 198 + os.Exit(1) 199 + } 200 + 201 + // +kubebuilder:scaffold:builder 202 + 203 + if metricsCertWatcher != nil { 204 + setupLog.Info("Adding metrics certificate watcher to manager") 205 + if err := mgr.Add(metricsCertWatcher); err != nil { 206 + setupLog.Error(err, "unable to add metrics certificate watcher to manager") 207 + os.Exit(1) 208 + } 209 + } 210 + 211 + if webhookCertWatcher != nil { 212 + setupLog.Info("Adding webhook certificate watcher to manager") 213 + if err := mgr.Add(webhookCertWatcher); err != nil { 214 + setupLog.Error(err, "unable to add webhook certificate watcher to manager") 215 + os.Exit(1) 216 + } 217 + } 218 + 219 + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { 220 + setupLog.Error(err, "unable to set up health check") 221 + os.Exit(1) 222 + } 223 + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { 224 + setupLog.Error(err, "unable to set up ready check") 225 + os.Exit(1) 226 + } 227 + 228 + setupLog.Info("starting manager") 229 + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { 230 + setupLog.Error(err, "problem running manager") 231 + os.Exit(1) 232 + } 233 + }
+30
config/default/cert_metrics_manager_patch.yaml
··· 1 + # This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs. 2 + 3 + # Add the volumeMount for the metrics-server certs 4 + - op: add 5 + path: /spec/template/spec/containers/0/volumeMounts/- 6 + value: 7 + mountPath: /tmp/k8s-metrics-server/metrics-certs 8 + name: metrics-certs 9 + readOnly: true 10 + 11 + # Add the --metrics-cert-path argument for the metrics server 12 + - op: add 13 + path: /spec/template/spec/containers/0/args/- 14 + value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs 15 + 16 + # Add the metrics-server certs volume configuration 17 + - op: add 18 + path: /spec/template/spec/volumes/- 19 + value: 20 + name: metrics-certs 21 + secret: 22 + secretName: metrics-server-cert 23 + optional: false 24 + items: 25 + - key: ca.crt 26 + path: ca.crt 27 + - key: tls.crt 28 + path: tls.crt 29 + - key: tls.key 30 + path: tls.key
+234
config/default/kustomization.yaml
··· 1 + # Adds namespace to all resources. 2 + namespace: pist-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: pist- 10 + 11 + # Labels to add to all resources and selectors. 12 + #labels: 13 + #- includeSelectors: true 14 + # pairs: 15 + # someName: someValue 16 + 17 + resources: 18 + #- ../crd 19 + - ../rbac 20 + - ../manager 21 + # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 22 + # crd/kustomization.yaml 23 + #- ../webhook 24 + # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. 25 + #- ../certmanager 26 + # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. 27 + #- ../prometheus 28 + # [METRICS] Expose the controller manager metrics service. 29 + - metrics_service.yaml 30 + # [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy. 31 + # Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics. 32 + # Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will 33 + # be able to communicate with the Webhook Server. 34 + #- ../network-policy 35 + 36 + # Uncomment the patches line if you enable Metrics 37 + patches: 38 + # [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443. 39 + # More info: https://book.kubebuilder.io/reference/metrics 40 + - path: manager_metrics_patch.yaml 41 + target: 42 + kind: Deployment 43 + 44 + # Uncomment the patches line if you enable Metrics and CertManager 45 + # [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line. 46 + # This patch will protect the metrics with certManager self-signed certs. 47 + #- path: cert_metrics_manager_patch.yaml 48 + # target: 49 + # kind: Deployment 50 + 51 + # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in 52 + # crd/kustomization.yaml 53 + #- path: manager_webhook_patch.yaml 54 + # target: 55 + # kind: Deployment 56 + 57 + # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. 58 + # Uncomment the following replacements to add the cert-manager CA injection annotations 59 + #replacements: 60 + # - source: # Uncomment the following block to enable certificates for metrics 61 + # kind: Service 62 + # version: v1 63 + # name: controller-manager-metrics-service 64 + # fieldPath: metadata.name 65 + # targets: 66 + # - select: 67 + # kind: Certificate 68 + # group: cert-manager.io 69 + # version: v1 70 + # name: metrics-certs 71 + # fieldPaths: 72 + # - spec.dnsNames.0 73 + # - spec.dnsNames.1 74 + # options: 75 + # delimiter: '.' 76 + # index: 0 77 + # create: true 78 + # - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor 79 + # kind: ServiceMonitor 80 + # group: monitoring.coreos.com 81 + # version: v1 82 + # name: controller-manager-metrics-monitor 83 + # fieldPaths: 84 + # - spec.endpoints.0.tlsConfig.serverName 85 + # options: 86 + # delimiter: '.' 87 + # index: 0 88 + # create: true 89 + 90 + # - source: 91 + # kind: Service 92 + # version: v1 93 + # name: controller-manager-metrics-service 94 + # fieldPath: metadata.namespace 95 + # targets: 96 + # - select: 97 + # kind: Certificate 98 + # group: cert-manager.io 99 + # version: v1 100 + # name: metrics-certs 101 + # fieldPaths: 102 + # - spec.dnsNames.0 103 + # - spec.dnsNames.1 104 + # options: 105 + # delimiter: '.' 106 + # index: 1 107 + # create: true 108 + # - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor 109 + # kind: ServiceMonitor 110 + # group: monitoring.coreos.com 111 + # version: v1 112 + # name: controller-manager-metrics-monitor 113 + # fieldPaths: 114 + # - spec.endpoints.0.tlsConfig.serverName 115 + # options: 116 + # delimiter: '.' 117 + # index: 1 118 + # create: true 119 + 120 + # - source: # Uncomment the following block if you have any webhook 121 + # kind: Service 122 + # version: v1 123 + # name: webhook-service 124 + # fieldPath: .metadata.name # Name of the service 125 + # targets: 126 + # - select: 127 + # kind: Certificate 128 + # group: cert-manager.io 129 + # version: v1 130 + # name: serving-cert 131 + # fieldPaths: 132 + # - .spec.dnsNames.0 133 + # - .spec.dnsNames.1 134 + # options: 135 + # delimiter: '.' 136 + # index: 0 137 + # create: true 138 + # - source: 139 + # kind: Service 140 + # version: v1 141 + # name: webhook-service 142 + # fieldPath: .metadata.namespace # Namespace of the service 143 + # targets: 144 + # - select: 145 + # kind: Certificate 146 + # group: cert-manager.io 147 + # version: v1 148 + # name: serving-cert 149 + # fieldPaths: 150 + # - .spec.dnsNames.0 151 + # - .spec.dnsNames.1 152 + # options: 153 + # delimiter: '.' 154 + # index: 1 155 + # create: true 156 + 157 + # - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation) 158 + # kind: Certificate 159 + # group: cert-manager.io 160 + # version: v1 161 + # name: serving-cert # This name should match the one in certificate.yaml 162 + # fieldPath: .metadata.namespace # Namespace of the certificate CR 163 + # targets: 164 + # - select: 165 + # kind: ValidatingWebhookConfiguration 166 + # fieldPaths: 167 + # - .metadata.annotations.[cert-manager.io/inject-ca-from] 168 + # options: 169 + # delimiter: '/' 170 + # index: 0 171 + # create: true 172 + # - source: 173 + # kind: Certificate 174 + # group: cert-manager.io 175 + # version: v1 176 + # name: serving-cert 177 + # fieldPath: .metadata.name 178 + # targets: 179 + # - select: 180 + # kind: ValidatingWebhookConfiguration 181 + # fieldPaths: 182 + # - .metadata.annotations.[cert-manager.io/inject-ca-from] 183 + # options: 184 + # delimiter: '/' 185 + # index: 1 186 + # create: true 187 + 188 + # - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting ) 189 + # kind: Certificate 190 + # group: cert-manager.io 191 + # version: v1 192 + # name: serving-cert 193 + # fieldPath: .metadata.namespace # Namespace of the certificate CR 194 + # targets: 195 + # - select: 196 + # kind: MutatingWebhookConfiguration 197 + # fieldPaths: 198 + # - .metadata.annotations.[cert-manager.io/inject-ca-from] 199 + # options: 200 + # delimiter: '/' 201 + # index: 0 202 + # create: true 203 + # - source: 204 + # kind: Certificate 205 + # group: cert-manager.io 206 + # version: v1 207 + # name: serving-cert 208 + # fieldPath: .metadata.name 209 + # targets: 210 + # - select: 211 + # kind: MutatingWebhookConfiguration 212 + # fieldPaths: 213 + # - .metadata.annotations.[cert-manager.io/inject-ca-from] 214 + # options: 215 + # delimiter: '/' 216 + # index: 1 217 + # create: true 218 + 219 + # - source: # Uncomment the following block if you have a ConversionWebhook (--conversion) 220 + # kind: Certificate 221 + # group: cert-manager.io 222 + # version: v1 223 + # name: serving-cert 224 + # fieldPath: .metadata.namespace # Namespace of the certificate CR 225 + # targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. 226 + # +kubebuilder:scaffold:crdkustomizecainjectionns 227 + # - source: 228 + # kind: Certificate 229 + # group: cert-manager.io 230 + # version: v1 231 + # name: serving-cert 232 + # fieldPath: .metadata.name 233 + # targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD. 234 + # +kubebuilder:scaffold:crdkustomizecainjectionname
+4
config/default/manager_metrics_patch.yaml
··· 1 + # This patch adds the args to allow exposing the metrics endpoint using HTTPS 2 + - op: add 3 + path: /spec/template/spec/containers/0/args/0 4 + value: --metrics-bind-address=:8443
+18
config/default/metrics_service.yaml
··· 1 + apiVersion: v1 2 + kind: Service 3 + metadata: 4 + labels: 5 + control-plane: controller-manager 6 + app.kubernetes.io/name: pist 7 + app.kubernetes.io/managed-by: kustomize 8 + name: controller-manager-metrics-service 9 + namespace: system 10 + spec: 11 + ports: 12 + - name: https 13 + port: 8443 14 + protocol: TCP 15 + targetPort: 8443 16 + selector: 17 + control-plane: controller-manager 18 + app.kubernetes.io/name: pist
+2
config/manager/kustomization.yaml
··· 1 + resources: 2 + - manager.yaml
+99
config/manager/manager.yaml
··· 1 + apiVersion: v1 2 + kind: Namespace 3 + metadata: 4 + labels: 5 + control-plane: controller-manager 6 + app.kubernetes.io/name: pist 7 + app.kubernetes.io/managed-by: kustomize 8 + name: system 9 + --- 10 + apiVersion: apps/v1 11 + kind: Deployment 12 + metadata: 13 + name: controller-manager 14 + namespace: system 15 + labels: 16 + control-plane: controller-manager 17 + app.kubernetes.io/name: pist 18 + app.kubernetes.io/managed-by: kustomize 19 + spec: 20 + selector: 21 + matchLabels: 22 + control-plane: controller-manager 23 + app.kubernetes.io/name: pist 24 + replicas: 1 25 + template: 26 + metadata: 27 + annotations: 28 + kubectl.kubernetes.io/default-container: manager 29 + labels: 30 + control-plane: controller-manager 31 + app.kubernetes.io/name: pist 32 + spec: 33 + # TODO(user): Uncomment the following code to configure the nodeAffinity expression 34 + # according to the platforms which are supported by your solution. 35 + # It is considered best practice to support multiple architectures. You can 36 + # build your manager image using the makefile target docker-buildx. 37 + # affinity: 38 + # nodeAffinity: 39 + # requiredDuringSchedulingIgnoredDuringExecution: 40 + # nodeSelectorTerms: 41 + # - matchExpressions: 42 + # - key: kubernetes.io/arch 43 + # operator: In 44 + # values: 45 + # - amd64 46 + # - arm64 47 + # - ppc64le 48 + # - s390x 49 + # - key: kubernetes.io/os 50 + # operator: In 51 + # values: 52 + # - linux 53 + securityContext: 54 + # Projects are configured by default to adhere to the "restricted" Pod Security Standards. 55 + # This ensures that deployments meet the highest security requirements for Kubernetes. 56 + # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted 57 + runAsNonRoot: true 58 + seccompProfile: 59 + type: RuntimeDefault 60 + containers: 61 + - command: 62 + - /manager 63 + args: 64 + - --leader-elect 65 + - --health-probe-bind-address=:8081 66 + image: controller:latest 67 + name: manager 68 + ports: [] 69 + securityContext: 70 + readOnlyRootFilesystem: true 71 + allowPrivilegeEscalation: false 72 + capabilities: 73 + drop: 74 + - "ALL" 75 + livenessProbe: 76 + httpGet: 77 + path: /healthz 78 + port: 8081 79 + initialDelaySeconds: 15 80 + periodSeconds: 20 81 + readinessProbe: 82 + httpGet: 83 + path: /readyz 84 + port: 8081 85 + initialDelaySeconds: 5 86 + periodSeconds: 10 87 + # TODO(user): Configure the resources accordingly based on the project requirements. 88 + # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ 89 + resources: 90 + limits: 91 + cpu: 500m 92 + memory: 128Mi 93 + requests: 94 + cpu: 10m 95 + memory: 64Mi 96 + volumeMounts: [] 97 + volumes: [] 98 + serviceAccountName: controller-manager 99 + terminationGracePeriodSeconds: 10
+27
config/network-policy/allow-metrics-traffic.yaml
··· 1 + # This NetworkPolicy allows ingress traffic 2 + # with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those 3 + # namespaces are able to gather data from the metrics endpoint. 4 + apiVersion: networking.k8s.io/v1 5 + kind: NetworkPolicy 6 + metadata: 7 + labels: 8 + app.kubernetes.io/name: pist 9 + app.kubernetes.io/managed-by: kustomize 10 + name: allow-metrics-traffic 11 + namespace: system 12 + spec: 13 + podSelector: 14 + matchLabels: 15 + control-plane: controller-manager 16 + app.kubernetes.io/name: pist 17 + policyTypes: 18 + - Ingress 19 + ingress: 20 + # This allows ingress traffic from any namespace with the label metrics: enabled 21 + - from: 22 + - namespaceSelector: 23 + matchLabels: 24 + metrics: enabled # Only from namespaces with this label 25 + ports: 26 + - port: 8443 27 + protocol: TCP
+2
config/network-policy/kustomization.yaml
··· 1 + resources: 2 + - allow-metrics-traffic.yaml
+11
config/prometheus/kustomization.yaml
··· 1 + resources: 2 + - monitor.yaml 3 + 4 + # [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus 5 + # to securely reference certificates created and managed by cert-manager. 6 + # Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml 7 + # to mount the "metrics-server-cert" secret in the Manager Deployment. 8 + #patches: 9 + # - path: monitor_tls_patch.yaml 10 + # target: 11 + # kind: ServiceMonitor
+27
config/prometheus/monitor.yaml
··· 1 + # Prometheus Monitor Service (Metrics) 2 + apiVersion: monitoring.coreos.com/v1 3 + kind: ServiceMonitor 4 + metadata: 5 + labels: 6 + control-plane: controller-manager 7 + app.kubernetes.io/name: pist 8 + app.kubernetes.io/managed-by: kustomize 9 + name: controller-manager-metrics-monitor 10 + namespace: system 11 + spec: 12 + endpoints: 13 + - path: /metrics 14 + port: https # Ensure this is the name of the port that exposes HTTPS metrics 15 + scheme: https 16 + bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token 17 + tlsConfig: 18 + # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables 19 + # certificate verification, exposing the system to potential man-in-the-middle attacks. 20 + # For production environments, it is recommended to use cert-manager for automatic TLS certificate management. 21 + # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml, 22 + # which securely references the certificate from the 'metrics-server-cert' secret. 23 + insecureSkipVerify: true 24 + selector: 25 + matchLabels: 26 + control-plane: controller-manager 27 + app.kubernetes.io/name: pist
+19
config/prometheus/monitor_tls_patch.yaml
··· 1 + # Patch for Prometheus ServiceMonitor to enable secure TLS configuration 2 + # using certificates managed by cert-manager 3 + - op: replace 4 + path: /spec/endpoints/0/tlsConfig 5 + value: 6 + # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize 7 + serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc 8 + insecureSkipVerify: false 9 + ca: 10 + secret: 11 + name: metrics-server-cert 12 + key: ca.crt 13 + cert: 14 + secret: 15 + name: metrics-server-cert 16 + key: tls.crt 17 + keySecret: 18 + name: metrics-server-cert 19 + key: tls.key
+20
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 + # The following RBAC configurations are used to protect 13 + # the metrics endpoint with authn/authz. These configurations 14 + # ensure that only authorized users and service accounts 15 + # can access the metrics endpoint. Comment the following 16 + # permissions if you want to disable this protection. 17 + # More info: https://book.kubebuilder.io/reference/metrics.html 18 + - metrics_auth_role.yaml 19 + - metrics_auth_role_binding.yaml 20 + - metrics_reader_role.yaml
+40
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 + labels: 6 + app.kubernetes.io/name: pist 7 + app.kubernetes.io/managed-by: kustomize 8 + name: leader-election-role 9 + rules: 10 + - apiGroups: 11 + - "" 12 + resources: 13 + - configmaps 14 + verbs: 15 + - get 16 + - list 17 + - watch 18 + - create 19 + - update 20 + - patch 21 + - delete 22 + - apiGroups: 23 + - coordination.k8s.io 24 + resources: 25 + - leases 26 + verbs: 27 + - get 28 + - list 29 + - watch 30 + - create 31 + - update 32 + - patch 33 + - delete 34 + - apiGroups: 35 + - "" 36 + resources: 37 + - events 38 + verbs: 39 + - create 40 + - patch
+15
config/rbac/leader_election_role_binding.yaml
··· 1 + apiVersion: rbac.authorization.k8s.io/v1 2 + kind: RoleBinding 3 + metadata: 4 + labels: 5 + app.kubernetes.io/name: pist 6 + app.kubernetes.io/managed-by: kustomize 7 + name: leader-election-rolebinding 8 + roleRef: 9 + apiGroup: rbac.authorization.k8s.io 10 + kind: Role 11 + name: leader-election-role 12 + subjects: 13 + - kind: ServiceAccount 14 + name: controller-manager 15 + namespace: system
+17
config/rbac/metrics_auth_role.yaml
··· 1 + apiVersion: rbac.authorization.k8s.io/v1 2 + kind: ClusterRole 3 + metadata: 4 + name: metrics-auth-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
+12
config/rbac/metrics_auth_role_binding.yaml
··· 1 + apiVersion: rbac.authorization.k8s.io/v1 2 + kind: ClusterRoleBinding 3 + metadata: 4 + name: metrics-auth-rolebinding 5 + roleRef: 6 + apiGroup: rbac.authorization.k8s.io 7 + kind: ClusterRole 8 + name: metrics-auth-role 9 + subjects: 10 + - kind: ServiceAccount 11 + name: controller-manager 12 + namespace: system
+9
config/rbac/metrics_reader_role.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
+11
config/rbac/role.yaml
··· 1 + apiVersion: rbac.authorization.k8s.io/v1 2 + kind: ClusterRole 3 + metadata: 4 + labels: 5 + app.kubernetes.io/name: pist 6 + app.kubernetes.io/managed-by: kustomize 7 + name: manager-role 8 + rules: 9 + - apiGroups: [""] 10 + resources: ["pods"] 11 + verbs: ["get", "list", "watch"]
+15
config/rbac/role_binding.yaml
··· 1 + apiVersion: rbac.authorization.k8s.io/v1 2 + kind: ClusterRoleBinding 3 + metadata: 4 + labels: 5 + app.kubernetes.io/name: pist 6 + app.kubernetes.io/managed-by: kustomize 7 + name: manager-rolebinding 8 + roleRef: 9 + apiGroup: rbac.authorization.k8s.io 10 + kind: ClusterRole 11 + name: manager-role 12 + subjects: 13 + - kind: ServiceAccount 14 + name: controller-manager 15 + namespace: system
+8
config/rbac/service_account.yaml
··· 1 + apiVersion: v1 2 + kind: ServiceAccount 3 + metadata: 4 + labels: 5 + app.kubernetes.io/name: pist 6 + app.kubernetes.io/managed-by: kustomize 7 + name: controller-manager 8 + namespace: system
+46
flake.lock
··· 1 + { 2 + "nodes": { 3 + "nixpkgs": { 4 + "locked": { 5 + "lastModified": 1755186698, 6 + "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", 7 + "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", 8 + "revCount": 844474, 9 + "type": "tarball", 10 + "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.1.844474%2Brev-fbcf476f790d8a217c3eab4e12033dc4a0f6d23c/0198bda5-652a-7335-9dc6-4f65049b2b8c/source.tar.gz" 11 + }, 12 + "original": { 13 + "type": "tarball", 14 + "url": "https://flakehub.com/f/NixOS/nixpkgs/0.1.%2A.tar.gz" 15 + } 16 + }, 17 + "root": { 18 + "inputs": { 19 + "nixpkgs": "nixpkgs", 20 + "rust-overlay": "rust-overlay" 21 + } 22 + }, 23 + "rust-overlay": { 24 + "inputs": { 25 + "nixpkgs": [ 26 + "nixpkgs" 27 + ] 28 + }, 29 + "locked": { 30 + "lastModified": 1755571033, 31 + "narHash": "sha256-V8gmZBfMiFGCyGJQx/yO81LFJ4d/I5Jxs2id96rLxrM=", 32 + "owner": "oxalica", 33 + "repo": "rust-overlay", 34 + "rev": "95487740bb7ac11553445e9249041a6fa4b5eccf", 35 + "type": "github" 36 + }, 37 + "original": { 38 + "owner": "oxalica", 39 + "repo": "rust-overlay", 40 + "type": "github" 41 + } 42 + } 43 + }, 44 + "root": "root", 45 + "version": 7 46 + }
+68
flake.nix
··· 1 + { 2 + description = "A Nix-flake-based Rust development environment"; 3 + 4 + inputs = { 5 + nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.1.*.tar.gz"; 6 + rust-overlay = { 7 + url = "github:oxalica/rust-overlay"; 8 + inputs.nixpkgs.follows = "nixpkgs"; 9 + }; 10 + }; 11 + 12 + outputs = { self, nixpkgs, rust-overlay }: 13 + let 14 + supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; 15 + forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f { 16 + pkgs = import nixpkgs { 17 + inherit system; 18 + overlays = [ rust-overlay.overlays.default self.overlays.default ]; 19 + }; 20 + }); 21 + in 22 + { 23 + overlays.default = final: prev: { 24 + rustToolchain = 25 + let 26 + rust = prev.rust-bin; 27 + in 28 + if builtins.pathExists ./rust-toolchain.toml then 29 + rust.fromRustupToolchainFile ./rust-toolchain.toml 30 + else if builtins.pathExists ./rust-toolchain then 31 + rust.fromRustupToolchainFile ./rust-toolchain 32 + else 33 + rust.stable.latest.default.override { 34 + extensions = [ "rust-src" "rustfmt" ]; 35 + }; 36 + }; 37 + 38 + devShells = forEachSupportedSystem ({ pkgs }: { 39 + default = pkgs.mkShell { 40 + buildInputs = with pkgs; [ 41 + rustToolchain 42 + openssl 43 + pkg-config 44 + cargo-deny 45 + cargo-edit 46 + cargo-watch 47 + cargo-nextest 48 + rust-analyzer 49 + jq 50 + 51 + # for local testing 52 + kind 53 + kubectl 54 + kubebuilder 55 + ]; 56 + 57 + env = { 58 + # Required by rust-analyzer 59 + RUST_SRC_PATH = "${pkgs.rustToolchain}/lib/rustlib/src/rust/library"; 60 + PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig;${pkgs.libxkbcommon.dev}/lib/pkgconfig;"; 61 + OPENSSL_NO_VENDOR = 1; 62 + OPENSSL_LIB_DIR = "${pkgs.lib.getLib pkgs.openssl}/lib"; 63 + OPENSSL_DIR = "${pkgs.openssl.dev}"; 64 + }; 65 + }; 66 + }); 67 + }; 68 + }
+97
go.mod
··· 1 + module github.com/kragniz/pist 2 + 3 + go 1.24.0 4 + 5 + require ( 6 + github.com/onsi/ginkgo/v2 v2.22.0 7 + github.com/onsi/gomega v1.36.1 8 + k8s.io/apimachinery v0.33.0 9 + k8s.io/client-go v0.33.0 10 + sigs.k8s.io/controller-runtime v0.21.0 11 + ) 12 + 13 + require ( 14 + cel.dev/expr v0.19.1 // indirect 15 + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect 16 + github.com/beorn7/perks v1.0.1 // indirect 17 + github.com/blang/semver/v4 v4.0.0 // indirect 18 + github.com/cenkalti/backoff/v4 v4.3.0 // indirect 19 + github.com/cespare/xxhash/v2 v2.3.0 // indirect 20 + github.com/davecgh/go-spew v1.1.1 // indirect 21 + github.com/emicklei/go-restful/v3 v3.11.0 // indirect 22 + github.com/evanphx/json-patch/v5 v5.9.11 // indirect 23 + github.com/felixge/httpsnoop v1.0.4 // indirect 24 + github.com/fsnotify/fsnotify v1.7.0 // indirect 25 + github.com/fxamacker/cbor/v2 v2.7.0 // indirect 26 + github.com/go-logr/logr v1.4.2 // indirect 27 + github.com/go-logr/stdr v1.2.2 // indirect 28 + github.com/go-logr/zapr v1.3.0 // indirect 29 + github.com/go-openapi/jsonpointer v0.21.0 // indirect 30 + github.com/go-openapi/jsonreference v0.20.2 // indirect 31 + github.com/go-openapi/swag v0.23.0 // indirect 32 + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect 33 + github.com/gogo/protobuf v1.3.2 // indirect 34 + github.com/google/btree v1.1.3 // indirect 35 + github.com/google/cel-go v0.23.2 // indirect 36 + github.com/google/gnostic-models v0.6.9 // indirect 37 + github.com/google/go-cmp v0.7.0 // indirect 38 + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect 39 + github.com/google/uuid v1.6.0 // indirect 40 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 // indirect 41 + github.com/inconshreveable/mousetrap v1.1.0 // indirect 42 + github.com/josharian/intern v1.0.0 // indirect 43 + github.com/json-iterator/go v1.1.12 // indirect 44 + github.com/mailru/easyjson v0.7.7 // indirect 45 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 46 + github.com/modern-go/reflect2 v1.0.2 // indirect 47 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 48 + github.com/pkg/errors v0.9.1 // indirect 49 + github.com/prometheus/client_golang v1.22.0 // indirect 50 + github.com/prometheus/client_model v0.6.1 // indirect 51 + github.com/prometheus/common v0.62.0 // indirect 52 + github.com/prometheus/procfs v0.15.1 // indirect 53 + github.com/spf13/cobra v1.8.1 // indirect 54 + github.com/spf13/pflag v1.0.5 // indirect 55 + github.com/stoewer/go-strcase v1.3.0 // indirect 56 + github.com/x448/float16 v0.8.4 // indirect 57 + go.opentelemetry.io/auto/sdk v1.1.0 // indirect 58 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect 59 + go.opentelemetry.io/otel v1.33.0 // indirect 60 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect 61 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect 62 + go.opentelemetry.io/otel/metric v1.33.0 // indirect 63 + go.opentelemetry.io/otel/sdk v1.33.0 // indirect 64 + go.opentelemetry.io/otel/trace v1.33.0 // indirect 65 + go.opentelemetry.io/proto/otlp v1.4.0 // indirect 66 + go.uber.org/multierr v1.11.0 // indirect 67 + go.uber.org/zap v1.27.0 // indirect 68 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect 69 + golang.org/x/net v0.38.0 // indirect 70 + golang.org/x/oauth2 v0.27.0 // indirect 71 + golang.org/x/sync v0.12.0 // indirect 72 + golang.org/x/sys v0.31.0 // indirect 73 + golang.org/x/term v0.30.0 // indirect 74 + golang.org/x/text v0.23.0 // indirect 75 + golang.org/x/time v0.9.0 // indirect 76 + golang.org/x/tools v0.26.0 // indirect 77 + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect 78 + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect 79 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 // indirect 80 + google.golang.org/grpc v1.68.1 // indirect 81 + google.golang.org/protobuf v1.36.5 // indirect 82 + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 83 + gopkg.in/inf.v0 v0.9.1 // indirect 84 + gopkg.in/yaml.v3 v3.0.1 // indirect 85 + k8s.io/api v0.33.0 // indirect 86 + k8s.io/apiextensions-apiserver v0.33.0 // indirect 87 + k8s.io/apiserver v0.33.0 // indirect 88 + k8s.io/component-base v0.33.0 // indirect 89 + k8s.io/klog/v2 v2.130.1 // indirect 90 + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 91 + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 92 + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect 93 + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 94 + sigs.k8s.io/randfill v1.0.0 // indirect 95 + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 96 + sigs.k8s.io/yaml v1.4.0 // indirect 97 + )
+254
go.sum
··· 1 + cel.dev/expr v0.19.1 h1:NciYrtDRIR0lNCnH1LFJegdjspNx9fI59O7TWcua/W4= 2 + cel.dev/expr v0.19.1/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= 3 + github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= 4 + github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= 5 + github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 6 + github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 7 + github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 8 + github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 9 + github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= 10 + github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= 11 + github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 12 + github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 13 + github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 14 + github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 15 + github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 + github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 + github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 + github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 19 + github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 20 + github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= 21 + github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= 22 + github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= 23 + github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= 24 + github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= 25 + github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= 26 + github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= 27 + github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= 28 + github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 29 + github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 30 + github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= 31 + github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 32 + github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 33 + github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= 34 + github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= 35 + github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 36 + github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 37 + github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 38 + github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 39 + github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 40 + github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 41 + github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 42 + github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 43 + github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 44 + github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 45 + github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 46 + github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 47 + github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 48 + github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 49 + github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 50 + github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 51 + github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 52 + github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 53 + github.com/google/cel-go v0.23.2 h1:UdEe3CvQh3Nv+E/j9r1Y//WO0K0cSyD7/y0bzyLIMI4= 54 + github.com/google/cel-go v0.23.2/go.mod h1:52Pb6QsDbC5kvgxvZhiL9QX1oZEkcUF/ZqaPx1J5Wwo= 55 + github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 56 + github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 57 + github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 58 + github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 59 + github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 60 + github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 61 + github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 62 + github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 63 + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 64 + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 65 + github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 66 + github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 67 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE= 68 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI= 69 + github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 70 + github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 71 + github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 72 + github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 73 + github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 74 + github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 75 + github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 76 + github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 77 + github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 78 + github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 79 + github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 80 + github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 81 + github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 82 + github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 83 + github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 84 + github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 85 + github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 86 + github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 87 + github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 88 + github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 89 + github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 90 + github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 91 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 92 + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 93 + github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 94 + github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 95 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 96 + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 97 + github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= 98 + github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 99 + github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= 100 + github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 101 + github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 102 + github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 103 + github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 104 + github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 105 + github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 106 + github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 107 + github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= 108 + github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= 109 + github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io= 110 + github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I= 111 + github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= 112 + github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= 113 + github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 114 + github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 115 + github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 116 + github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 117 + github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 118 + github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 119 + github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 120 + github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= 121 + github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= 122 + github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 123 + github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 124 + github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 125 + github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 126 + github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 127 + github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 128 + github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 129 + github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 130 + github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 131 + github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 132 + github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 133 + github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 134 + github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 135 + github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 136 + github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 137 + go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= 138 + go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= 139 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU= 140 + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q= 141 + go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= 142 + go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= 143 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA= 144 + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI= 145 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM= 146 + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA= 147 + go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= 148 + go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= 149 + go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM= 150 + go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM= 151 + go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= 152 + go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= 153 + go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg= 154 + go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY= 155 + go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 156 + go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 157 + go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 158 + go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 159 + go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 160 + go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 161 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 162 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 163 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 164 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= 165 + golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= 166 + golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 167 + golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 168 + golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 169 + golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 170 + golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 171 + golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 172 + golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 173 + golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 174 + golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= 175 + golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 176 + golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 177 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 178 + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 179 + golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 180 + golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 181 + golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 182 + golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 183 + golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 184 + golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 185 + golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 186 + golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 187 + golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 188 + golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 189 + golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 190 + golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 191 + golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 192 + golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 193 + golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 194 + golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 195 + golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 196 + golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 197 + golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 198 + golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 199 + golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 200 + golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 201 + golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 202 + golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 203 + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 204 + gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= 205 + gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= 206 + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 h1:CkkIfIt50+lT6NHAVoRYEyAvQGFM7xEwXUUywFvEb3Q= 207 + google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08= 208 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576 h1:8ZmaLZE4XWrtU3MyClkYqqtl6Oegr3235h7jxsDyqCY= 209 + google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= 210 + google.golang.org/grpc v1.68.1 h1:oI5oTa11+ng8r8XMMN7jAOmWfPZWbYpCFaMUTACxkM0= 211 + google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw= 212 + google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 213 + google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 214 + gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 215 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 216 + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 217 + gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 218 + gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 219 + gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 220 + gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 221 + gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 222 + gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 223 + gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 224 + k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= 225 + k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= 226 + k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= 227 + k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= 228 + k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= 229 + k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= 230 + k8s.io/apiserver v0.33.0 h1:QqcM6c+qEEjkOODHppFXRiw/cE2zP85704YrQ9YaBbc= 231 + k8s.io/apiserver v0.33.0/go.mod h1:EixYOit0YTxt8zrO2kBU7ixAtxFce9gKGq367nFmqI8= 232 + k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= 233 + k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= 234 + k8s.io/component-base v0.33.0 h1:Ot4PyJI+0JAD9covDhwLp9UNkUja209OzsJ4FzScBNk= 235 + k8s.io/component-base v0.33.0/go.mod h1:aXYZLbw3kihdkOPMDhWbjGCO6sg+luw554KP51t8qCU= 236 + k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 237 + k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 238 + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= 239 + k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= 240 + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= 241 + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 242 + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= 243 + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= 244 + sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= 245 + sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= 246 + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= 247 + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 248 + sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 249 + sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 250 + sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 251 + sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= 252 + sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 253 + sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 254 + sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
+15
hack/boilerplate.go.txt
··· 1 + /* 2 + Copyright 2026. 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 + */
+89
test/e2e/e2e_suite_test.go
··· 1 + /* 2 + Copyright 2026. 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 e2e 18 + 19 + import ( 20 + "fmt" 21 + "os" 22 + "os/exec" 23 + "testing" 24 + 25 + . "github.com/onsi/ginkgo/v2" 26 + . "github.com/onsi/gomega" 27 + 28 + "github.com/kragniz/pist/test/utils" 29 + ) 30 + 31 + var ( 32 + // Optional Environment Variables: 33 + // - CERT_MANAGER_INSTALL_SKIP=true: Skips CertManager installation during test setup. 34 + // These variables are useful if CertManager is already installed, avoiding 35 + // re-installation and conflicts. 36 + skipCertManagerInstall = os.Getenv("CERT_MANAGER_INSTALL_SKIP") == "true" 37 + // isCertManagerAlreadyInstalled will be set true when CertManager CRDs be found on the cluster 38 + isCertManagerAlreadyInstalled = false 39 + 40 + // projectImage is the name of the image which will be build and loaded 41 + // with the code source changes to be tested. 42 + projectImage = "example.com/pist:v0.0.1" 43 + ) 44 + 45 + // TestE2E runs the end-to-end (e2e) test suite for the project. These tests execute in an isolated, 46 + // temporary environment to validate project changes with the purpose of being used in CI jobs. 47 + // The default setup requires Kind, builds/loads the Manager Docker image locally, and installs 48 + // CertManager. 49 + func TestE2E(t *testing.T) { 50 + RegisterFailHandler(Fail) 51 + _, _ = fmt.Fprintf(GinkgoWriter, "Starting pist integration test suite\n") 52 + RunSpecs(t, "e2e suite") 53 + } 54 + 55 + var _ = BeforeSuite(func() { 56 + By("building the manager(Operator) image") 57 + cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectImage)) 58 + _, err := utils.Run(cmd) 59 + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to build the manager(Operator) image") 60 + 61 + // TODO(user): If you want to change the e2e test vendor from Kind, ensure the image is 62 + // built and available before running the tests. Also, remove the following block. 63 + By("loading the manager(Operator) image on Kind") 64 + err = utils.LoadImageToKindClusterWithName(projectImage) 65 + ExpectWithOffset(1, err).NotTo(HaveOccurred(), "Failed to load the manager(Operator) image into Kind") 66 + 67 + // The tests-e2e are intended to run on a temporary cluster that is created and destroyed for testing. 68 + // To prevent errors when tests run in environments with CertManager already installed, 69 + // we check for its presence before execution. 70 + // Setup CertManager before the suite if not skipped and if not already installed 71 + if !skipCertManagerInstall { 72 + By("checking if cert manager is installed already") 73 + isCertManagerAlreadyInstalled = utils.IsCertManagerCRDsInstalled() 74 + if !isCertManagerAlreadyInstalled { 75 + _, _ = fmt.Fprintf(GinkgoWriter, "Installing CertManager...\n") 76 + Expect(utils.InstallCertManager()).To(Succeed(), "Failed to install CertManager") 77 + } else { 78 + _, _ = fmt.Fprintf(GinkgoWriter, "WARNING: CertManager is already installed. Skipping installation...\n") 79 + } 80 + } 81 + }) 82 + 83 + var _ = AfterSuite(func() { 84 + // Teardown CertManager after the suite if not skipped and if it was not already installed 85 + if !skipCertManagerInstall && !isCertManagerAlreadyInstalled { 86 + _, _ = fmt.Fprintf(GinkgoWriter, "Uninstalling CertManager...\n") 87 + utils.UninstallCertManager() 88 + } 89 + })
+330
test/e2e/e2e_test.go
··· 1 + /* 2 + Copyright 2026. 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 e2e 18 + 19 + import ( 20 + "encoding/json" 21 + "fmt" 22 + "os" 23 + "os/exec" 24 + "path/filepath" 25 + "time" 26 + 27 + . "github.com/onsi/ginkgo/v2" 28 + . "github.com/onsi/gomega" 29 + 30 + "github.com/kragniz/pist/test/utils" 31 + ) 32 + 33 + // namespace where the project is deployed in 34 + const namespace = "pist-system" 35 + 36 + // serviceAccountName created for the project 37 + const serviceAccountName = "pist-controller-manager" 38 + 39 + // metricsServiceName is the name of the metrics service of the project 40 + const metricsServiceName = "pist-controller-manager-metrics-service" 41 + 42 + // metricsRoleBindingName is the name of the RBAC that will be created to allow get the metrics data 43 + const metricsRoleBindingName = "pist-metrics-binding" 44 + 45 + var _ = Describe("Manager", Ordered, func() { 46 + var controllerPodName string 47 + 48 + // Before running the tests, set up the environment by creating the namespace, 49 + // enforce the restricted security policy to the namespace, installing CRDs, 50 + // and deploying the controller. 51 + BeforeAll(func() { 52 + By("creating manager namespace") 53 + cmd := exec.Command("kubectl", "create", "ns", namespace) 54 + _, err := utils.Run(cmd) 55 + Expect(err).NotTo(HaveOccurred(), "Failed to create namespace") 56 + 57 + By("labeling the namespace to enforce the restricted security policy") 58 + cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace, 59 + "pod-security.kubernetes.io/enforce=restricted") 60 + _, err = utils.Run(cmd) 61 + Expect(err).NotTo(HaveOccurred(), "Failed to label namespace with restricted policy") 62 + 63 + By("installing CRDs") 64 + cmd = exec.Command("make", "install") 65 + _, err = utils.Run(cmd) 66 + Expect(err).NotTo(HaveOccurred(), "Failed to install CRDs") 67 + 68 + By("deploying the controller-manager") 69 + cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectImage)) 70 + _, err = utils.Run(cmd) 71 + Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager") 72 + }) 73 + 74 + // After all tests have been executed, clean up by undeploying the controller, uninstalling CRDs, 75 + // and deleting the namespace. 76 + AfterAll(func() { 77 + By("cleaning up the curl pod for metrics") 78 + cmd := exec.Command("kubectl", "delete", "pod", "curl-metrics", "-n", namespace) 79 + _, _ = utils.Run(cmd) 80 + 81 + By("undeploying the controller-manager") 82 + cmd = exec.Command("make", "undeploy") 83 + _, _ = utils.Run(cmd) 84 + 85 + By("uninstalling CRDs") 86 + cmd = exec.Command("make", "uninstall") 87 + _, _ = utils.Run(cmd) 88 + 89 + By("removing manager namespace") 90 + cmd = exec.Command("kubectl", "delete", "ns", namespace) 91 + _, _ = utils.Run(cmd) 92 + }) 93 + 94 + // After each test, check for failures and collect logs, events, 95 + // and pod descriptions for debugging. 96 + AfterEach(func() { 97 + specReport := CurrentSpecReport() 98 + if specReport.Failed() { 99 + By("Fetching controller manager pod logs") 100 + cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) 101 + controllerLogs, err := utils.Run(cmd) 102 + if err == nil { 103 + _, _ = fmt.Fprintf(GinkgoWriter, "Controller logs:\n %s", controllerLogs) 104 + } else { 105 + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Controller logs: %s", err) 106 + } 107 + 108 + By("Fetching Kubernetes events") 109 + cmd = exec.Command("kubectl", "get", "events", "-n", namespace, "--sort-by=.lastTimestamp") 110 + eventsOutput, err := utils.Run(cmd) 111 + if err == nil { 112 + _, _ = fmt.Fprintf(GinkgoWriter, "Kubernetes events:\n%s", eventsOutput) 113 + } else { 114 + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get Kubernetes events: %s", err) 115 + } 116 + 117 + By("Fetching curl-metrics logs") 118 + cmd = exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) 119 + metricsOutput, err := utils.Run(cmd) 120 + if err == nil { 121 + _, _ = fmt.Fprintf(GinkgoWriter, "Metrics logs:\n %s", metricsOutput) 122 + } else { 123 + _, _ = fmt.Fprintf(GinkgoWriter, "Failed to get curl-metrics logs: %s", err) 124 + } 125 + 126 + By("Fetching controller manager pod description") 127 + cmd = exec.Command("kubectl", "describe", "pod", controllerPodName, "-n", namespace) 128 + podDescription, err := utils.Run(cmd) 129 + if err == nil { 130 + fmt.Println("Pod description:\n", podDescription) 131 + } else { 132 + fmt.Println("Failed to describe controller pod") 133 + } 134 + } 135 + }) 136 + 137 + SetDefaultEventuallyTimeout(2 * time.Minute) 138 + SetDefaultEventuallyPollingInterval(time.Second) 139 + 140 + Context("Manager", func() { 141 + It("should run successfully", func() { 142 + By("validating that the controller-manager pod is running as expected") 143 + verifyControllerUp := func(g Gomega) { 144 + // Get the name of the controller-manager pod 145 + cmd := exec.Command("kubectl", "get", 146 + "pods", "-l", "control-plane=controller-manager", 147 + "-o", "go-template={{ range .items }}"+ 148 + "{{ if not .metadata.deletionTimestamp }}"+ 149 + "{{ .metadata.name }}"+ 150 + "{{ \"\\n\" }}{{ end }}{{ end }}", 151 + "-n", namespace, 152 + ) 153 + 154 + podOutput, err := utils.Run(cmd) 155 + g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve controller-manager pod information") 156 + podNames := utils.GetNonEmptyLines(podOutput) 157 + g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running") 158 + controllerPodName = podNames[0] 159 + g.Expect(controllerPodName).To(ContainSubstring("controller-manager")) 160 + 161 + // Validate the pod's status 162 + cmd = exec.Command("kubectl", "get", 163 + "pods", controllerPodName, "-o", "jsonpath={.status.phase}", 164 + "-n", namespace, 165 + ) 166 + output, err := utils.Run(cmd) 167 + g.Expect(err).NotTo(HaveOccurred()) 168 + g.Expect(output).To(Equal("Running"), "Incorrect controller-manager pod status") 169 + } 170 + Eventually(verifyControllerUp).Should(Succeed()) 171 + }) 172 + 173 + It("should ensure the metrics endpoint is serving metrics", func() { 174 + By("creating a ClusterRoleBinding for the service account to allow access to metrics") 175 + cmd := exec.Command("kubectl", "create", "clusterrolebinding", metricsRoleBindingName, 176 + "--clusterrole=pist-metrics-reader", 177 + fmt.Sprintf("--serviceaccount=%s:%s", namespace, serviceAccountName), 178 + ) 179 + _, err := utils.Run(cmd) 180 + Expect(err).NotTo(HaveOccurred(), "Failed to create ClusterRoleBinding") 181 + 182 + By("validating that the metrics service is available") 183 + cmd = exec.Command("kubectl", "get", "service", metricsServiceName, "-n", namespace) 184 + _, err = utils.Run(cmd) 185 + Expect(err).NotTo(HaveOccurred(), "Metrics service should exist") 186 + 187 + By("getting the service account token") 188 + token, err := serviceAccountToken() 189 + Expect(err).NotTo(HaveOccurred()) 190 + Expect(token).NotTo(BeEmpty()) 191 + 192 + By("waiting for the metrics endpoint to be ready") 193 + verifyMetricsEndpointReady := func(g Gomega) { 194 + cmd := exec.Command("kubectl", "get", "endpoints", metricsServiceName, "-n", namespace) 195 + output, err := utils.Run(cmd) 196 + g.Expect(err).NotTo(HaveOccurred()) 197 + g.Expect(output).To(ContainSubstring("8443"), "Metrics endpoint is not ready") 198 + } 199 + Eventually(verifyMetricsEndpointReady).Should(Succeed()) 200 + 201 + By("verifying that the controller manager is serving the metrics server") 202 + verifyMetricsServerStarted := func(g Gomega) { 203 + cmd := exec.Command("kubectl", "logs", controllerPodName, "-n", namespace) 204 + output, err := utils.Run(cmd) 205 + g.Expect(err).NotTo(HaveOccurred()) 206 + g.Expect(output).To(ContainSubstring("controller-runtime.metrics\tServing metrics server"), 207 + "Metrics server not yet started") 208 + } 209 + Eventually(verifyMetricsServerStarted).Should(Succeed()) 210 + 211 + By("creating the curl-metrics pod to access the metrics endpoint") 212 + cmd = exec.Command("kubectl", "run", "curl-metrics", "--restart=Never", 213 + "--namespace", namespace, 214 + "--image=curlimages/curl:latest", 215 + "--overrides", 216 + fmt.Sprintf(`{ 217 + "spec": { 218 + "containers": [{ 219 + "name": "curl", 220 + "image": "curlimages/curl:latest", 221 + "command": ["/bin/sh", "-c"], 222 + "args": ["curl -v -k -H 'Authorization: Bearer %s' https://%s.%s.svc.cluster.local:8443/metrics"], 223 + "securityContext": { 224 + "readOnlyRootFilesystem": true, 225 + "allowPrivilegeEscalation": false, 226 + "capabilities": { 227 + "drop": ["ALL"] 228 + }, 229 + "runAsNonRoot": true, 230 + "runAsUser": 1000, 231 + "seccompProfile": { 232 + "type": "RuntimeDefault" 233 + } 234 + } 235 + }], 236 + "serviceAccountName": "%s" 237 + } 238 + }`, token, metricsServiceName, namespace, serviceAccountName)) 239 + _, err = utils.Run(cmd) 240 + Expect(err).NotTo(HaveOccurred(), "Failed to create curl-metrics pod") 241 + 242 + By("waiting for the curl-metrics pod to complete.") 243 + verifyCurlUp := func(g Gomega) { 244 + cmd := exec.Command("kubectl", "get", "pods", "curl-metrics", 245 + "-o", "jsonpath={.status.phase}", 246 + "-n", namespace) 247 + output, err := utils.Run(cmd) 248 + g.Expect(err).NotTo(HaveOccurred()) 249 + g.Expect(output).To(Equal("Succeeded"), "curl pod in wrong status") 250 + } 251 + Eventually(verifyCurlUp, 5*time.Minute).Should(Succeed()) 252 + 253 + By("getting the metrics by checking curl-metrics logs") 254 + metricsOutput := getMetricsOutput() 255 + Expect(metricsOutput).To(ContainSubstring( 256 + "controller_runtime_reconcile_total", 257 + )) 258 + }) 259 + 260 + // +kubebuilder:scaffold:e2e-webhooks-checks 261 + 262 + // TODO: Customize the e2e test suite with scenarios specific to your project. 263 + // Consider applying sample/CR(s) and check their status and/or verifying 264 + // the reconciliation by using the metrics, i.e.: 265 + // metricsOutput := getMetricsOutput() 266 + // Expect(metricsOutput).To(ContainSubstring( 267 + // fmt.Sprintf(`controller_runtime_reconcile_total{controller="%s",result="success"} 1`, 268 + // strings.ToLower(<Kind>), 269 + // )) 270 + }) 271 + }) 272 + 273 + // serviceAccountToken returns a token for the specified service account in the given namespace. 274 + // It uses the Kubernetes TokenRequest API to generate a token by directly sending a request 275 + // and parsing the resulting token from the API response. 276 + func serviceAccountToken() (string, error) { 277 + const tokenRequestRawString = `{ 278 + "apiVersion": "authentication.k8s.io/v1", 279 + "kind": "TokenRequest" 280 + }` 281 + 282 + // Temporary file to store the token request 283 + secretName := fmt.Sprintf("%s-token-request", serviceAccountName) 284 + tokenRequestFile := filepath.Join("/tmp", secretName) 285 + err := os.WriteFile(tokenRequestFile, []byte(tokenRequestRawString), os.FileMode(0o644)) 286 + if err != nil { 287 + return "", err 288 + } 289 + 290 + var out string 291 + verifyTokenCreation := func(g Gomega) { 292 + // Execute kubectl command to create the token 293 + cmd := exec.Command("kubectl", "create", "--raw", fmt.Sprintf( 294 + "/api/v1/namespaces/%s/serviceaccounts/%s/token", 295 + namespace, 296 + serviceAccountName, 297 + ), "-f", tokenRequestFile) 298 + 299 + output, err := cmd.CombinedOutput() 300 + g.Expect(err).NotTo(HaveOccurred()) 301 + 302 + // Parse the JSON output to extract the token 303 + var token tokenRequest 304 + err = json.Unmarshal(output, &token) 305 + g.Expect(err).NotTo(HaveOccurred()) 306 + 307 + out = token.Status.Token 308 + } 309 + Eventually(verifyTokenCreation).Should(Succeed()) 310 + 311 + return out, err 312 + } 313 + 314 + // getMetricsOutput retrieves and returns the logs from the curl pod used to access the metrics endpoint. 315 + func getMetricsOutput() string { 316 + By("getting the curl-metrics logs") 317 + cmd := exec.Command("kubectl", "logs", "curl-metrics", "-n", namespace) 318 + metricsOutput, err := utils.Run(cmd) 319 + Expect(err).NotTo(HaveOccurred(), "Failed to retrieve logs from curl pod") 320 + Expect(metricsOutput).To(ContainSubstring("< HTTP/1.1 200 OK")) 321 + return metricsOutput 322 + } 323 + 324 + // tokenRequest is a simplified representation of the Kubernetes TokenRequest API response, 325 + // containing only the token field that we need to extract. 326 + type tokenRequest struct { 327 + Status struct { 328 + Token string `json:"token"` 329 + } `json:"status"` 330 + }
+267
test/utils/utils.go
··· 1 + /* 2 + Copyright 2026. 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 utils 18 + 19 + import ( 20 + "bufio" 21 + "bytes" 22 + "fmt" 23 + "os" 24 + "os/exec" 25 + "strings" 26 + 27 + . "github.com/onsi/ginkgo/v2" // nolint:revive,staticcheck 28 + ) 29 + 30 + const ( 31 + prometheusOperatorVersion = "v0.77.1" 32 + prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" + 33 + "releases/download/%s/bundle.yaml" 34 + 35 + certmanagerVersion = "v1.16.3" 36 + certmanagerURLTmpl = "https://github.com/cert-manager/cert-manager/releases/download/%s/cert-manager.yaml" 37 + ) 38 + 39 + func warnError(err error) { 40 + _, _ = fmt.Fprintf(GinkgoWriter, "warning: %v\n", err) 41 + } 42 + 43 + // Run executes the provided command within this context 44 + func Run(cmd *exec.Cmd) (string, error) { 45 + dir, _ := GetProjectDir() 46 + cmd.Dir = dir 47 + 48 + if err := os.Chdir(cmd.Dir); err != nil { 49 + _, _ = fmt.Fprintf(GinkgoWriter, "chdir dir: %q\n", err) 50 + } 51 + 52 + cmd.Env = append(os.Environ(), "GO111MODULE=on") 53 + command := strings.Join(cmd.Args, " ") 54 + _, _ = fmt.Fprintf(GinkgoWriter, "running: %q\n", command) 55 + output, err := cmd.CombinedOutput() 56 + if err != nil { 57 + return string(output), fmt.Errorf("%q failed with error %q: %w", command, string(output), err) 58 + } 59 + 60 + return string(output), nil 61 + } 62 + 63 + // InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics. 64 + func InstallPrometheusOperator() error { 65 + url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) 66 + cmd := exec.Command("kubectl", "create", "-f", url) 67 + _, err := Run(cmd) 68 + return err 69 + } 70 + 71 + // UninstallPrometheusOperator uninstalls the prometheus 72 + func UninstallPrometheusOperator() { 73 + url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion) 74 + cmd := exec.Command("kubectl", "delete", "-f", url) 75 + if _, err := Run(cmd); err != nil { 76 + warnError(err) 77 + } 78 + } 79 + 80 + // IsPrometheusCRDsInstalled checks if any Prometheus CRDs are installed 81 + // by verifying the existence of key CRDs related to Prometheus. 82 + func IsPrometheusCRDsInstalled() bool { 83 + // List of common Prometheus CRDs 84 + prometheusCRDs := []string{ 85 + "prometheuses.monitoring.coreos.com", 86 + "prometheusrules.monitoring.coreos.com", 87 + "prometheusagents.monitoring.coreos.com", 88 + } 89 + 90 + cmd := exec.Command("kubectl", "get", "crds", "-o", "custom-columns=NAME:.metadata.name") 91 + output, err := Run(cmd) 92 + if err != nil { 93 + return false 94 + } 95 + crdList := GetNonEmptyLines(output) 96 + for _, crd := range prometheusCRDs { 97 + for _, line := range crdList { 98 + if strings.Contains(line, crd) { 99 + return true 100 + } 101 + } 102 + } 103 + 104 + return false 105 + } 106 + 107 + // UninstallCertManager uninstalls the cert manager 108 + func UninstallCertManager() { 109 + url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) 110 + cmd := exec.Command("kubectl", "delete", "-f", url) 111 + if _, err := Run(cmd); err != nil { 112 + warnError(err) 113 + } 114 + 115 + // Delete leftover leases in kube-system (not cleaned by default) 116 + kubeSystemLeases := []string{ 117 + "cert-manager-cainjector-leader-election", 118 + "cert-manager-controller", 119 + } 120 + for _, lease := range kubeSystemLeases { 121 + cmd = exec.Command("kubectl", "delete", "lease", lease, 122 + "-n", "kube-system", "--ignore-not-found", "--force", "--grace-period=0") 123 + if _, err := Run(cmd); err != nil { 124 + warnError(err) 125 + } 126 + } 127 + } 128 + 129 + // InstallCertManager installs the cert manager bundle. 130 + func InstallCertManager() error { 131 + url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion) 132 + cmd := exec.Command("kubectl", "apply", "-f", url) 133 + if _, err := Run(cmd); err != nil { 134 + return err 135 + } 136 + // Wait for cert-manager-webhook to be ready, which can take time if cert-manager 137 + // was re-installed after uninstalling on a cluster. 138 + cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook", 139 + "--for", "condition=Available", 140 + "--namespace", "cert-manager", 141 + "--timeout", "5m", 142 + ) 143 + 144 + _, err := Run(cmd) 145 + return err 146 + } 147 + 148 + // IsCertManagerCRDsInstalled checks if any Cert Manager CRDs are installed 149 + // by verifying the existence of key CRDs related to Cert Manager. 150 + func IsCertManagerCRDsInstalled() bool { 151 + // List of common Cert Manager CRDs 152 + certManagerCRDs := []string{ 153 + "certificates.cert-manager.io", 154 + "issuers.cert-manager.io", 155 + "clusterissuers.cert-manager.io", 156 + "certificaterequests.cert-manager.io", 157 + "orders.acme.cert-manager.io", 158 + "challenges.acme.cert-manager.io", 159 + } 160 + 161 + // Execute the kubectl command to get all CRDs 162 + cmd := exec.Command("kubectl", "get", "crds") 163 + output, err := Run(cmd) 164 + if err != nil { 165 + return false 166 + } 167 + 168 + // Check if any of the Cert Manager CRDs are present 169 + crdList := GetNonEmptyLines(output) 170 + for _, crd := range certManagerCRDs { 171 + for _, line := range crdList { 172 + if strings.Contains(line, crd) { 173 + return true 174 + } 175 + } 176 + } 177 + 178 + return false 179 + } 180 + 181 + // LoadImageToKindClusterWithName loads a local docker image to the kind cluster 182 + func LoadImageToKindClusterWithName(name string) error { 183 + cluster := "kind" 184 + if v, ok := os.LookupEnv("KIND_CLUSTER"); ok { 185 + cluster = v 186 + } 187 + kindOptions := []string{"load", "docker-image", name, "--name", cluster} 188 + cmd := exec.Command("kind", kindOptions...) 189 + _, err := Run(cmd) 190 + return err 191 + } 192 + 193 + // GetNonEmptyLines converts given command output string into individual objects 194 + // according to line breakers, and ignores the empty elements in it. 195 + func GetNonEmptyLines(output string) []string { 196 + var res []string 197 + elements := strings.Split(output, "\n") 198 + for _, element := range elements { 199 + if element != "" { 200 + res = append(res, element) 201 + } 202 + } 203 + 204 + return res 205 + } 206 + 207 + // GetProjectDir will return the directory where the project is 208 + func GetProjectDir() (string, error) { 209 + wd, err := os.Getwd() 210 + if err != nil { 211 + return wd, fmt.Errorf("failed to get current working directory: %w", err) 212 + } 213 + wd = strings.ReplaceAll(wd, "/test/e2e", "") 214 + return wd, nil 215 + } 216 + 217 + // UncommentCode searches for target in the file and remove the comment prefix 218 + // of the target content. The target content may span multiple lines. 219 + func UncommentCode(filename, target, prefix string) error { 220 + // false positive 221 + // nolint:gosec 222 + content, err := os.ReadFile(filename) 223 + if err != nil { 224 + return fmt.Errorf("failed to read file %q: %w", filename, err) 225 + } 226 + strContent := string(content) 227 + 228 + idx := strings.Index(strContent, target) 229 + if idx < 0 { 230 + return fmt.Errorf("unable to find the code %q to be uncomment", target) 231 + } 232 + 233 + out := new(bytes.Buffer) 234 + _, err = out.Write(content[:idx]) 235 + if err != nil { 236 + return fmt.Errorf("failed to write to output: %w", err) 237 + } 238 + 239 + scanner := bufio.NewScanner(bytes.NewBufferString(target)) 240 + if !scanner.Scan() { 241 + return nil 242 + } 243 + for { 244 + if _, err = out.WriteString(strings.TrimPrefix(scanner.Text(), prefix)); err != nil { 245 + return fmt.Errorf("failed to write to output: %w", err) 246 + } 247 + // Avoid writing a newline in case the previous line was the last in target. 248 + if !scanner.Scan() { 249 + break 250 + } 251 + if _, err = out.WriteString("\n"); err != nil { 252 + return fmt.Errorf("failed to write to output: %w", err) 253 + } 254 + } 255 + 256 + if _, err = out.Write(content[idx+len(target):]); err != nil { 257 + return fmt.Errorf("failed to write to output: %w", err) 258 + } 259 + 260 + // false positive 261 + // nolint:gosec 262 + if err = os.WriteFile(filename, out.Bytes(), 0644); err != nil { 263 + return fmt.Errorf("failed to write file %q: %w", filename, err) 264 + } 265 + 266 + return nil 267 + }