···11+# More info: https://docs.docker.com/engine/reference/builder/#dockerignore-file
22+# Ignore build and test binaries.
33+bin/
+27
.gitignore
···11+# Binaries for programs and plugins
22+*.exe
33+*.exe~
44+*.dll
55+*.so
66+*.dylib
77+bin/*
88+Dockerfile.cross
99+1010+# Test binary, built with `go test -c`
1111+*.test
1212+1313+# Output of the go coverage tool, specifically when used with LiteIDE
1414+*.out
1515+1616+# Go workspace file
1717+go.work
1818+1919+# Kubernetes Generated files - skip generated files, except for vendored files
2020+!vendor/**/zz_generated.*
2121+2222+# editor and IDE paraphernalia
2323+.idea
2424+.vscode
2525+*.swp
2626+*.swo
2727+*~
···11+# Build the manager binary
22+FROM golang:1.21 AS builder
33+ARG TARGETOS
44+ARG TARGETARCH
55+66+WORKDIR /workspace
77+# Copy the Go Modules manifests
88+COPY go.mod go.mod
99+COPY go.sum go.sum
1010+# cache deps before building and copying source so that we don't need to re-download as much
1111+# and so that source changes don't invalidate our downloaded layer
1212+RUN go mod download
1313+1414+# Copy the go source
1515+COPY cmd/main.go cmd/main.go
1616+COPY api/ api/
1717+COPY internal/controller/ internal/controller/
1818+1919+# Build
2020+# the GOARCH has not a default value to allow the binary be built according to the host where the command
2121+# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
2222+# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
2323+# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
2424+RUN CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
2525+2626+# Use distroless as minimal base image to package the manager binary
2727+# Refer to https://github.com/GoogleContainerTools/distroless for more details
2828+FROM gcr.io/distroless/static:nonroot
2929+WORKDIR /
3030+COPY --from=builder /workspace/manager .
3131+USER 65532:65532
3232+3333+ENTRYPOINT ["/manager"]
+322
Makefile
···11+# VERSION defines the project version for the bundle.
22+# Update this value when you upgrade the version of your project.
33+# To re-generate a bundle for another specific version without changing the standard setup, you can:
44+# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
55+# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
66+VERSION ?= 0.0.1
77+88+# CHANNELS define the bundle channels used in the bundle.
99+# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
1010+# To re-generate a bundle for other specific channels without changing the standard setup, you can:
1111+# - use the CHANNELS as arg of the bundle target (e.g make bundle CHANNELS=candidate,fast,stable)
1212+# - use environment variables to overwrite this value (e.g export CHANNELS="candidate,fast,stable")
1313+ifneq ($(origin CHANNELS), undefined)
1414+BUNDLE_CHANNELS := --channels=$(CHANNELS)
1515+endif
1616+1717+# DEFAULT_CHANNEL defines the default channel used in the bundle.
1818+# Add a new line here if you would like to change its default config. (E.g DEFAULT_CHANNEL = "stable")
1919+# To re-generate a bundle for any other default channel without changing the default setup, you can:
2020+# - use the DEFAULT_CHANNEL as arg of the bundle target (e.g make bundle DEFAULT_CHANNEL=stable)
2121+# - use environment variables to overwrite this value (e.g export DEFAULT_CHANNEL="stable")
2222+ifneq ($(origin DEFAULT_CHANNEL), undefined)
2323+BUNDLE_DEFAULT_CHANNEL := --default-channel=$(DEFAULT_CHANNEL)
2424+endif
2525+BUNDLE_METADATA_OPTS ?= $(BUNDLE_CHANNELS) $(BUNDLE_DEFAULT_CHANNEL)
2626+2727+# IMAGE_TAG_BASE defines the docker.io namespace and part of the image name for remote images.
2828+# This variable is used to construct full image tags for bundle and catalog images.
2929+#
3030+# For example, running 'make bundle-build bundle-push catalog-build catalog-push' will build and push both
3131+# j5t.io/secret-service-operator-bundle:$VERSION and j5t.io/secret-service-operator-catalog:$VERSION.
3232+IMAGE_TAG_BASE ?= j5t.io/secret-service-operator
3333+3434+# BUNDLE_IMG defines the image:tag used for the bundle.
3535+# You can use it as an arg. (E.g make bundle-build BUNDLE_IMG=<some-registry>/<project-name-bundle>:<tag>)
3636+BUNDLE_IMG ?= $(IMAGE_TAG_BASE)-bundle:v$(VERSION)
3737+3838+# BUNDLE_GEN_FLAGS are the flags passed to the operator-sdk generate bundle command
3939+BUNDLE_GEN_FLAGS ?= -q --overwrite --version $(VERSION) $(BUNDLE_METADATA_OPTS)
4040+4141+# USE_IMAGE_DIGESTS defines if images are resolved via tags or digests
4242+# You can enable this value if you would like to use SHA Based Digests
4343+# To enable set flag to true
4444+USE_IMAGE_DIGESTS ?= false
4545+ifeq ($(USE_IMAGE_DIGESTS), true)
4646+ BUNDLE_GEN_FLAGS += --use-image-digests
4747+endif
4848+4949+# Set the Operator SDK version to use. By default, what is installed on the system is used.
5050+# This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit.
5151+OPERATOR_SDK_VERSION ?= v1.37.0
5252+# Image URL to use all building/pushing image targets
5353+IMG ?= controller:latest
5454+# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
5555+ENVTEST_K8S_VERSION = 1.29.0
5656+5757+# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
5858+ifeq (,$(shell go env GOBIN))
5959+GOBIN=$(shell go env GOPATH)/bin
6060+else
6161+GOBIN=$(shell go env GOBIN)
6262+endif
6363+6464+# CONTAINER_TOOL defines the container tool to be used for building images.
6565+# Be aware that the target commands are only tested with Docker which is
6666+# scaffolded by default. However, you might want to replace it to use other
6767+# tools. (i.e. podman)
6868+CONTAINER_TOOL ?= podman
6969+7070+# Setting SHELL to bash allows bash commands to be executed by recipes.
7171+# Options are set to exit when a recipe line exits non-zero or a piped command fails.
7272+SHELL = /usr/bin/env bash -o pipefail
7373+.SHELLFLAGS = -ec
7474+7575+.PHONY: all
7676+all: build
7777+7878+##@ General
7979+8080+# The help target prints out all targets with their descriptions organized
8181+# beneath their categories. The categories are represented by '##@' and the
8282+# target descriptions by '##'. The awk command is responsible for reading the
8383+# entire set of makefiles included in this invocation, looking for lines of the
8484+# file as xyz: ## something, and then pretty-format the target and help. Then,
8585+# if there's a line with ##@ something, that gets pretty-printed as a category.
8686+# More info on the usage of ANSI control characters for terminal formatting:
8787+# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters
8888+# More info on the awk command:
8989+# http://linuxcommand.org/lc3_adv_awk.php
9090+9191+.PHONY: help
9292+help: ## Display this help.
9393+ @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)
9494+9595+##@ Development
9696+9797+.PHONY: manifests
9898+manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects.
9999+ $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases
100100+101101+.PHONY: generate
102102+generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations.
103103+ $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..."
104104+105105+.PHONY: fmt
106106+fmt: ## Run go fmt against code.
107107+ go fmt ./...
108108+109109+.PHONY: vet
110110+vet: ## Run go vet against code.
111111+ go vet ./...
112112+113113+.PHONY: test
114114+test: manifests generate fmt vet envtest ## Run tests.
115115+ KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
116116+117117+# Utilize Kind or modify the e2e tests to load the image locally, enabling compatibility with other vendors.
118118+.PHONY: test-e2e # Run the e2e tests against a Kind k8s instance that is spun up.
119119+test-e2e:
120120+ go test ./test/e2e/ -v -ginkgo.v
121121+122122+.PHONY: lint
123123+lint: golangci-lint ## Run golangci-lint linter & yamllint
124124+ $(GOLANGCI_LINT) run
125125+126126+.PHONY: lint-fix
127127+lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes
128128+ $(GOLANGCI_LINT) run --fix
129129+130130+##@ Build
131131+132132+.PHONY: build
133133+build: manifests generate fmt vet ## Build manager binary.
134134+ go build -o bin/manager cmd/main.go
135135+136136+.PHONY: run
137137+run: manifests generate fmt vet ## Run a controller from your host.
138138+ go run ./cmd/main.go
139139+140140+# If you wish to build the manager image targeting other platforms you can use the --platform flag.
141141+# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
142142+# More info: https://docs.docker.com/develop/develop-images/build_enhancements/
143143+.PHONY: docker-build
144144+docker-build: ## Build docker image with the manager.
145145+ $(CONTAINER_TOOL) build -t ${IMG} .
146146+147147+.PHONY: docker-push
148148+docker-push: ## Push docker image with the manager.
149149+ $(CONTAINER_TOOL) push ${IMG}
150150+151151+# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple
152152+# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to:
153153+# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/
154154+# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/
155155+# - 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)
156156+# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option.
157157+PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
158158+.PHONY: docker-buildx
159159+docker-buildx: ## Build and push docker image for the manager for cross-platform support
160160+ # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
161161+ sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
162162+ - $(CONTAINER_TOOL) buildx create --name project-v3-builder
163163+ $(CONTAINER_TOOL) buildx use project-v3-builder
164164+ - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
165165+ - $(CONTAINER_TOOL) buildx rm project-v3-builder
166166+ rm Dockerfile.cross
167167+168168+.PHONY: build-installer
169169+build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
170170+ mkdir -p dist
171171+ cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
172172+ $(KUSTOMIZE) build config/default > dist/install.yaml
173173+174174+##@ Deployment
175175+176176+ifndef ignore-not-found
177177+ ignore-not-found = false
178178+endif
179179+180180+.PHONY: install
181181+install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config.
182182+ $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f -
183183+184184+.PHONY: uninstall
185185+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.
186186+ $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
187187+188188+.PHONY: deploy
189189+deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
190190+ cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
191191+ $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -
192192+193193+.PHONY: undeploy
194194+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.
195195+ $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
196196+197197+##@ Dependencies
198198+199199+## Location to install dependencies to
200200+LOCALBIN ?= $(shell pwd)/bin
201201+$(LOCALBIN):
202202+ mkdir -p $(LOCALBIN)
203203+204204+## Tool Binaries
205205+KUBECTL ?= kubectl
206206+KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION)
207207+CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION)
208208+ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION)
209209+GOLANGCI_LINT = $(LOCALBIN)/golangci-lint-$(GOLANGCI_LINT_VERSION)
210210+211211+## Tool Versions
212212+KUSTOMIZE_VERSION ?= v5.3.0
213213+CONTROLLER_TOOLS_VERSION ?= v0.14.0
214214+ENVTEST_VERSION ?= release-0.17
215215+GOLANGCI_LINT_VERSION ?= v1.57.2
216216+217217+.PHONY: kustomize
218218+kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
219219+$(KUSTOMIZE): $(LOCALBIN)
220220+ $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))
221221+222222+.PHONY: controller-gen
223223+controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
224224+$(CONTROLLER_GEN): $(LOCALBIN)
225225+ $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION))
226226+227227+.PHONY: envtest
228228+envtest: $(ENVTEST) ## Download setup-envtest locally if necessary.
229229+$(ENVTEST): $(LOCALBIN)
230230+ $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION))
231231+232232+.PHONY: golangci-lint
233233+golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary.
234234+$(GOLANGCI_LINT): $(LOCALBIN)
235235+ $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,${GOLANGCI_LINT_VERSION})
236236+237237+# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist
238238+# $1 - target path with name of binary (ideally with version)
239239+# $2 - package url which can be installed
240240+# $3 - specific version of package
241241+define go-install-tool
242242+@[ -f $(1) ] || { \
243243+set -e; \
244244+package=$(2)@$(3) ;\
245245+echo "Downloading $${package}" ;\
246246+GOBIN=$(LOCALBIN) go install $${package} ;\
247247+mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\
248248+}
249249+endef
250250+251251+.PHONY: operator-sdk
252252+OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk
253253+operator-sdk: ## Download operator-sdk locally if necessary.
254254+ifeq (,$(wildcard $(OPERATOR_SDK)))
255255+ifeq (, $(shell which operator-sdk 2>/dev/null))
256256+ @{ \
257257+ set -e ;\
258258+ mkdir -p $(dir $(OPERATOR_SDK)) ;\
259259+ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
260260+ curl -sSLo $(OPERATOR_SDK) https://github.com/operator-framework/operator-sdk/releases/download/$(OPERATOR_SDK_VERSION)/operator-sdk_$${OS}_$${ARCH} ;\
261261+ chmod +x $(OPERATOR_SDK) ;\
262262+ }
263263+else
264264+OPERATOR_SDK = $(shell which operator-sdk)
265265+endif
266266+endif
267267+268268+.PHONY: bundle
269269+bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metadata, then validate generated files.
270270+ $(OPERATOR_SDK) generate kustomize manifests -q
271271+ cd config/manager && $(KUSTOMIZE) edit set image controller=$(IMG)
272272+ $(KUSTOMIZE) build config/manifests | $(OPERATOR_SDK) generate bundle $(BUNDLE_GEN_FLAGS)
273273+ $(OPERATOR_SDK) bundle validate ./bundle
274274+275275+.PHONY: bundle-build
276276+bundle-build: ## Build the bundle image.
277277+ docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) .
278278+279279+.PHONY: bundle-push
280280+bundle-push: ## Push the bundle image.
281281+ $(MAKE) docker-push IMG=$(BUNDLE_IMG)
282282+283283+.PHONY: opm
284284+OPM = $(LOCALBIN)/opm
285285+opm: ## Download opm locally if necessary.
286286+ifeq (,$(wildcard $(OPM)))
287287+ifeq (,$(shell which opm 2>/dev/null))
288288+ @{ \
289289+ set -e ;\
290290+ mkdir -p $(dir $(OPM)) ;\
291291+ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \
292292+ curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\
293293+ chmod +x $(OPM) ;\
294294+ }
295295+else
296296+OPM = $(shell which opm)
297297+endif
298298+endif
299299+300300+# A comma-separated list of bundle images (e.g. make catalog-build BUNDLE_IMGS=example.com/operator-bundle:v0.1.0,example.com/operator-bundle:v0.2.0).
301301+# These images MUST exist in a registry and be pull-able.
302302+BUNDLE_IMGS ?= $(BUNDLE_IMG)
303303+304304+# The image tag given to the resulting catalog image (e.g. make catalog-build CATALOG_IMG=example.com/operator-catalog:v0.2.0).
305305+CATALOG_IMG ?= $(IMAGE_TAG_BASE)-catalog:v$(VERSION)
306306+307307+# Set CATALOG_BASE_IMG to an existing catalog image tag to add $BUNDLE_IMGS to that image.
308308+ifneq ($(origin CATALOG_BASE_IMG), undefined)
309309+FROM_INDEX_OPT := --from-index $(CATALOG_BASE_IMG)
310310+endif
311311+312312+# Build a catalog image by adding bundle images to an empty catalog using the operator package manager tool, 'opm'.
313313+# This recipe invokes 'opm' in 'semver' bundle add mode. For more information on add modes, see:
314314+# https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator
315315+.PHONY: catalog-build
316316+catalog-build: opm ## Build a catalog image.
317317+ $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT)
318318+319319+# Push the catalog image.
320320+.PHONY: catalog-push
321321+catalog-push: ## Push a catalog image.
322322+ $(MAKE) docker-push IMG=$(CATALOG_IMG)
+23
PROJECT
···11+# Code generated by tool. DO NOT EDIT.
22+# This file is used to track the info used to scaffold your project
33+# and allow the plugins properly work.
44+# More info: https://book.kubebuilder.io/reference/project-config.html
55+domain: j5t.io
66+layout:
77+- go.kubebuilder.io/v4
88+plugins:
99+ manifests.sdk.operatorframework.io/v2: {}
1010+ scorecard.sdk.operatorframework.io/v2: {}
1111+projectName: secret-service-operator
1212+repo: github.com/evanjarrett/secret-service-operator
1313+resources:
1414+- api:
1515+ crdVersion: v1
1616+ namespaced: true
1717+ controller: true
1818+ domain: j5t.io
1919+ group: apps
2020+ kind: SecretService
2121+ path: github.com/evanjarrett/secret-service-operator/api/v1
2222+ version: v1
2323+version: "3"
+114
README.md
···11+# secret-service-operator
22+// TODO(user): Add simple overview of use/purpose
33+44+## Description
55+// TODO(user): An in-depth paragraph about your project and overview of use
66+77+## Getting Started
88+99+### Prerequisites
1010+- go version v1.21.0+
1111+- docker version 17.03+.
1212+- kubectl version v1.11.3+.
1313+- Access to a Kubernetes v1.11.3+ cluster.
1414+1515+### To Deploy on the cluster
1616+**Build and push your image to the location specified by `IMG`:**
1717+1818+```sh
1919+make docker-build docker-push IMG=<some-registry>/secret-service-operator:tag
2020+```
2121+2222+**NOTE:** This image ought to be published in the personal registry you specified.
2323+And it is required to have access to pull the image from the working environment.
2424+Make sure you have the proper permission to the registry if the above commands don’t work.
2525+2626+**Install the CRDs into the cluster:**
2727+2828+```sh
2929+make install
3030+```
3131+3232+**Deploy the Manager to the cluster with the image specified by `IMG`:**
3333+3434+```sh
3535+make deploy IMG=<some-registry>/secret-service-operator:tag
3636+```
3737+3838+> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin
3939+privileges or be logged in as admin.
4040+4141+**Create instances of your solution**
4242+You can apply the samples (examples) from the config/sample:
4343+4444+```sh
4545+kubectl apply -k config/samples/
4646+```
4747+4848+>**NOTE**: Ensure that the samples has default values to test it out.
4949+5050+### To Uninstall
5151+**Delete the instances (CRs) from the cluster:**
5252+5353+```sh
5454+kubectl delete -k config/samples/
5555+```
5656+5757+**Delete the APIs(CRDs) from the cluster:**
5858+5959+```sh
6060+make uninstall
6161+```
6262+6363+**UnDeploy the controller from the cluster:**
6464+6565+```sh
6666+make undeploy
6767+```
6868+6969+## Project Distribution
7070+7171+Following are the steps to build the installer and distribute this project to users.
7272+7373+1. Build the installer for the image built and published in the registry:
7474+7575+```sh
7676+make build-installer IMG=<some-registry>/secret-service-operator:tag
7777+```
7878+7979+NOTE: The makefile target mentioned above generates an 'install.yaml'
8080+file in the dist directory. This file contains all the resources built
8181+with Kustomize, which are necessary to install this project without
8282+its dependencies.
8383+8484+2. Using the installer
8585+8686+Users can just run kubectl apply -f <URL for YAML BUNDLE> to install the project, i.e.:
8787+8888+```sh
8989+kubectl apply -f https://raw.githubusercontent.com/<org>/secret-service-operator/<tag or branch>/dist/install.yaml
9090+```
9191+9292+## Contributing
9393+// TODO(user): Add detailed information on how you would like others to contribute to this project
9494+9595+**NOTE:** Run `make help` for more information on all potential `make` targets
9696+9797+More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
9898+9999+## License
100100+101101+Copyright 2024.
102102+103103+Licensed under the Apache License, Version 2.0 (the "License");
104104+you may not use this file except in compliance with the License.
105105+You may obtain a copy of the License at
106106+107107+ http://www.apache.org/licenses/LICENSE-2.0
108108+109109+Unless required by applicable law or agreed to in writing, software
110110+distributed under the License is distributed on an "AS IS" BASIS,
111111+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
112112+See the License for the specific language governing permissions and
113113+limitations under the License.
114114+
+36
api/v1/groupversion_info.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+// Package v1 contains API Schema definitions for the apps v1 API group
1818+// +kubebuilder:object:generate=true
1919+// +groupName=apps.j5t.io
2020+package v1
2121+2222+import (
2323+ "k8s.io/apimachinery/pkg/runtime/schema"
2424+ "sigs.k8s.io/controller-runtime/pkg/scheme"
2525+)
2626+2727+var (
2828+ // GroupVersion is group version used to register these objects
2929+ GroupVersion = schema.GroupVersion{Group: "apps.j5t.io", Version: "v1"}
3030+3131+ // SchemeBuilder is used to add go types to the GroupVersionKind scheme
3232+ SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
3333+3434+ // AddToScheme adds the types in this group-version to the given scheme.
3535+ AddToScheme = SchemeBuilder.AddToScheme
3636+)
+63
api/v1/secretservice_types.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package v1
1818+1919+import (
2020+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2121+)
2222+2323+// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
2424+// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
2525+2626+// SecretServiceSpec defines the desired state of SecretService
2727+type SecretServiceSpec struct {
2828+ // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
2929+ // Important: Run "make" to regenerate code after modifying this file
3030+3131+ SecretName string `json:"secretName"`
3232+}
3333+3434+// SecretServiceStatus defines the observed state of SecretService
3535+type SecretServiceStatus struct {
3636+ // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
3737+ // Important: Run "make" to regenerate code after modifying this file
3838+}
3939+4040+//+kubebuilder:object:root=true
4141+//+kubebuilder:subresource:status
4242+4343+// SecretService is the Schema for the secretservices API
4444+type SecretService struct {
4545+ metav1.TypeMeta `json:",inline"`
4646+ metav1.ObjectMeta `json:"metadata,omitempty"`
4747+4848+ Spec SecretServiceSpec `json:"spec,omitempty"`
4949+ Status SecretServiceStatus `json:"status,omitempty"`
5050+}
5151+5252+//+kubebuilder:object:root=true
5353+5454+// SecretServiceList contains a list of SecretService
5555+type SecretServiceList struct {
5656+ metav1.TypeMeta `json:",inline"`
5757+ metav1.ListMeta `json:"metadata,omitempty"`
5858+ Items []SecretService `json:"items"`
5959+}
6060+6161+func init() {
6262+ SchemeBuilder.Register(&SecretService{}, &SecretServiceList{})
6363+}
+114
api/v1/zz_generated.deepcopy.go
···11+//go:build !ignore_autogenerated
22+33+/*
44+Copyright 2024.
55+66+Licensed under the Apache License, Version 2.0 (the "License");
77+you may not use this file except in compliance with the License.
88+You may obtain a copy of the License at
99+1010+ http://www.apache.org/licenses/LICENSE-2.0
1111+1212+Unless required by applicable law or agreed to in writing, software
1313+distributed under the License is distributed on an "AS IS" BASIS,
1414+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1515+See the License for the specific language governing permissions and
1616+limitations under the License.
1717+*/
1818+1919+// Code generated by controller-gen. DO NOT EDIT.
2020+2121+package v1
2222+2323+import (
2424+ runtime "k8s.io/apimachinery/pkg/runtime"
2525+)
2626+2727+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
2828+func (in *SecretService) DeepCopyInto(out *SecretService) {
2929+ *out = *in
3030+ out.TypeMeta = in.TypeMeta
3131+ in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
3232+ out.Spec = in.Spec
3333+ out.Status = in.Status
3434+}
3535+3636+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretService.
3737+func (in *SecretService) DeepCopy() *SecretService {
3838+ if in == nil {
3939+ return nil
4040+ }
4141+ out := new(SecretService)
4242+ in.DeepCopyInto(out)
4343+ return out
4444+}
4545+4646+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
4747+func (in *SecretService) DeepCopyObject() runtime.Object {
4848+ if c := in.DeepCopy(); c != nil {
4949+ return c
5050+ }
5151+ return nil
5252+}
5353+5454+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
5555+func (in *SecretServiceList) DeepCopyInto(out *SecretServiceList) {
5656+ *out = *in
5757+ out.TypeMeta = in.TypeMeta
5858+ in.ListMeta.DeepCopyInto(&out.ListMeta)
5959+ if in.Items != nil {
6060+ in, out := &in.Items, &out.Items
6161+ *out = make([]SecretService, len(*in))
6262+ for i := range *in {
6363+ (*in)[i].DeepCopyInto(&(*out)[i])
6464+ }
6565+ }
6666+}
6767+6868+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretServiceList.
6969+func (in *SecretServiceList) DeepCopy() *SecretServiceList {
7070+ if in == nil {
7171+ return nil
7272+ }
7373+ out := new(SecretServiceList)
7474+ in.DeepCopyInto(out)
7575+ return out
7676+}
7777+7878+// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
7979+func (in *SecretServiceList) DeepCopyObject() runtime.Object {
8080+ if c := in.DeepCopy(); c != nil {
8181+ return c
8282+ }
8383+ return nil
8484+}
8585+8686+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
8787+func (in *SecretServiceSpec) DeepCopyInto(out *SecretServiceSpec) {
8888+ *out = *in
8989+}
9090+9191+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretServiceSpec.
9292+func (in *SecretServiceSpec) DeepCopy() *SecretServiceSpec {
9393+ if in == nil {
9494+ return nil
9595+ }
9696+ out := new(SecretServiceSpec)
9797+ in.DeepCopyInto(out)
9898+ return out
9999+}
100100+101101+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
102102+func (in *SecretServiceStatus) DeepCopyInto(out *SecretServiceStatus) {
103103+ *out = *in
104104+}
105105+106106+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SecretServiceStatus.
107107+func (in *SecretServiceStatus) DeepCopy() *SecretServiceStatus {
108108+ if in == nil {
109109+ return nil
110110+ }
111111+ out := new(SecretServiceStatus)
112112+ in.DeepCopyInto(out)
113113+ return out
114114+}
+148
cmd/main.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package main
1818+1919+import (
2020+ "crypto/tls"
2121+ "flag"
2222+ "os"
2323+2424+ // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
2525+ // to ensure that exec-entrypoint and run can make use of them.
2626+ _ "k8s.io/client-go/plugin/pkg/client/auth"
2727+2828+ "k8s.io/apimachinery/pkg/runtime"
2929+ utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3030+ clientgoscheme "k8s.io/client-go/kubernetes/scheme"
3131+ ctrl "sigs.k8s.io/controller-runtime"
3232+ "sigs.k8s.io/controller-runtime/pkg/healthz"
3333+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
3434+ metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
3535+ "sigs.k8s.io/controller-runtime/pkg/webhook"
3636+3737+ appsv1 "github.com/evanjarrett/secret-service-operator/api/v1"
3838+ "github.com/evanjarrett/secret-service-operator/internal/controller"
3939+ //+kubebuilder:scaffold:imports
4040+)
4141+4242+var (
4343+ scheme = runtime.NewScheme()
4444+ setupLog = ctrl.Log.WithName("setup")
4545+)
4646+4747+func init() {
4848+ utilruntime.Must(clientgoscheme.AddToScheme(scheme))
4949+5050+ utilruntime.Must(appsv1.AddToScheme(scheme))
5151+ //+kubebuilder:scaffold:scheme
5252+}
5353+5454+func main() {
5555+ var metricsAddr string
5656+ var enableLeaderElection bool
5757+ var probeAddr string
5858+ var secureMetrics bool
5959+ var enableHTTP2 bool
6060+ flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
6161+ flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
6262+ flag.BoolVar(&enableLeaderElection, "leader-elect", false,
6363+ "Enable leader election for controller manager. "+
6464+ "Enabling this will ensure there is only one active controller manager.")
6565+ flag.BoolVar(&secureMetrics, "metrics-secure", false,
6666+ "If set the metrics endpoint is served securely")
6767+ flag.BoolVar(&enableHTTP2, "enable-http2", false,
6868+ "If set, HTTP/2 will be enabled for the metrics and webhook servers")
6969+ opts := zap.Options{
7070+ Development: true,
7171+ }
7272+ opts.BindFlags(flag.CommandLine)
7373+ flag.Parse()
7474+7575+ ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts)))
7676+7777+ // if the enable-http2 flag is false (the default), http/2 should be disabled
7878+ // due to its vulnerabilities. More specifically, disabling http/2 will
7979+ // prevent from being vulnerable to the HTTP/2 Stream Cancellation and
8080+ // Rapid Reset CVEs. For more information see:
8181+ // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3
8282+ // - https://github.com/advisories/GHSA-4374-p667-p6c8
8383+ disableHTTP2 := func(c *tls.Config) {
8484+ setupLog.Info("disabling http/2")
8585+ c.NextProtos = []string{"http/1.1"}
8686+ }
8787+8888+ tlsOpts := []func(*tls.Config){}
8989+ if !enableHTTP2 {
9090+ tlsOpts = append(tlsOpts, disableHTTP2)
9191+ }
9292+9393+ webhookServer := webhook.NewServer(webhook.Options{
9494+ TLSOpts: tlsOpts,
9595+ })
9696+9797+ mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
9898+ Scheme: scheme,
9999+ Metrics: metricsserver.Options{
100100+ BindAddress: metricsAddr,
101101+ SecureServing: secureMetrics,
102102+ TLSOpts: tlsOpts,
103103+ },
104104+ WebhookServer: webhookServer,
105105+ HealthProbeBindAddress: probeAddr,
106106+ LeaderElection: enableLeaderElection,
107107+ LeaderElectionID: "edaf68d8.j5t.io",
108108+ // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily
109109+ // when the Manager ends. This requires the binary to immediately end when the
110110+ // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly
111111+ // speeds up voluntary leader transitions as the new leader don't have to wait
112112+ // LeaseDuration time first.
113113+ //
114114+ // In the default scaffold provided, the program ends immediately after
115115+ // the manager stops, so would be fine to enable this option. However,
116116+ // if you are doing or is intended to do any operation such as perform cleanups
117117+ // after the manager stops then its usage might be unsafe.
118118+ // LeaderElectionReleaseOnCancel: true,
119119+ })
120120+ if err != nil {
121121+ setupLog.Error(err, "unable to start manager")
122122+ os.Exit(1)
123123+ }
124124+125125+ if err = (&controller.SecretServiceReconciler{
126126+ Client: mgr.GetClient(),
127127+ Scheme: mgr.GetScheme(),
128128+ }).SetupWithManager(mgr); err != nil {
129129+ setupLog.Error(err, "unable to create controller", "controller", "SecretService")
130130+ os.Exit(1)
131131+ }
132132+ //+kubebuilder:scaffold:builder
133133+134134+ if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
135135+ setupLog.Error(err, "unable to set up health check")
136136+ os.Exit(1)
137137+ }
138138+ if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
139139+ setupLog.Error(err, "unable to set up ready check")
140140+ os.Exit(1)
141141+ }
142142+143143+ setupLog.Info("starting manager")
144144+ if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
145145+ setupLog.Error(err, "problem running manager")
146146+ os.Exit(1)
147147+ }
148148+}
+54
config/crd/bases/apps.j5t.io_secretservices.yaml
···11+---
22+apiVersion: apiextensions.k8s.io/v1
33+kind: CustomResourceDefinition
44+metadata:
55+ annotations:
66+ controller-gen.kubebuilder.io/version: v0.14.0
77+ name: secretservices.apps.j5t.io
88+spec:
99+ group: apps.j5t.io
1010+ names:
1111+ kind: SecretService
1212+ listKind: SecretServiceList
1313+ plural: secretservices
1414+ singular: secretservice
1515+ scope: Namespaced
1616+ versions:
1717+ - name: v1
1818+ schema:
1919+ openAPIV3Schema:
2020+ description: SecretService is the Schema for the secretservices API
2121+ properties:
2222+ apiVersion:
2323+ description: |-
2424+ APIVersion defines the versioned schema of this representation of an object.
2525+ Servers should convert recognized schemas to the latest internal value, and
2626+ may reject unrecognized values.
2727+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
2828+ type: string
2929+ kind:
3030+ description: |-
3131+ Kind is a string value representing the REST resource this object represents.
3232+ Servers may infer this from the endpoint the client submits requests to.
3333+ Cannot be updated.
3434+ In CamelCase.
3535+ More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
3636+ type: string
3737+ metadata:
3838+ type: object
3939+ spec:
4040+ description: SecretServiceSpec defines the desired state of SecretService
4141+ properties:
4242+ secretName:
4343+ type: string
4444+ required:
4545+ - secretName
4646+ type: object
4747+ status:
4848+ description: SecretServiceStatus defines the observed state of SecretService
4949+ type: object
5050+ type: object
5151+ served: true
5252+ storage: true
5353+ subresources:
5454+ status: {}
+22
config/crd/kustomization.yaml
···11+# This kustomization.yaml is not intended to be run by itself,
22+# since it depends on service name and namespace that are out of this kustomize package.
33+# It should be run by config/default
44+resources:
55+- bases/apps.j5t.io_secretservices.yaml
66+#+kubebuilder:scaffold:crdkustomizeresource
77+88+patches:
99+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
1010+# patches here are for enabling the conversion webhook for each CRD
1111+#+kubebuilder:scaffold:crdkustomizewebhookpatch
1212+1313+# [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix.
1414+# patches here are for enabling the CA injection for each CRD
1515+#- path: patches/cainjection_in_secretservices.yaml
1616+#+kubebuilder:scaffold:crdkustomizecainjectionpatch
1717+1818+# [WEBHOOK] To enable webhook, uncomment the following section
1919+# the following config is for teaching kustomize how to do kustomization for CRDs.
2020+2121+#configurations:
2222+#- kustomizeconfig.yaml
+19
config/crd/kustomizeconfig.yaml
···11+# This file is for teaching kustomize how to substitute name and namespace reference in CRD
22+nameReference:
33+- kind: Service
44+ version: v1
55+ fieldSpecs:
66+ - kind: CustomResourceDefinition
77+ version: v1
88+ group: apiextensions.k8s.io
99+ path: spec/conversion/webhook/clientConfig/service/name
1010+1111+namespace:
1212+- kind: CustomResourceDefinition
1313+ version: v1
1414+ group: apiextensions.k8s.io
1515+ path: spec/conversion/webhook/clientConfig/service/namespace
1616+ create: false
1717+1818+varReference:
1919+- path: metadata/annotations
+142
config/default/kustomization.yaml
···11+# Adds namespace to all resources.
22+namespace: secret-service-operator-system
33+44+# Value of this field is prepended to the
55+# names of all resources, e.g. a deployment named
66+# "wordpress" becomes "alices-wordpress".
77+# Note that it should also match with the prefix (text before '-') of the namespace
88+# field above.
99+namePrefix: secret-service-operator-
1010+1111+# Labels to add to all resources and selectors.
1212+#labels:
1313+#- includeSelectors: true
1414+# pairs:
1515+# someName: someValue
1616+1717+resources:
1818+- ../crd
1919+- ../rbac
2020+- ../manager
2121+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
2222+# crd/kustomization.yaml
2323+#- ../webhook
2424+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
2525+#- ../certmanager
2626+# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
2727+#- ../prometheus
2828+2929+patches:
3030+# Protect the /metrics endpoint by putting it behind auth.
3131+# If you want your controller-manager to expose the /metrics
3232+# endpoint w/o any authn/z, please comment the following line.
3333+- path: manager_auth_proxy_patch.yaml
3434+3535+# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
3636+# crd/kustomization.yaml
3737+#- path: manager_webhook_patch.yaml
3838+3939+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
4040+# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
4141+# 'CERTMANAGER' needs to be enabled to use ca injection
4242+#- path: webhookcainjection_patch.yaml
4343+4444+# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
4545+# Uncomment the following replacements to add the cert-manager CA injection annotations
4646+#replacements:
4747+# - source: # Add cert-manager annotation to ValidatingWebhookConfiguration, MutatingWebhookConfiguration and CRDs
4848+# kind: Certificate
4949+# group: cert-manager.io
5050+# version: v1
5151+# name: serving-cert # this name should match the one in certificate.yaml
5252+# fieldPath: .metadata.namespace # namespace of the certificate CR
5353+# targets:
5454+# - select:
5555+# kind: ValidatingWebhookConfiguration
5656+# fieldPaths:
5757+# - .metadata.annotations.[cert-manager.io/inject-ca-from]
5858+# options:
5959+# delimiter: '/'
6060+# index: 0
6161+# create: true
6262+# - select:
6363+# kind: MutatingWebhookConfiguration
6464+# fieldPaths:
6565+# - .metadata.annotations.[cert-manager.io/inject-ca-from]
6666+# options:
6767+# delimiter: '/'
6868+# index: 0
6969+# create: true
7070+# - select:
7171+# kind: CustomResourceDefinition
7272+# fieldPaths:
7373+# - .metadata.annotations.[cert-manager.io/inject-ca-from]
7474+# options:
7575+# delimiter: '/'
7676+# index: 0
7777+# create: true
7878+# - source:
7979+# kind: Certificate
8080+# group: cert-manager.io
8181+# version: v1
8282+# name: serving-cert # this name should match the one in certificate.yaml
8383+# fieldPath: .metadata.name
8484+# targets:
8585+# - select:
8686+# kind: ValidatingWebhookConfiguration
8787+# fieldPaths:
8888+# - .metadata.annotations.[cert-manager.io/inject-ca-from]
8989+# options:
9090+# delimiter: '/'
9191+# index: 1
9292+# create: true
9393+# - select:
9494+# kind: MutatingWebhookConfiguration
9595+# fieldPaths:
9696+# - .metadata.annotations.[cert-manager.io/inject-ca-from]
9797+# options:
9898+# delimiter: '/'
9999+# index: 1
100100+# create: true
101101+# - select:
102102+# kind: CustomResourceDefinition
103103+# fieldPaths:
104104+# - .metadata.annotations.[cert-manager.io/inject-ca-from]
105105+# options:
106106+# delimiter: '/'
107107+# index: 1
108108+# create: true
109109+# - source: # Add cert-manager annotation to the webhook Service
110110+# kind: Service
111111+# version: v1
112112+# name: webhook-service
113113+# fieldPath: .metadata.name # namespace of the service
114114+# targets:
115115+# - select:
116116+# kind: Certificate
117117+# group: cert-manager.io
118118+# version: v1
119119+# fieldPaths:
120120+# - .spec.dnsNames.0
121121+# - .spec.dnsNames.1
122122+# options:
123123+# delimiter: '.'
124124+# index: 0
125125+# create: true
126126+# - source:
127127+# kind: Service
128128+# version: v1
129129+# name: webhook-service
130130+# fieldPath: .metadata.namespace # namespace of the service
131131+# targets:
132132+# - select:
133133+# kind: Certificate
134134+# group: cert-manager.io
135135+# version: v1
136136+# fieldPaths:
137137+# - .spec.dnsNames.0
138138+# - .spec.dnsNames.1
139139+# options:
140140+# delimiter: '.'
141141+# index: 1
142142+# create: true
+39
config/default/manager_auth_proxy_patch.yaml
···11+# This patch inject a sidecar container which is a HTTP proxy for the
22+# controller manager, it performs RBAC authorization against the Kubernetes API using SubjectAccessReviews.
33+apiVersion: apps/v1
44+kind: Deployment
55+metadata:
66+ name: controller-manager
77+ namespace: system
88+spec:
99+ template:
1010+ spec:
1111+ containers:
1212+ - name: kube-rbac-proxy
1313+ securityContext:
1414+ allowPrivilegeEscalation: false
1515+ capabilities:
1616+ drop:
1717+ - "ALL"
1818+ image: gcr.io/kubebuilder/kube-rbac-proxy:v0.16.0
1919+ args:
2020+ - "--secure-listen-address=0.0.0.0:8443"
2121+ - "--upstream=http://127.0.0.1:8080/"
2222+ - "--logtostderr=true"
2323+ - "--v=0"
2424+ ports:
2525+ - containerPort: 8443
2626+ protocol: TCP
2727+ name: https
2828+ resources:
2929+ limits:
3030+ cpu: 500m
3131+ memory: 128Mi
3232+ requests:
3333+ cpu: 5m
3434+ memory: 64Mi
3535+ - name: manager
3636+ args:
3737+ - "--health-probe-bind-address=:8081"
3838+ - "--metrics-bind-address=127.0.0.1:8080"
3939+ - "--leader-elect"
···11+apiVersion: v1
22+kind: Namespace
33+metadata:
44+ labels:
55+ control-plane: controller-manager
66+ app.kubernetes.io/name: secret-service-operator
77+ app.kubernetes.io/managed-by: kustomize
88+ name: system
99+---
1010+apiVersion: apps/v1
1111+kind: Deployment
1212+metadata:
1313+ name: controller-manager
1414+ namespace: system
1515+ labels:
1616+ control-plane: controller-manager
1717+ app.kubernetes.io/name: secret-service-operator
1818+ app.kubernetes.io/managed-by: kustomize
1919+spec:
2020+ selector:
2121+ matchLabels:
2222+ control-plane: controller-manager
2323+ replicas: 1
2424+ template:
2525+ metadata:
2626+ annotations:
2727+ kubectl.kubernetes.io/default-container: manager
2828+ labels:
2929+ control-plane: controller-manager
3030+ spec:
3131+ # TODO(user): Uncomment the following code to configure the nodeAffinity expression
3232+ # according to the platforms which are supported by your solution.
3333+ # It is considered best practice to support multiple architectures. You can
3434+ # build your manager image using the makefile target docker-buildx.
3535+ # affinity:
3636+ # nodeAffinity:
3737+ # requiredDuringSchedulingIgnoredDuringExecution:
3838+ # nodeSelectorTerms:
3939+ # - matchExpressions:
4040+ # - key: kubernetes.io/arch
4141+ # operator: In
4242+ # values:
4343+ # - amd64
4444+ # - arm64
4545+ # - ppc64le
4646+ # - s390x
4747+ # - key: kubernetes.io/os
4848+ # operator: In
4949+ # values:
5050+ # - linux
5151+ securityContext:
5252+ runAsNonRoot: true
5353+ # TODO(user): For common cases that do not require escalating privileges
5454+ # it is recommended to ensure that all your Pods/Containers are restrictive.
5555+ # More info: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
5656+ # Please uncomment the following code if your project does NOT have to work on old Kubernetes
5757+ # versions < 1.19 or on vendors versions which do NOT support this field by default (i.e. Openshift < 4.11 ).
5858+ # seccompProfile:
5959+ # type: RuntimeDefault
6060+ containers:
6161+ - command:
6262+ - /manager
6363+ args:
6464+ - --leader-elect
6565+ image: controller:latest
6666+ name: manager
6767+ securityContext:
6868+ allowPrivilegeEscalation: false
6969+ capabilities:
7070+ drop:
7171+ - "ALL"
7272+ livenessProbe:
7373+ httpGet:
7474+ path: /healthz
7575+ port: 8081
7676+ initialDelaySeconds: 15
7777+ periodSeconds: 20
7878+ readinessProbe:
7979+ httpGet:
8080+ path: /readyz
8181+ port: 8081
8282+ initialDelaySeconds: 5
8383+ periodSeconds: 10
8484+ # TODO(user): Configure the resources accordingly based on the project requirements.
8585+ # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
8686+ resources:
8787+ limits:
8888+ cpu: 500m
8989+ memory: 128Mi
9090+ requests:
9191+ cpu: 10m
9292+ memory: 64Mi
9393+ serviceAccountName: controller-manager
9494+ terminationGracePeriodSeconds: 10
+28
config/manifests/kustomization.yaml
···11+# These resources constitute the fully configured set of manifests
22+# used to generate the 'manifests/' directory in a bundle.
33+resources:
44+- bases/secret-service-operator.clusterserviceversion.yaml
55+- ../default
66+- ../samples
77+- ../scorecard
88+99+# [WEBHOOK] To enable webhooks, uncomment all the sections with [WEBHOOK] prefix.
1010+# Do NOT uncomment sections with prefix [CERTMANAGER], as OLM does not support cert-manager.
1111+# These patches remove the unnecessary "cert" volume and its manager container volumeMount.
1212+#patchesJson6902:
1313+#- target:
1414+# group: apps
1515+# version: v1
1616+# kind: Deployment
1717+# name: controller-manager
1818+# namespace: system
1919+# patch: |-
2020+# # Remove the manager container's "cert" volumeMount, since OLM will create and mount a set of certs.
2121+# # Update the indices in this path if adding or removing containers/volumeMounts in the manager's Deployment.
2222+# - op: remove
2323+2424+# path: /spec/template/spec/containers/0/volumeMounts/0
2525+# # Remove the "cert" volume, since OLM will create and mount a set of certs.
2626+# # Update the indices in this path if adding or removing volumes in the manager's Deployment.
2727+# - op: remove
2828+# path: /spec/template/spec/volumes/0
···11+resources:
22+# All RBAC will be applied under this service account in
33+# the deployment namespace. You may comment out this resource
44+# if your manager will use a service account that exists at
55+# runtime. Be sure to update RoleBinding and ClusterRoleBinding
66+# subjects if changing service account names.
77+- service_account.yaml
88+- role.yaml
99+- role_binding.yaml
1010+- leader_election_role.yaml
1111+- leader_election_role_binding.yaml
1212+# Comment the following 4 lines if you want to disable
1313+# the auth proxy (https://github.com/brancz/kube-rbac-proxy)
1414+# which protects your /metrics endpoint.
1515+- auth_proxy_service.yaml
1616+- auth_proxy_role.yaml
1717+- auth_proxy_role_binding.yaml
1818+- auth_proxy_client_clusterrole.yaml
1919+# For each CRD, "Editor" and "Viewer" roles are scaffolded by
2020+# default, aiding admins in cluster management. Those roles are
2121+# not used by the Project itself. You can comment the following lines
2222+# if you do not want those helpers be installed with your Project.
2323+- secretservice_editor_role.yaml
2424+- secretservice_viewer_role.yaml
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
+200
internal/controller/secretservice_controller.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package controller
1818+1919+import (
2020+ "context"
2121+ "net"
2222+ "strconv"
2323+ "strings"
2424+ "time"
2525+2626+ "k8s.io/apimachinery/pkg/api/errors"
2727+ "k8s.io/apimachinery/pkg/runtime"
2828+ "k8s.io/apimachinery/pkg/types"
2929+ "k8s.io/apimachinery/pkg/util/intstr"
3030+ ctrl "sigs.k8s.io/controller-runtime"
3131+ "sigs.k8s.io/controller-runtime/pkg/client"
3232+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3333+ "sigs.k8s.io/controller-runtime/pkg/log"
3434+3535+ appsv1 "github.com/evanjarrett/secret-service-operator/api/v1"
3636+ corev1 "k8s.io/api/core/v1"
3737+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3838+)
3939+4040+// SecretServiceReconciler reconciles a SecretService object
4141+type SecretServiceReconciler struct {
4242+ client.Client
4343+ Scheme *runtime.Scheme
4444+}
4545+4646+//+kubebuilder:rbac:groups=apps.j5t.io,resources=secretservices,verbs=get;list;watch;create;update;patch;delete
4747+//+kubebuilder:rbac:groups=apps.j5t.io,resources=secretservices/status,verbs=get;update;patch
4848+//+kubebuilder:rbac:groups=apps.j5t.io,resources=secretservices/finalizers,verbs=update
4949+5050+// Reconcile is part of the main kubernetes reconciliation loop which aims to
5151+// move the current state of the cluster closer to the desired state.
5252+// TODO(user): Modify the Reconcile function to compare the state specified by
5353+// the SecretService object against the actual cluster state, and then
5454+// perform operations to make the cluster state reflect the state specified by
5555+// the user.
5656+//
5757+// For more details, check Reconcile and its Result here:
5858+// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.17.3/pkg/reconcile
5959+func (r *SecretServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
6060+ logger := log.FromContext(ctx)
6161+6262+ // Get the SecretService object
6363+ instance := &appsv1.SecretService{}
6464+ if err := r.Get(ctx, req.NamespacedName, instance); err != nil {
6565+ if errors.IsNotFound(err) {
6666+ return ctrl.Result{}, nil // Don't requeue if the object is gone
6767+ }
6868+ return ctrl.Result{}, err
6969+ }
7070+7171+ // Get the Secret
7272+ secret := &corev1.Secret{}
7373+ if err := r.Get(ctx, types.NamespacedName{Name: instance.Spec.SecretName, Namespace: instance.Namespace}, secret); err != nil {
7474+ if errors.IsNotFound(err) {
7575+ // If the secret doesn't exist, requeue after a delay
7676+ logger.Error(err, "Secret not found", "secretName", instance.Spec.SecretName)
7777+ return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
7878+ }
7979+ return ctrl.Result{}, err
8080+ }
8181+8282+ for serviceName, value := range secret.Data {
8383+8484+ endpointPort := string(value)
8585+ parts := strings.Split(endpointPort, ":")
8686+ if len(parts) != 2 {
8787+ // Log a warning or skip if it doesn't match the expected format, but don't return an error
8888+ logger.Info("Skipping secret key with invalid format:", "key", serviceName, "value", endpointPort)
8989+ continue // Skip this key and process the next one
9090+ }
9191+9292+ endpointIP := parts[0]
9393+ portStr := parts[1]
9494+9595+ // Validate IP address format
9696+ if ip := net.ParseIP(endpointIP); ip == nil {
9797+ logger.Info("Skipping secret key with invalid IP address:", "key", serviceName, "ip", endpointIP)
9898+ continue
9999+ }
100100+101101+ port, err := strconv.Atoi(portStr)
102102+ if err != nil || port < 1 || port > 65535 {
103103+ logger.Info("Skipping secret key with invalid port:", "key", serviceName, "port", portStr)
104104+ continue
105105+ }
106106+107107+ // Create or update the Service (Example: ClusterIP service)
108108+ service := &corev1.Service{
109109+ ObjectMeta: metav1.ObjectMeta{
110110+ Name: serviceName,
111111+ Namespace: instance.Namespace,
112112+ },
113113+ Spec: corev1.ServiceSpec{
114114+ Type: corev1.ServiceTypeClusterIP,
115115+ Ports: []corev1.ServicePort{
116116+ {
117117+ Name: "http",
118118+ Port: int32(port),
119119+ TargetPort: intstr.FromInt(port), // Or string target port if needed
120120+ Protocol: corev1.ProtocolTCP,
121121+ },
122122+ },
123123+ },
124124+ }
125125+126126+ if err := r.CreateOrUpdate(ctx, service, instance); err != nil {
127127+ return ctrl.Result{}, err
128128+ }
129129+130130+ // Create or update Endpoints (for ClusterIP service)
131131+ endpoints := &corev1.Endpoints{
132132+ ObjectMeta: metav1.ObjectMeta{
133133+ Name: serviceName,
134134+ Namespace: instance.Namespace,
135135+ },
136136+ Subsets: []corev1.EndpointSubset{
137137+ {
138138+ Addresses: []corev1.EndpointAddress{
139139+ {
140140+ IP: endpointIP,
141141+ },
142142+ },
143143+ Ports: []corev1.EndpointPort{
144144+ {
145145+ Name: "http",
146146+ Port: int32(port),
147147+ Protocol: corev1.ProtocolTCP,
148148+ },
149149+ },
150150+ },
151151+ },
152152+ }
153153+154154+ if err := r.CreateOrUpdate(ctx, endpoints, instance); err != nil {
155155+ return ctrl.Result{}, err
156156+ }
157157+ }
158158+159159+ return ctrl.Result{}, nil
160160+}
161161+162162+// CreateOrUpdate helper function
163163+func (r *SecretServiceReconciler) CreateOrUpdate(ctx context.Context, obj client.Object, owner metav1.Object) error {
164164+ logger := log.FromContext(ctx)
165165+ key := client.ObjectKeyFromObject(obj)
166166+167167+ // 1. Attempt to get the object. This checks if it already exists.
168168+ if err := r.Client.Get(ctx, key, obj); err != nil {
169169+ // 2. If the object is not found, create it.
170170+ if !errors.IsNotFound(err) {
171171+ return err // Return any error other than NotFound
172172+ }
173173+174174+ // Use Controllerutil to manage the obj
175175+ if err := controllerutil.SetControllerReference(owner, obj, r.Scheme); err != nil {
176176+ return err
177177+ }
178178+179179+ logger.Info("Creating resource", "object", key)
180180+ return r.Client.Create(ctx, obj)
181181+ }
182182+183183+ // 3. If the object exists, update it.
184184+ existing := obj.DeepCopyObject().(client.Object) // Deep copy to avoid modifying the cache
185185+186186+ // Use Patch to apply only the changes, improving efficiency
187187+ if err := r.Client.Patch(ctx, obj, client.MergeFrom(existing)); err != nil {
188188+ return err
189189+ }
190190+191191+ logger.Info("Updated resource", "object", key)
192192+ return nil
193193+}
194194+195195+// SetupWithManager sets up the controller with the Manager.
196196+func (r *SecretServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
197197+ return ctrl.NewControllerManagedBy(mgr).
198198+ For(&appsv1.SecretService{}).
199199+ Complete(r)
200200+}
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package controller
1818+1919+import (
2020+ "context"
2121+2222+ . "github.com/onsi/ginkgo/v2"
2323+ . "github.com/onsi/gomega"
2424+ "k8s.io/apimachinery/pkg/api/errors"
2525+ "k8s.io/apimachinery/pkg/types"
2626+ "sigs.k8s.io/controller-runtime/pkg/reconcile"
2727+2828+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2929+3030+ appsv1 "github.com/evanjarrett/secret-service-operator/api/v1"
3131+)
3232+3333+var _ = Describe("SecretService Controller", func() {
3434+ Context("When reconciling a resource", func() {
3535+ const resourceName = "test-resource"
3636+3737+ ctx := context.Background()
3838+3939+ typeNamespacedName := types.NamespacedName{
4040+ Name: resourceName,
4141+ Namespace: "default", // TODO(user):Modify as needed
4242+ }
4343+ secretservice := &appsv1.SecretService{}
4444+4545+ BeforeEach(func() {
4646+ By("creating the custom resource for the Kind SecretService")
4747+ err := k8sClient.Get(ctx, typeNamespacedName, secretservice)
4848+ if err != nil && errors.IsNotFound(err) {
4949+ resource := &appsv1.SecretService{
5050+ ObjectMeta: metav1.ObjectMeta{
5151+ Name: resourceName,
5252+ Namespace: "default",
5353+ },
5454+ // TODO(user): Specify other spec details if needed.
5555+ }
5656+ Expect(k8sClient.Create(ctx, resource)).To(Succeed())
5757+ }
5858+ })
5959+6060+ AfterEach(func() {
6161+ // TODO(user): Cleanup logic after each test, like removing the resource instance.
6262+ resource := &appsv1.SecretService{}
6363+ err := k8sClient.Get(ctx, typeNamespacedName, resource)
6464+ Expect(err).NotTo(HaveOccurred())
6565+6666+ By("Cleanup the specific resource instance SecretService")
6767+ Expect(k8sClient.Delete(ctx, resource)).To(Succeed())
6868+ })
6969+ It("should successfully reconcile the resource", func() {
7070+ By("Reconciling the created resource")
7171+ controllerReconciler := &SecretServiceReconciler{
7272+ Client: k8sClient,
7373+ Scheme: k8sClient.Scheme(),
7474+ }
7575+7676+ _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{
7777+ NamespacedName: typeNamespacedName,
7878+ })
7979+ Expect(err).NotTo(HaveOccurred())
8080+ // TODO(user): Add more specific assertions depending on your controller's reconciliation logic.
8181+ // Example: If you expect a certain status condition after reconciliation, verify it here.
8282+ })
8383+ })
8484+})
+90
internal/controller/suite_test.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package controller
1818+1919+import (
2020+ "fmt"
2121+ "path/filepath"
2222+ "runtime"
2323+ "testing"
2424+2525+ . "github.com/onsi/ginkgo/v2"
2626+ . "github.com/onsi/gomega"
2727+2828+ "k8s.io/client-go/kubernetes/scheme"
2929+ "k8s.io/client-go/rest"
3030+ "sigs.k8s.io/controller-runtime/pkg/client"
3131+ "sigs.k8s.io/controller-runtime/pkg/envtest"
3232+ logf "sigs.k8s.io/controller-runtime/pkg/log"
3333+ "sigs.k8s.io/controller-runtime/pkg/log/zap"
3434+3535+ appsv1 "github.com/evanjarrett/secret-service-operator/api/v1"
3636+ //+kubebuilder:scaffold:imports
3737+)
3838+3939+// These tests use Ginkgo (BDD-style Go testing framework). Refer to
4040+// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
4141+4242+var cfg *rest.Config
4343+var k8sClient client.Client
4444+var testEnv *envtest.Environment
4545+4646+func TestControllers(t *testing.T) {
4747+ RegisterFailHandler(Fail)
4848+4949+ RunSpecs(t, "Controller Suite")
5050+}
5151+5252+var _ = BeforeSuite(func() {
5353+ logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true)))
5454+5555+ By("bootstrapping test environment")
5656+ testEnv = &envtest.Environment{
5757+ CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")},
5858+ ErrorIfCRDPathMissing: true,
5959+6060+ // The BinaryAssetsDirectory is only required if you want to run the tests directly
6161+ // without call the makefile target test. If not informed it will look for the
6262+ // default path defined in controller-runtime which is /usr/local/kubebuilder/.
6363+ // Note that you must have the required binaries setup under the bin directory to perform
6464+ // the tests directly. When we run make test it will be setup and used automatically.
6565+ BinaryAssetsDirectory: filepath.Join("..", "..", "bin", "k8s",
6666+ fmt.Sprintf("1.29.0-%s-%s", runtime.GOOS, runtime.GOARCH)),
6767+ }
6868+6969+ var err error
7070+ // cfg is defined in this file globally.
7171+ cfg, err = testEnv.Start()
7272+ Expect(err).NotTo(HaveOccurred())
7373+ Expect(cfg).NotTo(BeNil())
7474+7575+ err = appsv1.AddToScheme(scheme.Scheme)
7676+ Expect(err).NotTo(HaveOccurred())
7777+7878+ //+kubebuilder:scaffold:scheme
7979+8080+ k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
8181+ Expect(err).NotTo(HaveOccurred())
8282+ Expect(k8sClient).NotTo(BeNil())
8383+8484+})
8585+8686+var _ = AfterSuite(func() {
8787+ By("tearing down the test environment")
8888+ err := testEnv.Stop()
8989+ Expect(err).NotTo(HaveOccurred())
9090+})
+32
test/e2e/e2e_suite_test.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package e2e
1818+1919+import (
2020+ "fmt"
2121+ "testing"
2222+2323+ . "github.com/onsi/ginkgo/v2"
2424+ . "github.com/onsi/gomega"
2525+)
2626+2727+// Run e2e tests using the Ginkgo runner.
2828+func TestE2E(t *testing.T) {
2929+ RegisterFailHandler(Fail)
3030+ fmt.Fprintf(GinkgoWriter, "Starting secret-service-operator suite\n")
3131+ RunSpecs(t, "e2e suite")
3232+}
+122
test/e2e/e2e_test.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package e2e
1818+1919+import (
2020+ "fmt"
2121+ "os/exec"
2222+ "time"
2323+2424+ . "github.com/onsi/ginkgo/v2"
2525+ . "github.com/onsi/gomega"
2626+2727+ "github.com/evanjarrett/secret-service-operator/test/utils"
2828+)
2929+3030+const namespace = "secret-service-operator-system"
3131+3232+var _ = Describe("controller", Ordered, func() {
3333+ BeforeAll(func() {
3434+ By("installing prometheus operator")
3535+ Expect(utils.InstallPrometheusOperator()).To(Succeed())
3636+3737+ By("installing the cert-manager")
3838+ Expect(utils.InstallCertManager()).To(Succeed())
3939+4040+ By("creating manager namespace")
4141+ cmd := exec.Command("kubectl", "create", "ns", namespace)
4242+ _, _ = utils.Run(cmd)
4343+ })
4444+4545+ AfterAll(func() {
4646+ By("uninstalling the Prometheus manager bundle")
4747+ utils.UninstallPrometheusOperator()
4848+4949+ By("uninstalling the cert-manager bundle")
5050+ utils.UninstallCertManager()
5151+5252+ By("removing manager namespace")
5353+ cmd := exec.Command("kubectl", "delete", "ns", namespace)
5454+ _, _ = utils.Run(cmd)
5555+ })
5656+5757+ Context("Operator", func() {
5858+ It("should run successfully", func() {
5959+ var controllerPodName string
6060+ var err error
6161+6262+ // projectimage stores the name of the image used in the example
6363+ var projectimage = "example.com/secret-service-operator:v0.0.1"
6464+6565+ By("building the manager(Operator) image")
6666+ cmd := exec.Command("make", "docker-build", fmt.Sprintf("IMG=%s", projectimage))
6767+ _, err = utils.Run(cmd)
6868+ ExpectWithOffset(1, err).NotTo(HaveOccurred())
6969+7070+ By("loading the the manager(Operator) image on Kind")
7171+ err = utils.LoadImageToKindClusterWithName(projectimage)
7272+ ExpectWithOffset(1, err).NotTo(HaveOccurred())
7373+7474+ By("installing CRDs")
7575+ cmd = exec.Command("make", "install")
7676+ _, err = utils.Run(cmd)
7777+ ExpectWithOffset(1, err).NotTo(HaveOccurred())
7878+7979+ By("deploying the controller-manager")
8080+ cmd = exec.Command("make", "deploy", fmt.Sprintf("IMG=%s", projectimage))
8181+ _, err = utils.Run(cmd)
8282+ ExpectWithOffset(1, err).NotTo(HaveOccurred())
8383+8484+ By("validating that the controller-manager pod is running as expected")
8585+ verifyControllerUp := func() error {
8686+ // Get pod name
8787+8888+ cmd = exec.Command("kubectl", "get",
8989+ "pods", "-l", "control-plane=controller-manager",
9090+ "-o", "go-template={{ range .items }}"+
9191+ "{{ if not .metadata.deletionTimestamp }}"+
9292+ "{{ .metadata.name }}"+
9393+ "{{ \"\\n\" }}{{ end }}{{ end }}",
9494+ "-n", namespace,
9595+ )
9696+9797+ podOutput, err := utils.Run(cmd)
9898+ ExpectWithOffset(2, err).NotTo(HaveOccurred())
9999+ podNames := utils.GetNonEmptyLines(string(podOutput))
100100+ if len(podNames) != 1 {
101101+ return fmt.Errorf("expect 1 controller pods running, but got %d", len(podNames))
102102+ }
103103+ controllerPodName = podNames[0]
104104+ ExpectWithOffset(2, controllerPodName).Should(ContainSubstring("controller-manager"))
105105+106106+ // Validate pod status
107107+ cmd = exec.Command("kubectl", "get",
108108+ "pods", controllerPodName, "-o", "jsonpath={.status.phase}",
109109+ "-n", namespace,
110110+ )
111111+ status, err := utils.Run(cmd)
112112+ ExpectWithOffset(2, err).NotTo(HaveOccurred())
113113+ if string(status) != "Running" {
114114+ return fmt.Errorf("controller pod in %s status", status)
115115+ }
116116+ return nil
117117+ }
118118+ EventuallyWithOffset(1, verifyControllerUp, time.Minute, time.Second).Should(Succeed())
119119+120120+ })
121121+ })
122122+})
+140
test/utils/utils.go
···11+/*
22+Copyright 2024.
33+44+Licensed under the Apache License, Version 2.0 (the "License");
55+you may not use this file except in compliance with the License.
66+You may obtain a copy of the License at
77+88+ http://www.apache.org/licenses/LICENSE-2.0
99+1010+Unless required by applicable law or agreed to in writing, software
1111+distributed under the License is distributed on an "AS IS" BASIS,
1212+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1313+See the License for the specific language governing permissions and
1414+limitations under the License.
1515+*/
1616+1717+package utils
1818+1919+import (
2020+ "fmt"
2121+ "os"
2222+ "os/exec"
2323+ "strings"
2424+2525+ . "github.com/onsi/ginkgo/v2" //nolint:golint,revive
2626+)
2727+2828+const (
2929+ prometheusOperatorVersion = "v0.72.0"
3030+ prometheusOperatorURL = "https://github.com/prometheus-operator/prometheus-operator/" +
3131+ "releases/download/%s/bundle.yaml"
3232+3333+ certmanagerVersion = "v1.14.4"
3434+ certmanagerURLTmpl = "https://github.com/jetstack/cert-manager/releases/download/%s/cert-manager.yaml"
3535+)
3636+3737+func warnError(err error) {
3838+ fmt.Fprintf(GinkgoWriter, "warning: %v\n", err)
3939+}
4040+4141+// InstallPrometheusOperator installs the prometheus Operator to be used to export the enabled metrics.
4242+func InstallPrometheusOperator() error {
4343+ url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
4444+ cmd := exec.Command("kubectl", "create", "-f", url)
4545+ _, err := Run(cmd)
4646+ return err
4747+}
4848+4949+// Run executes the provided command within this context
5050+func Run(cmd *exec.Cmd) ([]byte, error) {
5151+ dir, _ := GetProjectDir()
5252+ cmd.Dir = dir
5353+5454+ if err := os.Chdir(cmd.Dir); err != nil {
5555+ fmt.Fprintf(GinkgoWriter, "chdir dir: %s\n", err)
5656+ }
5757+5858+ cmd.Env = append(os.Environ(), "GO111MODULE=on")
5959+ command := strings.Join(cmd.Args, " ")
6060+ fmt.Fprintf(GinkgoWriter, "running: %s\n", command)
6161+ output, err := cmd.CombinedOutput()
6262+ if err != nil {
6363+ return output, fmt.Errorf("%s failed with error: (%v) %s", command, err, string(output))
6464+ }
6565+6666+ return output, nil
6767+}
6868+6969+// UninstallPrometheusOperator uninstalls the prometheus
7070+func UninstallPrometheusOperator() {
7171+ url := fmt.Sprintf(prometheusOperatorURL, prometheusOperatorVersion)
7272+ cmd := exec.Command("kubectl", "delete", "-f", url)
7373+ if _, err := Run(cmd); err != nil {
7474+ warnError(err)
7575+ }
7676+}
7777+7878+// UninstallCertManager uninstalls the cert manager
7979+func UninstallCertManager() {
8080+ url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
8181+ cmd := exec.Command("kubectl", "delete", "-f", url)
8282+ if _, err := Run(cmd); err != nil {
8383+ warnError(err)
8484+ }
8585+}
8686+8787+// InstallCertManager installs the cert manager bundle.
8888+func InstallCertManager() error {
8989+ url := fmt.Sprintf(certmanagerURLTmpl, certmanagerVersion)
9090+ cmd := exec.Command("kubectl", "apply", "-f", url)
9191+ if _, err := Run(cmd); err != nil {
9292+ return err
9393+ }
9494+ // Wait for cert-manager-webhook to be ready, which can take time if cert-manager
9595+ // was re-installed after uninstalling on a cluster.
9696+ cmd = exec.Command("kubectl", "wait", "deployment.apps/cert-manager-webhook",
9797+ "--for", "condition=Available",
9898+ "--namespace", "cert-manager",
9999+ "--timeout", "5m",
100100+ )
101101+102102+ _, err := Run(cmd)
103103+ return err
104104+}
105105+106106+// LoadImageToKindCluster loads a local docker image to the kind cluster
107107+func LoadImageToKindClusterWithName(name string) error {
108108+ cluster := "kind"
109109+ if v, ok := os.LookupEnv("KIND_CLUSTER"); ok {
110110+ cluster = v
111111+ }
112112+ kindOptions := []string{"load", "docker-image", name, "--name", cluster}
113113+ cmd := exec.Command("kind", kindOptions...)
114114+ _, err := Run(cmd)
115115+ return err
116116+}
117117+118118+// GetNonEmptyLines converts given command output string into individual objects
119119+// according to line breakers, and ignores the empty elements in it.
120120+func GetNonEmptyLines(output string) []string {
121121+ var res []string
122122+ elements := strings.Split(output, "\n")
123123+ for _, element := range elements {
124124+ if element != "" {
125125+ res = append(res, element)
126126+ }
127127+ }
128128+129129+ return res
130130+}
131131+132132+// GetProjectDir will return the directory where the project is
133133+func GetProjectDir() (string, error) {
134134+ wd, err := os.Getwd()
135135+ if err != nil {
136136+ return wd, err
137137+ }
138138+ wd = strings.Replace(wd, "/test/e2e", "", -1)
139139+ return wd, nil
140140+}