diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..7457664 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,16 @@ +--- +name: Bug Report +about: Report a bug +labels: kind/bug + +--- + +**What happened**: + +**What you expected to happen**: + +**How to reproduce it (as minimally and precisely as possible)**: + +**Anything else we need to know**: + +**Environment**: diff --git a/.github/ISSUE_TEMPLATE/enhancement_request.md b/.github/ISSUE_TEMPLATE/enhancement_request.md new file mode 100644 index 0000000..4179e17 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/enhancement_request.md @@ -0,0 +1,10 @@ +--- +name: Enhancement Request +about: Suggest an enhancement +labels: kind/enhancement + +--- + +**What would you like to be added**: + +**Why is this needed**: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..1fe33e5 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,20 @@ +**What this PR does / why we need it**: + +**Which issue(s) this PR fixes**: +Fixes # + +**Special notes for your reviewer**: + +**Release note**: + +```feature user + +``` diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..350feb1 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,41 @@ +name: CI + +on: + push: + tags: + - v* + branches: + - master + - main + pull_request: + +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + + - name: task generate + run: | + task generate --verbose + git diff --exit-code + + - name: task validate + run: task validate --verbose + + - name: task test + run: task test --verbose diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml new file mode 100644 index 0000000..156975b --- /dev/null +++ b/.github/workflows/publish.yaml @@ -0,0 +1,87 @@ +name: Publish + +on: + push: + tags: + - v* + workflow_dispatch: + +permissions: + packages: write + +env: + OCI_URL: ghcr.io/openmcp-project + +jobs: + release_tag: + name: Release version + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.PUSH_KEY }} + fetch-tags: true + fetch-depth: 0 + submodules: recursive + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + + - name: Read and validate VERSION + id: version + run: | + VERSION=$(task version) + if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-dev(-[0-9a-f]*)?)?$ ]]; then + echo "Invalid version format: $VERSION" + exit 1 + fi + echo "New version: $VERSION" + echo "version=$VERSION" >> $GITHUB_ENV + + - name: Skip release if version is a dev version + if: contains(env.version, '-dev') + run: | + echo "Skipping development version release: ${{ env.version }}" + echo "SKIP=true" >> $GITHUB_ENV + exit 0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Context for Buildx + id: buildx-context + run: | + docker context create builders + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Docker Buildx + timeout-minutes: 5 + uses: docker/setup-buildx-action@v3 + with: + version: latest + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build and Push Images + run: | + task build:img:all --verbose + + - name: Package and Push Helm Charts + run: | + task build:helm:all --verbose + + - name: Build and Push OCM Component + run: | + task build:ocm:all --verbose diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..4d99cba --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,144 @@ +name: Versioned Release + +on: + push: + branches: + - main + +permissions: + contents: write # we need this to be able to push tags + pull-requests: read + +jobs: + release_tag: + name: Release version + runs-on: ubuntu-24.04 + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ssh-key: ${{ secrets.PUSH_KEY }} + fetch-tags: true + fetch-depth: 0 + submodules: recursive + + - name: Install Task + uses: arduino/setup-task@v2 + with: + version: 3.x + + - name: Read and validate VERSION + id: version + run: | + VERSION=$(task version) + if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-dev(-[0-9a-f]*)?)?$ ]]; then + echo "Invalid version format: $VERSION" + exit 1 + fi + echo "New version: $VERSION" + echo "version=$VERSION" >> $GITHUB_ENV + + - name: Skip release if version is a dev version + if: contains(env.version, '-dev') + run: | + echo "Skipping development version release: ${{ env.version }}" + echo "SKIP=true" >> $GITHUB_ENV + exit 0 + + - name: Check if VERSION is already tagged + id: check_tag + run: | + if git rev-parse "refs/tags/${{ env.version }}" >/dev/null 2>&1; then + echo "Tag ${{ env.version }} already exists. Skipping release." + echo "SKIP=true" >> $GITHUB_ENV + exit 0 + fi + echo "Tag ${{ env.version }} doesn't exists. Proceeding with release." + + - name: Create Git tag + if: ${{ env.SKIP != 'true' }} + run: | + AUTHOR_NAME=$(git log -1 --pretty=format:'%an') + AUTHOR_EMAIL=$(git log -1 --pretty=format:'%ae') + echo "Tagging as $AUTHOR_NAME <$AUTHOR_EMAIL>" + + echo "AUTHOR_NAME=$AUTHOR_NAME" >> $GITHUB_ENV + echo "AUTHOR_EMAIL=$AUTHOR_EMAIL" >> $GITHUB_ENV + + git config user.name "$AUTHOR_NAME" + git config user.email "$AUTHOR_EMAIL" + + git tag -a "${{ env.version }}" -m "Release ${{ env.version }}" + git push origin "${{ env.version }}" + + - name: Create Git tag for api submodule + if: ${{ env.SKIP != 'true' }} + run: | + AUTHOR_NAME=$(git log -1 --pretty=format:'%an') + AUTHOR_EMAIL=$(git log -1 --pretty=format:'%ae') + echo "Tagging as $AUTHOR_NAME <$AUTHOR_EMAIL>" + + echo "AUTHOR_NAME=$AUTHOR_NAME" >> $GITHUB_ENV + echo "AUTHOR_EMAIL=$AUTHOR_EMAIL" >> $GITHUB_ENV + + git config user.name "$AUTHOR_NAME" + git config user.email "$AUTHOR_EMAIL" + + git tag -a "api/${{ env.version }}" -m "Release ${{ env.version }}" + git push origin "api/${{ env.version }}" + + - name: Build Changelog + id: github_release + uses: mikepenz/release-changelog-builder-action@v5 + with: + mode: "PR" + configurationJson: | + { + "template": "#{{CHANGELOG}}", + "pr_template": "- #{{TITLE}}: ##{{NUMBER}}", + "categories": [ + { + "title": "## Feature", + "labels": ["feat", "feature"] + }, + { + "title": "## Fix", + "labels": ["fix", "bug"] + }, + { + "title": "## Other", + "labels": [] + } + ], + "label_extractor": [ + { + "pattern": "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test){1}(\\([\\w\\-\\.]+\\))?(!)?: ([\\w ])+([\\s\\S]*)", + "on_property": "title", + "target": "$1" + } + ] + } + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create GitHub release + if: ${{ env.SKIP != 'true' }} + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ env.version }} + name: Release ${{ env.version }} + body: ${{steps.github_release.outputs.changelog}} + draft: true + prerelease: false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Push dev VERSION + if: ${{ env.SKIP != 'true' }} + run: | + task release:set-version --verbose -- "${{ env.version }}-dev" + git config user.name "${{ env.AUTHOR_NAME }}" + git config user.email "${{ env.AUTHOR_EMAIL }}" + git add VERSION + git commit -m "Update VERSION to ${{ env.version }}-dev" + git push origin main diff --git a/.github/workflows/reuse.yaml b/.github/workflows/reuse.yaml new file mode 100644 index 0000000..328ee5b --- /dev/null +++ b/.github/workflows/reuse.yaml @@ -0,0 +1,11 @@ +name: REUSE Compliance Check + +on: [push, pull_request] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: REUSE Compliance Check + uses: fsfe/reuse-action@v5 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..adfe341 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "hack/common"] + path = hack/common + url = https://github.com/openmcp-project/build.git diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..3c7f6c5 --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,183 @@ +version: "2" + +linters: + enable: + - staticcheck + - unused + - govet + - gocyclo + - gocritic + - goconst + - prealloc + - revive + - unconvert + - misspell + - nakedret + disable: + - nilnesserr + settings: + errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: false + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: false + + # [deprecated] comma-separated list of pairs of the form pkg:regex + # the regex is used to ignore names within pkg. (default "fmt:.*"). + # see https://github.com/kisielk/errcheck#the-deprecated-method for details + exclude-functions: + - fmt:.* + - io/ioutil:^Read.* + + govet: + # report about shadowed variables + disable: + - shadow + gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 10 + + dupl: + # tokens count to trigger issue, 150 by default + threshold: 100 + + goconst: + # minimal length of string constant, 3 by default + min-len: 3 + # minimal occurrences count to trigger, 3 by default + min-occurrences: 5 + + lll: + # Max line length, lines longer will be reported. + # '\t' is counted as 1 character by default, and can be changed with the tab-width option. + # Default: 120. + line-length: 120 + # Tab width in spaces. + # Default: 1 + tab-width: 1 + + unused: + # treat code as a program (not a library) and report unused exported identifiers; default is false. + # XXX: if you enable this setting, unused will report a lot of false-positives in text editors: + # if it's called for subdir of a project it can't find funcs usages. All text editor integrations + # with golangci-lint call it on a directory with the changed file. + exported-fields-are-used: false + + unparam: + # Inspect exported functions, default is false. Set to true if no external program/library imports your code. + # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: + # if it's called for subdir of a project it can't find external interfaces. All text editor integrations + # with golangci-lint call it on a directory with the changed file. + check-exported: false + + nakedret: + # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 + max-func-lines: 30 + + prealloc: + # XXX: we don't recommend using this linter before doing performance profiling. + # For most programs usage of prealloc will be a premature optimization. + + # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. + # True by default. + simple: true + range-loops: true # Report preallocation suggestions on range loops, true by default + for-loops: false # Report preallocation suggestions on for loops, false by default + + gocritic: + # Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint` run to see all tags and checks. + # Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". + enabled-tags: + - performance + + settings: # settings passed to gocritic + captLocal: # must be valid enabled check name + paramsOnly: true + rangeValCopy: + sizeThreshold: 32 + + exclusions: + generated: strict + rules: + # Exclude some linters from running on tests files. + - path: _test(ing)?\.go + linters: + - gocyclo + - errcheck + - dupl + - gosec + - scopelint + - unparam + - revive + + # Ease some gocritic warnings on test files. + - path: _test\.go + text: "(unnamedResult|exitAfterDefer)" + linters: + - gocritic + + # These are performance optimisations rather than style issues per se. + # They warn when function arguments or range values copy a lot of memory + # rather than using a pointer. + - text: "(hugeParam|rangeValCopy):" + linters: + - gocritic + + # This "TestMain should call os.Exit to set exit code" warning is not clever + # enough to notice that we call a helper method that calls os.Exit. + - text: "SA3000:" + linters: + - staticcheck + + - text: "k8s.io/api/core/v1" + linters: + - goimports + + # This is a "potential hardcoded credentials" warning. It's triggered by + # any variable with 'secret' in the same, and thus hits a lot of false + # positives in Kubernetes land where a Secret is an object type. + - text: "G101:" + linters: + - gosec + - gas + + # This is an 'errors unhandled' warning that duplicates errcheck. + - text: "G104:" + linters: + - gosec + - gas + # ease package comments rule + - text: "package-comments:" + linters: + - revive + +issues: + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. + new: false + + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-issues-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same-issues: 0 + +formatters: + enable: + - gofmt + - goimports + settings: + + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + +run: + timeout: 10m \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml deleted file mode 100644 index 6b29746..0000000 --- a/.golangci.yml +++ /dev/null @@ -1,47 +0,0 @@ -run: - timeout: 5m - allow-parallel-runners: true - -issues: - # don't skip warning about doc comments - # don't exclude the default set of lint - exclude-use-default: false - # restore some of the defaults - # (fill in the rest as needed) - exclude-rules: - - path: "api/*" - linters: - - lll - - path: "internal/*" - linters: - - dupl - - lll -linters: - disable-all: true - enable: - - dupl - - errcheck - - copyloopvar - - ginkgolinter - - goconst - - gocyclo - - gofmt - - goimports - - gosimple - - govet - - ineffassign - - lll - - misspell - - nakedret - - prealloc - - revive - - staticcheck - - typecheck - - unconvert - - unparam - - unused - -linters-settings: - revive: - rules: - - name: comment-spacings diff --git a/Makefile b/Makefile deleted file mode 100644 index 8967981..0000000 --- a/Makefile +++ /dev/null @@ -1,224 +0,0 @@ -# Image URL to use all building/pushing image targets -IMG ?= controller:latest - -# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) -ifeq (,$(shell go env GOBIN)) -GOBIN=$(shell go env GOPATH)/bin -else -GOBIN=$(shell go env GOBIN) -endif - -# CONTAINER_TOOL defines the container tool to be used for building images. -# Be aware that the target commands are only tested with Docker which is -# scaffolded by default. However, you might want to replace it to use other -# tools. (i.e. podman) -CONTAINER_TOOL ?= docker - -# Setting SHELL to bash allows bash commands to be executed by recipes. -# Options are set to exit when a recipe line exits non-zero or a piped command fails. -SHELL = /usr/bin/env bash -o pipefail -.SHELLFLAGS = -ec - -.PHONY: all -all: build - -##@ General - -# The help target prints out all targets with their descriptions organized -# beneath their categories. The categories are represented by '##@' and the -# target descriptions by '##'. The awk command is responsible for reading the -# entire set of makefiles included in this invocation, looking for lines of the -# file as xyz: ## something, and then pretty-format the target and help. Then, -# if there's a line with ##@ something, that gets pretty-printed as a category. -# More info on the usage of ANSI control characters for terminal formatting: -# https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_parameters -# More info on the awk command: -# http://linuxcommand.org/lc3_adv_awk.php - -.PHONY: help -help: ## Display this help. - @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) - -##@ Development - -.PHONY: manifests -manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and CustomResourceDefinition objects. - $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases - -.PHONY: generate -generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. - $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." - -.PHONY: fmt -fmt: ## Run go fmt against code. - go fmt ./... - -.PHONY: vet -vet: ## Run go vet against code. - go vet ./... - -.PHONY: test -test: manifests generate fmt vet setup-envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out - -# TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'. -# The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally. -# CertManager is installed by default; skip with: -# - CERT_MANAGER_INSTALL_SKIP=true -.PHONY: test-e2e -test-e2e: manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. - @command -v kind >/dev/null 2>&1 || { \ - echo "Kind is not installed. Please install Kind manually."; \ - exit 1; \ - } - @kind get clusters | grep -q 'kind' || { \ - echo "No Kind cluster is running. Please start a Kind cluster before running the e2e tests."; \ - exit 1; \ - } - go test ./test/e2e/ -v -ginkgo.v - -.PHONY: lint -lint: golangci-lint ## Run golangci-lint linter - $(GOLANGCI_LINT) run - -.PHONY: lint-fix -lint-fix: golangci-lint ## Run golangci-lint linter and perform fixes - $(GOLANGCI_LINT) run --fix - -.PHONY: lint-config -lint-config: golangci-lint ## Verify golangci-lint linter configuration - $(GOLANGCI_LINT) config verify - -##@ Build - -.PHONY: build -build: manifests generate fmt vet ## Build manager binary. - go build -o bin/manager cmd/main.go - -.PHONY: run -run: manifests generate fmt vet ## Run a controller from your host. - go run ./cmd/main.go - -# If you wish to build the manager image targeting other platforms you can use the --platform flag. -# (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it. -# More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -.PHONY: docker-build -docker-build: ## Build docker image with the manager. - $(CONTAINER_TOOL) build -t ${IMG} . - -.PHONY: docker-push -docker-push: ## Push docker image with the manager. - $(CONTAINER_TOOL) push ${IMG} - -# PLATFORMS defines the target platforms for the manager image be built to provide support to multiple -# architectures. (i.e. make docker-buildx IMG=myregistry/mypoperator:0.0.1). To use this option you need to: -# - be able to use docker buildx. More info: https://docs.docker.com/build/buildx/ -# - have enabled BuildKit. More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -# - be able to push the image to your registry (i.e. if you do not set a valid value via IMG=> then the export will fail) -# To adequately provide solutions that are compatible with multiple platforms, you should consider using this option. -PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le -.PHONY: docker-buildx -docker-buildx: ## Build and push docker image for the manager for cross-platform support - # copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile - sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross - - $(CONTAINER_TOOL) buildx create --name cluster-provider-kind-builder - $(CONTAINER_TOOL) buildx use cluster-provider-kind-builder - - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - $(CONTAINER_TOOL) buildx rm cluster-provider-kind-builder - rm Dockerfile.cross - -.PHONY: build-installer -build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment. - mkdir -p dist - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default > dist/install.yaml - -##@ Deployment - -ifndef ignore-not-found - ignore-not-found = false -endif - -.PHONY: install -install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f - - -.PHONY: uninstall -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. - $(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - - -.PHONY: deploy -deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config. - cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG} - $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - - -.PHONY: undeploy -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. - $(KUSTOMIZE) build config/default | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f - - -##@ Dependencies - -## Location to install dependencies to -LOCALBIN ?= $(shell pwd)/bin -$(LOCALBIN): - mkdir -p $(LOCALBIN) - -## Tool Binaries -KUBECTL ?= kubectl -KUSTOMIZE ?= $(LOCALBIN)/kustomize -CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen -ENVTEST ?= $(LOCALBIN)/setup-envtest -GOLANGCI_LINT = $(LOCALBIN)/golangci-lint - -## Tool Versions -KUSTOMIZE_VERSION ?= v5.5.0 -CONTROLLER_TOOLS_VERSION ?= v0.17.2 -#ENVTEST_VERSION is the version of controller-runtime release branch to fetch the envtest setup script (i.e. release-0.20) -ENVTEST_VERSION ?= $(shell go list -m -f "{{ .Version }}" sigs.k8s.io/controller-runtime | awk -F'[v.]' '{printf "release-%d.%d", $$2, $$3}') -#ENVTEST_K8S_VERSION is the version of Kubernetes to use for setting up ENVTEST binaries (i.e. 1.31) -ENVTEST_K8S_VERSION ?= $(shell go list -m -f "{{ .Version }}" k8s.io/api | awk -F'[v.]' '{printf "1.%d", $$3}') -GOLANGCI_LINT_VERSION ?= v1.63.4 - -.PHONY: kustomize -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. -$(KUSTOMIZE): $(LOCALBIN) - $(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION)) - -.PHONY: controller-gen -controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. -$(CONTROLLER_GEN): $(LOCALBIN) - $(call go-install-tool,$(CONTROLLER_GEN),sigs.k8s.io/controller-tools/cmd/controller-gen,$(CONTROLLER_TOOLS_VERSION)) - -.PHONY: setup-envtest -setup-envtest: envtest ## Download the binaries required for ENVTEST in the local bin directory. - @echo "Setting up envtest binaries for Kubernetes version $(ENVTEST_K8S_VERSION)..." - @$(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path || { \ - echo "Error: Failed to set up envtest binaries for version $(ENVTEST_K8S_VERSION)."; \ - exit 1; \ - } - -.PHONY: envtest -envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. -$(ENVTEST): $(LOCALBIN) - $(call go-install-tool,$(ENVTEST),sigs.k8s.io/controller-runtime/tools/setup-envtest,$(ENVTEST_VERSION)) - -.PHONY: golangci-lint -golangci-lint: $(GOLANGCI_LINT) ## Download golangci-lint locally if necessary. -$(GOLANGCI_LINT): $(LOCALBIN) - $(call go-install-tool,$(GOLANGCI_LINT),github.com/golangci/golangci-lint/cmd/golangci-lint,$(GOLANGCI_LINT_VERSION)) - -# go-install-tool will 'go install' any package with custom target and name of binary, if it doesn't exist -# $1 - target path with name of binary -# $2 - package url which can be installed -# $3 - specific version of package -define go-install-tool -@[ -f "$(1)-$(3)" ] || { \ -set -e; \ -package=$(2)@$(3) ;\ -echo "Downloading $${package}" ;\ -rm -f $(1) || true ;\ -GOBIN=$(LOCALBIN) go install $${package} ;\ -mv $(1) $(1)-$(3) ;\ -} ;\ -ln -sf $(1)-$(3) $(1) -endef diff --git a/Makefile b/Makefile new file mode 120000 index 0000000..2b98345 --- /dev/null +++ b/Makefile @@ -0,0 +1 @@ +hack/common/Makefile \ No newline at end of file diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 0000000..522e007 --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,15 @@ +version: 3 + +vars: + NESTED_MODULES: api + API_DIRS: '{{.ROOT_DIR}}/api/v1alpha1/...' + MANIFEST_OUT: '{{.ROOT_DIR}}/config/crd/bases' + CODE_DIRS: '{{.ROOT_DIR}}/cmd/... {{.ROOT_DIR}}/internal/... {{.ROOT_DIR}}/api/v1alpha1/...' + COMPONENTS: 'cluster-provider-kind' + REPO_NAME: 'https://github.com/openmcp-project/cluster-provider-kind' + GENERATE_DOCS_INDEX: "false" + +includes: + shared: + taskfile: hack/common/Taskfile_controller.yaml + flatten: true \ No newline at end of file diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..8a9ecc2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.1 \ No newline at end of file diff --git a/api/v1alpha1/accessrequest_types.go b/api/v1alpha1/accessrequest_types.go index 74a3d4d..9b4dff1 100644 --- a/api/v1alpha1/accessrequest_types.go +++ b/api/v1alpha1/accessrequest_types.go @@ -33,12 +33,14 @@ type AccessRequestStatus struct { Kubeconfig *Kubeconfig `json:"kubeconfig,omitempty"` } +// Kubeconfig contains the information needed to access a cluster. type Kubeconfig struct { SecretRef corev1.SecretReference `json:"secretRef,omitempty"` ExpiresAt metav1.Time `json:"expiresAt,omitempty"` ServiceAccount ServiceAccountRef `json:"serviceAccount,omitempty"` } +// ServiceAccountRef contains the information needed to access a service account. type ServiceAccountRef struct { Name string `json:"name"` Namespace string `json:"namespace"` diff --git a/api/v1alpha1/cluster_types.go b/api/v1alpha1/cluster_types.go index ee28cb2..21ff81b 100644 --- a/api/v1alpha1/cluster_types.go +++ b/api/v1alpha1/cluster_types.go @@ -33,8 +33,10 @@ const ( Unknown Phase = "Unknown" ) +// ClusterConditionType is a custom type representing the condition type of a cluster. type ClusterConditionType string +// Constants representing the conditions of a cluster. const ( ClusterReady ClusterConditionType = "Ready" KindReady ClusterConditionType = "KindReady" diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c34b211..1a9214a 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1,26 +1,12 @@ //go:build !ignore_autogenerated -/* -Copyright 2025. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - // Code generated by controller-gen. DO NOT EDIT. package v1alpha1 import ( + v1 "k8s.io/api/rbac/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -29,8 +15,8 @@ func (in *AccessRequest) DeepCopyInto(out *AccessRequest) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRequest. @@ -86,6 +72,14 @@ func (in *AccessRequestList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AccessRequestSpec) DeepCopyInto(out *AccessRequestSpec) { *out = *in + out.ClusterRef = in.ClusterRef + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]v1.PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRequestSpec. @@ -101,6 +95,11 @@ func (in *AccessRequestSpec) DeepCopy() *AccessRequestSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *AccessRequestStatus) DeepCopyInto(out *AccessRequestStatus) { *out = *in + if in.Kubeconfig != nil { + in, out := &in.Kubeconfig, &out.Kubeconfig + *out = new(Kubeconfig) + (*in).DeepCopyInto(*out) + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AccessRequestStatus. @@ -119,7 +118,7 @@ func (in *Cluster) DeepCopyInto(out *Cluster) { out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) out.Spec = in.Spec - out.Status = in.Status + in.Status.DeepCopyInto(&out.Status) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Cluster. @@ -190,6 +189,13 @@ func (in *ClusterSpec) DeepCopy() *ClusterSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) { *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]metav1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ClusterStatus. @@ -201,3 +207,36 @@ func (in *ClusterStatus) DeepCopy() *ClusterStatus { in.DeepCopyInto(out) return out } + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Kubeconfig) DeepCopyInto(out *Kubeconfig) { + *out = *in + out.SecretRef = in.SecretRef + in.ExpiresAt.DeepCopyInto(&out.ExpiresAt) + out.ServiceAccount = in.ServiceAccount +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Kubeconfig. +func (in *Kubeconfig) DeepCopy() *Kubeconfig { + if in == nil { + return nil + } + out := new(Kubeconfig) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ServiceAccountRef) DeepCopyInto(out *ServiceAccountRef) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServiceAccountRef. +func (in *ServiceAccountRef) DeepCopy() *ServiceAccountRef { + if in == nil { + return nil + } + out := new(ServiceAccountRef) + in.DeepCopyInto(out) + return out +} diff --git a/cmd/main.go b/cmd/cluster-provider-kind/main.go similarity index 100% rename from cmd/main.go rename to cmd/cluster-provider-kind/main.go diff --git a/config/crd/bases/kind.clusters.openmcp.cloud_accessrequests.yaml b/config/crd/bases/kind.clusters.openmcp.cloud_accessrequests.yaml index 2e07351..b5b5a31 100644 --- a/config/crd/bases/kind.clusters.openmcp.cloud_accessrequests.yaml +++ b/config/crd/bases/kind.clusters.openmcp.cloud_accessrequests.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.17.3 name: accessrequests.kind.clusters.openmcp.cloud spec: group: kind.clusters.openmcp.cloud @@ -111,6 +111,8 @@ spec: description: AccessRequestStatus defines the observed state of AccessRequest. properties: kubeconfig: + description: Kubeconfig contains the information needed to access + a cluster. properties: expiresAt: format: date-time @@ -131,6 +133,8 @@ spec: type: object x-kubernetes-map-type: atomic serviceAccount: + description: ServiceAccountRef contains the information needed + to access a service account. properties: name: type: string diff --git a/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml b/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml index 80fdcb8..29a8f57 100644 --- a/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml +++ b/config/crd/bases/kind.clusters.openmcp.cloud_clusters.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.17.2 + controller-gen.kubebuilder.io/version: v0.17.3 name: clusters.kind.clusters.openmcp.cloud spec: group: kind.clusters.openmcp.cloud diff --git a/go.mod b/go.mod index 8cc11a4..df9d430 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( sigs.k8s.io/controller-runtime v0.20.2 sigs.k8s.io/kind v0.27.0 sigs.k8s.io/kustomize/api v0.19.0 + sigs.k8s.io/kustomize/kyaml v0.19.0 ) require ( @@ -108,7 +109,6 @@ require ( k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.0 // indirect sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect sigs.k8s.io/yaml v1.4.0 // indirect diff --git a/hack/common b/hack/common new file mode 160000 index 0000000..80bfca5 --- /dev/null +++ b/hack/common @@ -0,0 +1 @@ +Subproject commit 80bfca5ddf3186e97df6b7d1acdea035f6ba4632 diff --git a/internal/controller/accessrequest_controller_test.go b/internal/controller/accessrequest_controller_test.go deleted file mode 100644 index dbf756a..0000000 --- a/internal/controller/accessrequest_controller_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2025. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controller - -import ( - "context" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - kindclustersopenmcpcloudv1alpha1 "github.com/openmcp-project/cluster-provider-kind/api/v1alpha1" -) - -var _ = Describe("AccessRequest Controller", func() { - Context("When reconciling a resource", func() { - const resourceName = "test-resource" - - ctx := context.Background() - - typeNamespacedName := types.NamespacedName{ - Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed - } - accessrequest := &kindclustersopenmcpcloudv1alpha1.AccessRequest{} - - BeforeEach(func() { - By("creating the custom resource for the Kind AccessRequest") - err := k8sClient.Get(ctx, typeNamespacedName, accessrequest) - if err != nil && errors.IsNotFound(err) { - resource := &kindclustersopenmcpcloudv1alpha1.AccessRequest{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", - }, - // TODO(user): Specify other spec details if needed. - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } - }) - - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &kindclustersopenmcpcloudv1alpha1.AccessRequest{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - By("Cleanup the specific resource instance AccessRequest") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &AccessRequestReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - } - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. - }) - }) -}) diff --git a/internal/controller/cluster_controller.go b/internal/controller/cluster_controller.go index ace4652..08cb4e8 100644 --- a/internal/controller/cluster_controller.go +++ b/internal/controller/cluster_controller.go @@ -37,6 +37,7 @@ import ( ) var ( + // Finalizer is the finalizer for Cluster Finalizer = v1alpha1.GroupVersion.Group + "/finalizer" ) @@ -68,7 +69,8 @@ func (r *ClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct } // Always try to update the status - defer r.Status().Update(ctx, cluster) + // FIXME: error handling at the end? + defer r.Status().Update(ctx, cluster) //nolint:errcheck ctx = smartrequeue.NewContext(ctx, r.RequeueStore.For(cluster)) @@ -109,6 +111,7 @@ func (r *ClusterReconciler) handleDelete(ctx context.Context, cluster *v1alpha1. return requeue.Progressing() } +//nolint:gocyclo func (r *ClusterReconciler) handleCreateOrUpdate(ctx context.Context, cluster *v1alpha1.Cluster) (ctrl.Result, error) { requeue := smartrequeue.FromContext(ctx) cluster.Status.Phase = v1alpha1.Progressing diff --git a/internal/controller/cluster_controller_test.go b/internal/controller/cluster_controller_test.go deleted file mode 100644 index dbf74ca..0000000 --- a/internal/controller/cluster_controller_test.go +++ /dev/null @@ -1,84 +0,0 @@ -/* -Copyright 2025. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controller - -import ( - "context" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - kindclustersopenmcpcloudv1alpha1 "github.com/openmcp-project/cluster-provider-kind/api/v1alpha1" -) - -var _ = Describe("Cluster Controller", func() { - Context("When reconciling a resource", func() { - const resourceName = "test-resource" - - ctx := context.Background() - - typeNamespacedName := types.NamespacedName{ - Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed - } - cluster := &kindclustersopenmcpcloudv1alpha1.Cluster{} - - BeforeEach(func() { - By("creating the custom resource for the Kind Cluster") - err := k8sClient.Get(ctx, typeNamespacedName, cluster) - if err != nil && errors.IsNotFound(err) { - resource := &kindclustersopenmcpcloudv1alpha1.Cluster{ - ObjectMeta: metav1.ObjectMeta{ - Name: resourceName, - Namespace: "default", - }, - // TODO(user): Specify other spec details if needed. - } - Expect(k8sClient.Create(ctx, resource)).To(Succeed()) - } - }) - - AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. - resource := &kindclustersopenmcpcloudv1alpha1.Cluster{} - err := k8sClient.Get(ctx, typeNamespacedName, resource) - Expect(err).NotTo(HaveOccurred()) - - By("Cleanup the specific resource instance Cluster") - Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) - }) - It("should successfully reconcile the resource", func() { - By("Reconciling the created resource") - controllerReconciler := &ClusterReconciler{ - Client: k8sClient, - Scheme: k8sClient.Scheme(), - } - - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. - }) - }) -}) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go deleted file mode 100644 index bea36fc..0000000 --- a/internal/controller/suite_test.go +++ /dev/null @@ -1,116 +0,0 @@ -/* -Copyright 2025. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package controller - -import ( - "context" - "os" - "path/filepath" - "testing" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/envtest" - logf "sigs.k8s.io/controller-runtime/pkg/log" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - - kindclustersopenmcpcloudv1alpha1 "github.com/openmcp-project/cluster-provider-kind/api/v1alpha1" - // +kubebuilder:scaffold:imports -) - -// These tests use Ginkgo (BDD-style Go testing framework). Refer to -// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. - -var ( - ctx context.Context - cancel context.CancelFunc - testEnv *envtest.Environment - cfg *rest.Config - k8sClient client.Client -) - -func TestControllers(t *testing.T) { - RegisterFailHandler(Fail) - - RunSpecs(t, "Controller Suite") -} - -var _ = BeforeSuite(func() { - logf.SetLogger(zap.New(zap.WriteTo(GinkgoWriter), zap.UseDevMode(true))) - - ctx, cancel = context.WithCancel(context.TODO()) - - var err error - err = kindclustersopenmcpcloudv1alpha1.AddToScheme(scheme.Scheme) - Expect(err).NotTo(HaveOccurred()) - - // +kubebuilder:scaffold:scheme - - By("bootstrapping test environment") - testEnv = &envtest.Environment{ - CRDDirectoryPaths: []string{filepath.Join("..", "..", "config", "crd", "bases")}, - ErrorIfCRDPathMissing: true, - } - - // Retrieve the first found binary directory to allow running tests from IDEs - if getFirstFoundEnvTestBinaryDir() != "" { - testEnv.BinaryAssetsDirectory = getFirstFoundEnvTestBinaryDir() - } - - // cfg is defined in this file globally. - cfg, err = testEnv.Start() - Expect(err).NotTo(HaveOccurred()) - Expect(cfg).NotTo(BeNil()) - - k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) - Expect(err).NotTo(HaveOccurred()) - Expect(k8sClient).NotTo(BeNil()) -}) - -var _ = AfterSuite(func() { - By("tearing down the test environment") - cancel() - err := testEnv.Stop() - Expect(err).NotTo(HaveOccurred()) -}) - -// getFirstFoundEnvTestBinaryDir locates the first binary in the specified path. -// ENVTEST-based tests depend on specific binaries, usually located in paths set by -// controller-runtime. When running tests directly (e.g., via an IDE) without using -// Makefile targets, the 'BinaryAssetsDirectory' must be explicitly configured. -// -// This function streamlines the process by finding the required binaries, similar to -// setting the 'KUBEBUILDER_ASSETS' environment variable. To ensure the binaries are -// properly set up, run 'make setup-envtest' beforehand. -func getFirstFoundEnvTestBinaryDir() string { - basePath := filepath.Join("..", "..", "bin", "k8s") - entries, err := os.ReadDir(basePath) - if err != nil { - logf.Log.Error(err, "Failed to read directory", "path", basePath) - return "" - } - for _, entry := range entries { - if entry.IsDir() { - return filepath.Join(basePath, entry.Name()) - } - } - return "" -} diff --git a/test/utils/utils.go b/test/utils/utils.go index 04a5141..ea7c494 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -24,7 +24,7 @@ import ( "os/exec" "strings" - . "github.com/onsi/ginkgo/v2" //nolint:golint,revive + . "github.com/onsi/ginkgo/v2" //nolint:golint,revive,staticcheck ) const ( @@ -197,7 +197,7 @@ func GetProjectDir() (string, error) { if err != nil { return wd, err } - wd = strings.Replace(wd, "/test/e2e", "", -1) + wd = strings.ReplaceAll(wd, "/test/e2e", "") return wd, nil }