diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..30912a8 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,24 @@ +{ + "name": "Kubebuilder DevContainer", + "image": "docker.io/golang:1.24", + "features": { + "ghcr.io/devcontainers/features/docker-in-docker:2": {}, + "ghcr.io/devcontainers/features/git:1": {} + }, + + "runArgs": ["--network=host"], + + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.shell.linux": "/bin/bash" + }, + "extensions": [ + "ms-kubernetes-tools.vscode-kubernetes-tools", + "ms-azuretools.vscode-docker" + ] + } + }, + + "onCreateCommand": "bash .devcontainer/post-install.sh" +} diff --git a/.devcontainer/post-install.sh b/.devcontainer/post-install.sh new file mode 100644 index 0000000..9f94d54 --- /dev/null +++ b/.devcontainer/post-install.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -x + +curl -Lo ./kind https://kind.sigs.k8s.io/dl/latest/kind-linux-amd64 +chmod +x ./kind +mv ./kind /usr/local/bin/kind + +curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/linux/amd64 +chmod +x kubebuilder +mv kubebuilder /usr/local/bin/ + +KUBECTL_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt) +curl -LO "https://dl.k8s.io/release/$KUBECTL_VERSION/bin/linux/amd64/kubectl" +chmod +x kubectl +mv kubectl /usr/local/bin/kubectl + +docker network create -d=bridge --subnet=172.19.0.0/24 kind + +kind version +kubebuilder version +docker --version +go version +kubectl version --client \ No newline at end of file diff --git a/.github/workflows/ci-go.yaml b/.github/workflows/ci-go.yaml index 63cdc9f..783204e 100644 --- a/.github/workflows/ci-go.yaml +++ b/.github/workflows/ci-go.yaml @@ -21,7 +21,9 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: "1.23.x" + go-version-file: go.mod + - name: Run linter + uses: golangci/golangci-lint-action@v8 - name: Install dependencies run: go mod download - name: Test with Go diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..e5b21b0 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,52 @@ +version: "2" +run: + allow-parallel-runners: true +linters: + default: none + enable: + - copyloopvar + - dupl + - errcheck + - ginkgolinter + - goconst + - gocyclo + - govet + - ineffassign + - lll + - misspell + - nakedret + - prealloc + - revive + - staticcheck + - unconvert + - unparam + - unused + settings: + revive: + rules: + - name: comment-spacings + - name: import-shadowing + exclusions: + generated: lax + rules: + - linters: + - lll + path: api/* + - linters: + - dupl + - lll + path: internal/* + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Dockerfile b/Dockerfile index 7f91fc3..a8fab81 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build the manager binary -FROM golang:1.23 as builder +FROM golang:1.25 as builder ARG TARGETOS ARG TARGETARCH diff --git a/Makefile b/Makefile index 2df8fa1..2d2dc46 100644 --- a/Makefile +++ b/Makefile @@ -48,12 +48,9 @@ endif # Set the Operator SDK version to use. By default, what is installed on the system is used. # This is useful for CI or a project to utilize a specific version of the operator-sdk toolkit. -OPERATOR_SDK_VERSION ?= v1.39.1 - +OPERATOR_SDK_VERSION ?= v1.42.0 # Image URL to use all building/pushing image targets IMG ?= controller:latest -# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary. -ENVTEST_K8S_VERSION = 1.32.0 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -62,6 +59,12 @@ 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 @@ -74,7 +77,7 @@ all: build # 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 commands is responsible for reading 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. @@ -106,8 +109,49 @@ vet: ## Run go vet against code. go vet ./... .PHONY: test -test: manifests generate fmt vet envtest ## Run tests. - KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out +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 +KIND_CLUSTER ?= example-operator-test-e2e + +.PHONY: setup-test-e2e +setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist + @command -v $(KIND) >/dev/null 2>&1 || { \ + echo "Kind is not installed. Please install Kind manually."; \ + exit 1; \ + } + @case "$$($(KIND) get clusters)" in \ + *"$(KIND_CLUSTER)"*) \ + echo "Kind cluster '$(KIND_CLUSTER)' already exists. Skipping creation." ;; \ + *) \ + echo "Creating Kind cluster '$(KIND_CLUSTER)'..."; \ + $(KIND) create cluster --name $(KIND_CLUSTER) ;; \ + esac + +.PHONY: test-e2e +test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind. + KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v + $(MAKE) cleanup-test-e2e + +.PHONY: cleanup-test-e2e +cleanup-test-e2e: ## Tear down the Kind cluster used for e2e tests + @$(KIND) delete cluster --name $(KIND_CLUSTER) + +.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 @@ -119,34 +163,40 @@ build: manifests generate fmt vet ## Build manager binary. run: manifests generate fmt vet ## Run a controller from your host. go run ./cmd/main.go -# If you wish built 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. +# 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: test ## Build docker image with the manager. - docker build -t ${IMG} . +docker-build: ## Build docker image with the manager. + $(CONTAINER_TOOL) build -t ${IMG} . .PHONY: docker-push docker-push: ## Push docker image with the manager. - docker push ${IMG} + $(CONTAINER_TOOL) push ${IMG} -# PLATFORMS defines the target platforms for the manager image be build to provide support to multiple +# 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: -# - able to use docker buildx . More info: https://docs.docker.com/build/buildx/ -# - have enable BuildKit, More info: https://docs.docker.com/develop/develop-images/build_enhancements/ -# - be able to push the image for your registry (i.e. if you do not inform a valid value via IMG=> then the export will fail) -# To properly provided solutions that supports more than one platform you should use this option. +# - 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: test ## Build and push docker image for the manager for cross-platform support +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 - - docker buildx create --name project-v3-builder - docker buildx use project-v3-builder - - docker buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . - - docker buildx rm project-v3-builder + - $(CONTAINER_TOOL) buildx create --name example-operator-builder + $(CONTAINER_TOOL) buildx use example-operator-builder + - $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross . + - $(CONTAINER_TOOL) buildx rm example-operator-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 @@ -155,22 +205,22 @@ endif .PHONY: install install: manifests kustomize ## Install CRDs into the K8s cluster specified in ~/.kube/config. - $(KUSTOMIZE) build config/crd | kubectl apply -f - + $(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 - + $(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 - + $(KUSTOMIZE) build config/default | $(KUBECTL) apply -f - .PHONY: undeploy -undeploy: ## 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 - +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 - -##@ Build Dependencies +##@ Dependencies ## Location to install dependencies to LOCALBIN ?= $(shell pwd)/bin @@ -178,34 +228,65 @@ $(LOCALBIN): mkdir -p $(LOCALBIN) ## Tool Binaries +KUBECTL ?= kubectl +KIND ?= kind KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint ## Tool Versions -KUSTOMIZE_VERSION ?= v3.8.7 -CONTROLLER_TOOLS_VERSION ?= v0.17.1 +KUSTOMIZE_VERSION ?= v5.6.0 +CONTROLLER_TOOLS_VERSION ?= v0.18.0 +#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 ?= v2.1.0 -KUSTOMIZE_INSTALL_SCRIPT ?= "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" .PHONY: kustomize -kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. If wrong version is installed, it will be removed before downloading. +kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary. $(KUSTOMIZE): $(LOCALBIN) - @if test -x $(LOCALBIN)/kustomize && ! $(LOCALBIN)/kustomize version | grep -q $(KUSTOMIZE_VERSION); then \ - echo "$(LOCALBIN)/kustomize version is not expected $(KUSTOMIZE_VERSION). Removing it before installing."; \ - rm -rf $(LOCALBIN)/kustomize; \ - fi - test -s $(LOCALBIN)/kustomize || { curl -Ss $(KUSTOMIZE_INSTALL_SCRIPT) | bash -s -- $(subst v,,$(KUSTOMIZE_VERSION)) $(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. If wrong version is installed, it will be overwritten. +controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary. $(CONTROLLER_GEN): $(LOCALBIN) - test -s $(LOCALBIN)/controller-gen && $(LOCALBIN)/controller-gen --version | grep -q $(CONTROLLER_TOOLS_VERSION) || \ - GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-tools/cmd/controller-gen@$(CONTROLLER_TOOLS_VERSION) + $(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 envtest-setup locally if necessary. +envtest: $(ENVTEST) ## Download setup-envtest locally if necessary. $(ENVTEST): $(LOCALBIN) - test -s $(LOCALBIN)/setup-envtest || GOBIN=$(LOCALBIN) go install sigs.k8s.io/controller-runtime/tools/setup-envtest@latest + $(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/v2/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 .PHONY: operator-sdk OPERATOR_SDK ?= $(LOCALBIN)/operator-sdk @@ -233,14 +314,14 @@ bundle: manifests kustomize operator-sdk ## Generate bundle manifests and metada .PHONY: bundle-build bundle-build: ## Build the bundle image. - docker build -f bundle.Dockerfile -t $(BUNDLE_IMG) . + $(CONTAINER_TOOL) build -f bundle.Dockerfile -t $(BUNDLE_IMG) . .PHONY: bundle-push bundle-push: ## Push the bundle image. $(MAKE) docker-push IMG=$(BUNDLE_IMG) .PHONY: opm -OPM = ./bin/opm +OPM = $(LOCALBIN)/opm opm: ## Download opm locally if necessary. ifeq (,$(wildcard $(OPM))) ifeq (,$(shell which opm 2>/dev/null)) @@ -248,7 +329,7 @@ ifeq (,$(shell which opm 2>/dev/null)) set -e ;\ mkdir -p $(dir $(OPM)) ;\ OS=$(shell go env GOOS) && ARCH=$(shell go env GOARCH) && \ - curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.23.0/$${OS}-$${ARCH}-opm ;\ + curl -sSLo $(OPM) https://github.com/operator-framework/operator-registry/releases/download/v1.55.0/$${OS}-$${ARCH}-opm ;\ chmod +x $(OPM) ;\ } else @@ -273,7 +354,7 @@ endif # https://github.com/operator-framework/community-operators/blob/7f1438c/docs/packaging-operator.md#updating-your-existing-operator .PHONY: catalog-build catalog-build: opm ## Build a catalog image. - $(OPM) index add --container-tool docker --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) + $(OPM) index add --container-tool $(CONTAINER_TOOL) --mode semver --tag $(CATALOG_IMG) --bundles $(BUNDLE_IMGS) $(FROM_INDEX_OPT) # Push the catalog image. .PHONY: catalog-push diff --git a/api/v1alpha1/bucket_types.go b/api/v1alpha1/bucket_types.go index f624fd7..d292eb6 100644 --- a/api/v1alpha1/bucket_types.go +++ b/api/v1alpha1/bucket_types.go @@ -17,8 +17,8 @@ limitations under the License. package v1alpha1 import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -57,8 +57,8 @@ type BucketStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // Bucket is the Schema for the buckets API type Bucket struct { @@ -69,7 +69,7 @@ type Bucket struct { Status BucketStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // BucketList contains a list of Bucket type BucketList struct { diff --git a/api/v1alpha1/path_types.go b/api/v1alpha1/path_types.go index 1f69c86..bc421de 100644 --- a/api/v1alpha1/path_types.go +++ b/api/v1alpha1/path_types.go @@ -52,8 +52,8 @@ type PathStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // Path is the Schema for the paths API type Path struct { @@ -64,7 +64,7 @@ type Path struct { Status PathStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // PathList contains a list of Path type PathList struct { diff --git a/api/v1alpha1/policy_types.go b/api/v1alpha1/policy_types.go index 17078af..f0d122e 100644 --- a/api/v1alpha1/policy_types.go +++ b/api/v1alpha1/policy_types.go @@ -52,8 +52,8 @@ type PolicyStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // Policy is the Schema for the policies API type Policy struct { @@ -64,7 +64,7 @@ type Policy struct { Status PolicyStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // PolicyList contains a list of Policy type PolicyList struct { diff --git a/api/v1alpha1/s3instance_types.go b/api/v1alpha1/s3instance_types.go index fa252d7..3c0c4ba 100644 --- a/api/v1alpha1/s3instance_types.go +++ b/api/v1alpha1/s3instance_types.go @@ -25,7 +25,6 @@ import ( // S3InstanceSpec defines the desired state of S3Instance type S3InstanceSpec struct { - // type of the S3Instance // +kubebuilder:validation:Required // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="S3Provider is immutable" @@ -77,8 +76,8 @@ type S3InstanceStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // S3Instance is the Schema for the S3Instances API type S3Instance struct { @@ -89,7 +88,7 @@ type S3Instance struct { Status S3InstanceStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // S3InstanceList contains a list of S3Instance type S3InstanceList struct { diff --git a/api/v1alpha1/s3user_types.go b/api/v1alpha1/s3user_types.go index bb361c6..fa749ba 100644 --- a/api/v1alpha1/s3user_types.go +++ b/api/v1alpha1/s3user_types.go @@ -25,7 +25,6 @@ import ( // S3UserSpec defines the desired state of S3User type S3UserSpec struct { - // Name of the S3User // +kubebuilder:validation:Required AccessKey string `json:"accessKey"` @@ -68,8 +67,8 @@ type S3UserStatus struct { Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } -//+kubebuilder:object:root=true -//+kubebuilder:subresource:status +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status // S3User is the Schema for the S3Users API type S3User struct { @@ -80,7 +79,7 @@ type S3User struct { Status S3UserStatus `json:"status,omitempty"` } -//+kubebuilder:object:root=true +// +kubebuilder:object:root=true // S3UserList contains a list of S3User type S3UserList struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index f191f44..a41dc1f 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -92,7 +92,7 @@ func (in *BucketSpec) DeepCopyInto(out *BucketSpec) { *out = make([]string, len(*in)) copy(*out, *in) } - out.Quota = in.Quota + in.Quota.DeepCopyInto(&out.Quota) } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BucketSpec. @@ -327,6 +327,8 @@ func (in *PolicyStatus) DeepCopy() *PolicyStatus { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Quota) DeepCopyInto(out *Quota) { *out = *in + out.Default = in.Default.DeepCopy() + out.Override = in.Override.DeepCopy() } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Quota. diff --git a/cmd/main.go b/cmd/main.go index fa4843c..a81dd89 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -17,9 +17,10 @@ limitations under the License. package main import ( + "crypto/tls" "flag" - "fmt" "os" + "path/filepath" "time" // Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.) @@ -40,10 +41,13 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/certwatcher" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - "sigs.k8s.io/controller-runtime/pkg/metrics/server" - //+kubebuilder:scaffold:imports + "sigs.k8s.io/controller-runtime/pkg/metrics/filters" + metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" + // +kubebuilder:scaffold:imports ) var ( @@ -51,52 +55,113 @@ var ( setupLog = ctrl.Log.WithName("setup") ) -// Implementing multi-value flag for custom CAs -// See also : https://stackoverflow.com/a/28323276 -type ArrayFlags []string - -func (flags *ArrayFlags) String() string { - return fmt.Sprint(*flags) -} - -func (flags *ArrayFlags) Set(value string) error { - *flags = append(*flags, value) - return nil -} - func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(s3v1alpha1.AddToScheme(scheme)) - //+kubebuilder:scaffold:scheme + // +kubebuilder:scaffold:scheme } +// nolint:gocyclo func main() { + + // operator related variables var metricsAddr string + var metricsCertPath, metricsCertName, metricsCertKey string + var webhookCertPath, webhookCertName, webhookCertKey string var enableLeaderElection bool var probeAddr string + var secureMetrics bool + var enableHTTP2 bool + var tlsOpts []func(*tls.Config) // S3 related variables var reconcilePeriod time.Duration - //K8S related variable + // K8S related variable var overrideExistingSecret bool var readExistingSecret bool + // operator related flags flag.StringVar( &metricsAddr, "metrics-bind-address", - ":8080", - "The address the metric endpoint binds to.", + "0", + "The address the metrics endpoint binds to. Use :8443 for HTTPS or :8080 for HTTP,"+ + "or leave as 0 to disable the metrics service.", ) + flag.StringVar( &probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.", ) - flag.BoolVar(&enableLeaderElection, "leader-elect", false, - "Enable leader election for controller manager. "+ - "Enabling this will ensure there is only one active controller manager.") + + flag.BoolVar( + &enableLeaderElection, + "leader-elect", + false, + "Enable leader election for controller manager."+ + "Enabling this will ensure there is only one active controller manager.", + ) + + flag.BoolVar( + &secureMetrics, + "metrics-secure", + true, + "If set, the metrics endpoint is served securely via HTTPS. Use --metrics-secure=false to use HTTP instead.", + ) + + flag.StringVar( + &webhookCertPath, + "webhook-cert-path", + "", + "The directory that contains the webhook certificate.", + ) + + flag.StringVar( + &webhookCertName, + "webhook-cert-name", + "tls.crt", + "The name of the webhook certificate file.", + ) + + flag.StringVar( + &webhookCertKey, + "webhook-cert-key", + "tls.key", + "The name of the webhook key file.", + ) + + flag.StringVar( + &metricsCertPath, + "metrics-cert-path", + "", + "The directory that contains the metrics server certificate.", + ) + + flag.StringVar( + &metricsCertName, + "metrics-cert-name", + "tls.crt", + "The name of the metrics server certificate file.", + ) + + flag.StringVar( + &metricsCertKey, + "metrics-cert-key", + "tls.key", + "The name of the metrics server key file.", + ) + + flag.BoolVar( + &enableHTTP2, + "enable-http2", + false, + "If set, HTTP/2 will be enabled for the metrics and webhook servers", + ) + + // k8s related flags flag.DurationVar(&reconcilePeriod, "reconcile-period", 0, "Default reconcile period for controllers. Zero to disable periodic reconciliation") @@ -124,9 +189,99 @@ func main() { ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) - // change due to upgrade version to 0.16.2 of sigs.k8s.io/controller-runtime - var serverOption = server.Options{ - BindAddress: metricsAddr, + // if the enable-http2 flag is false (the default), http/2 should be disabled + // due to its vulnerabilities. More specifically, disabling http/2 will + // prevent from being vulnerable to the HTTP/2 Stream Cancellation and + // Rapid Reset CVEs. For more information see: + // - https://github.com/advisories/GHSA-qppj-fm5r-hxr3 + // - https://github.com/advisories/GHSA-4374-p667-p6c8 + disableHTTP2 := func(c *tls.Config) { + setupLog.Info("disabling http/2") + c.NextProtos = []string{"http/1.1"} + } + + if !enableHTTP2 { + tlsOpts = append(tlsOpts, disableHTTP2) + } + + // Create watchers for metrics and webhooks certificates + var metricsCertWatcher, webhookCertWatcher *certwatcher.CertWatcher + + // Initial webhook TLS options + webhookTLSOpts := tlsOpts + + if len(webhookCertPath) > 0 { + setupLog.Info("Initializing webhook certificate watcher using provided certificates", + "webhook-cert-path", webhookCertPath, "webhook-cert-name", webhookCertName, "webhook-cert-key", webhookCertKey) + + var err error + webhookCertWatcher, err = certwatcher.New( + filepath.Join(webhookCertPath, webhookCertName), + filepath.Join(webhookCertPath, webhookCertKey), + ) + if err != nil { + setupLog.Error(err, "Failed to initialize webhook certificate watcher") + os.Exit(1) + } + + webhookTLSOpts = append(webhookTLSOpts, func(config *tls.Config) { + config.GetCertificate = webhookCertWatcher.GetCertificate + }) + } + + webhookServer := webhook.NewServer(webhook.Options{ + TLSOpts: webhookTLSOpts, + }) + + // Metrics endpoint is enabled in 'config/default/kustomization.yaml'. The Metrics options configure the server. + // More info: + // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.4/pkg/metrics/server + // - https://book.kubebuilder.io/reference/metrics.html + metricsServerOptions := metricsserver.Options{ + BindAddress: metricsAddr, + SecureServing: secureMetrics, + // TODO(user): TLSOpts is used to allow configuring the TLS config used for the server. If certificates are + // not provided, self-signed certificates will be generated by default. This option is not recommended for + // production environments as self-signed certificates do not offer the same level of trust and security + // as certificates issued by a trusted Certificate Authority (CA). The primary risk is potentially allowing + // unauthorized access to sensitive metrics data. Consider replacing with CertDir, CertName, and KeyName + // to provide certificates, ensuring the server communicates using trusted and secure certificates. + TLSOpts: tlsOpts, + } + + if secureMetrics { + // FilterProvider is used to protect the metrics endpoint with authn/authz. + // These configurations ensure that only authorized users and service accounts + // can access the metrics endpoint. The RBAC are configured in 'config/rbac/kustomization.yaml'. More info: + // https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.18.4/pkg/metrics/filters#WithAuthenticationAndAuthorization + metricsServerOptions.FilterProvider = filters.WithAuthenticationAndAuthorization + } + + // If the certificate is not specified, controller-runtime will automatically + // generate self-signed certificates for the metrics server. While convenient for development and testing, + // this setup is not recommended for production. + // + // TODO(user): If you enable certManager, uncomment the following lines: + // - [METRICS-WITH-CERTS] at config/default/kustomization.yaml to generate and use certificates + // managed by cert-manager for the metrics server. + // - [PROMETHEUS-WITH-CERTS] at config/prometheus/kustomization.yaml for TLS certification. + if len(metricsCertPath) > 0 { + setupLog.Info("Initializing metrics certificate watcher using provided certificates", + "metrics-cert-path", metricsCertPath, "metrics-cert-name", metricsCertName, "metrics-cert-key", metricsCertKey) + + var err error + metricsCertWatcher, err = certwatcher.New( + filepath.Join(metricsCertPath, metricsCertName), + filepath.Join(metricsCertPath, metricsCertKey), + ) + if err != nil { + setupLog.Error(err, "to initialize metrics certificate watcher", "error", err) + os.Exit(1) + } + + metricsServerOptions.TLSOpts = append(metricsServerOptions.TLSOpts, func(config *tls.Config) { + config.GetCertificate = metricsCertWatcher.GetCertificate + }) } s3Factory := s3factory.NewS3Factory() @@ -134,17 +289,12 @@ func main() { controllerHelper := helpers.NewControllerHelper() mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ - Scheme: scheme, - Metrics: serverOption, - // commented because option format change in ctrl.Options - //Port: 9443, + Scheme: scheme, + Metrics: metricsServerOptions, + WebhookServer: webhookServer, HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "1402b7b1.onyxia.sh", - // To change the sync period, it's possible to use cache.Options() - // Caveat : the actual period is slightly longer than what configured, - // possibly because of cache expiration not accounted for ? - // Cache: cache.Options{SyncPeriod: 2 * time.Minute}, // LeaderElectionReleaseOnCancel defines if the leader should step down voluntarily // when the Manager ends. This requires the binary to immediately end when the // Manager is stopped, otherwise, this setting is unsafe. Setting this significantly @@ -221,7 +371,23 @@ func main() { os.Exit(1) } - //+kubebuilder:scaffold:builder + // +kubebuilder:scaffold:builder + + if metricsCertWatcher != nil { + setupLog.Info("Adding metrics certificate watcher to manager") + if err := mgr.Add(metricsCertWatcher); err != nil { + setupLog.Error(err, "unable to add metrics certificate watcher to manager") + os.Exit(1) + } + } + + if webhookCertWatcher != nil { + setupLog.Info("Adding webhook certificate watcher to manager") + if err := mgr.Add(webhookCertWatcher); err != nil { + setupLog.Error(err, "unable to add webhook certificate watcher to manager") + os.Exit(1) + } + } if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { setupLog.Error(err, "unable to set up health check") diff --git a/config/crd/bases/s3.onyxia.sh_buckets.yaml b/config/crd/bases/s3.onyxia.sh_buckets.yaml index e889043..030bd58 100644 --- a/config/crd/bases/s3.onyxia.sh_buckets.yaml +++ b/config/crd/bases/s3.onyxia.sh_buckets.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.1 name: buckets.s3.onyxia.sh spec: group: s3.onyxia.sh diff --git a/config/crd/bases/s3.onyxia.sh_paths.yaml b/config/crd/bases/s3.onyxia.sh_paths.yaml index b878514..67fd1c5 100644 --- a/config/crd/bases/s3.onyxia.sh_paths.yaml +++ b/config/crd/bases/s3.onyxia.sh_paths.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.1 name: paths.s3.onyxia.sh spec: group: s3.onyxia.sh diff --git a/config/crd/bases/s3.onyxia.sh_policies.yaml b/config/crd/bases/s3.onyxia.sh_policies.yaml index 0f22984..ce5f62e 100644 --- a/config/crd/bases/s3.onyxia.sh_policies.yaml +++ b/config/crd/bases/s3.onyxia.sh_policies.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.1 name: policies.s3.onyxia.sh spec: group: s3.onyxia.sh diff --git a/config/crd/bases/s3.onyxia.sh_s3instances.yaml b/config/crd/bases/s3.onyxia.sh_s3instances.yaml index 75056bd..8cf429d 100644 --- a/config/crd/bases/s3.onyxia.sh_s3instances.yaml +++ b/config/crd/bases/s3.onyxia.sh_s3instances.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.1 name: s3instances.s3.onyxia.sh spec: group: s3.onyxia.sh diff --git a/config/crd/bases/s3.onyxia.sh_s3users.yaml b/config/crd/bases/s3.onyxia.sh_s3users.yaml index a131b2b..5dffc6d 100644 --- a/config/crd/bases/s3.onyxia.sh_s3users.yaml +++ b/config/crd/bases/s3.onyxia.sh_s3users.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.1 name: s3users.s3.onyxia.sh spec: group: s3.onyxia.sh diff --git a/go.mod b/go.mod index f5c36c1..dc1c302 100644 --- a/go.mod +++ b/go.mod @@ -1,104 +1,134 @@ module github.com/InseeFrLab/s3-operator -go 1.23.0 +go 1.25.0 require ( - github.com/minio/madmin-go/v3 v3.0.99 - github.com/minio/minio-go/v7 v7.0.88 - github.com/onsi/ginkgo/v2 v2.23.0 - github.com/onsi/gomega v1.36.2 - github.com/stretchr/testify v1.10.0 - go.uber.org/zap v1.27.0 - k8s.io/api v0.32.3 - k8s.io/apimachinery v0.32.3 - k8s.io/client-go v0.32.3 - sigs.k8s.io/controller-runtime v0.20.3 + github.com/minio/madmin-go/v4 v4.6.0 + github.com/minio/minio-go/v7 v7.0.97 + github.com/onsi/ginkgo/v2 v2.27.3 + github.com/onsi/gomega v1.38.3 + github.com/stretchr/testify v1.11.1 + go.uber.org/zap v1.27.1 + k8s.io/api v0.35.0 + k8s.io/apimachinery v0.35.0 + k8s.io/client-go v0.35.0 + sigs.k8s.io/controller-runtime v0.21.0 ) require ( - github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-ini/ini v1.67.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/prometheus/prometheus v0.54.1 // indirect - github.com/shoenig/go-m1cpu v0.1.6 // indirect + github.com/prometheus/prometheus v0.304.1 // indirect github.com/x448/float16 v0.8.4 // indirect ) require ( + cel.dev/expr v0.24.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.0 // indirect github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/ebitengine/purego v0.8.4 // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang-jwt/jwt/v4 v4.5.0 // indirect + github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.6.8 // indirect - github.com/google/go-cmp v0.6.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect + github.com/google/cel-go v0.26.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.11 // indirect - github.com/klauspost/cpuid/v2 v2.2.9 // indirect - github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 // indirect + github.com/klauspost/compress v1.18.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.11 // indirect + github.com/klauspost/crc32 v1.3.0 // indirect + github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect - github.com/minio/crc64nvme v1.0.1 // indirect + github.com/minio/crc64nvme v1.1.0 // indirect github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/pkg/v3 v3.4.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c // indirect - github.com/pkg/errors v0.9.1 // indirect + github.com/philhofer/fwd v1.2.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect - github.com/prometheus/client_golang v1.20.4 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect - github.com/prometheus/prom2json v1.4.1 // indirect + github.com/prometheus/client_golang v1.23.2 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/prometheus/prom2json v1.4.2 // indirect github.com/rs/xid v1.6.0 // indirect - github.com/safchain/ethtool v0.4.1 // indirect + github.com/safchain/ethtool v0.6.1 // indirect github.com/secure-io/sio-go v0.3.1 // indirect - github.com/shirou/gopsutil/v3 v3.24.5 // indirect - github.com/spf13/pflag v1.0.5 // indirect + github.com/shirou/gopsutil/v4 v4.25.5 // indirect + github.com/spf13/cobra v1.10.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect - github.com/tinylib/msgp v1.2.5 // indirect - github.com/tklauser/go-sysconf v0.3.14 // indirect - github.com/tklauser/numcpus v0.8.0 // indirect + github.com/tinylib/msgp v1.5.0 // indirect + github.com/tklauser/go-sysconf v0.3.15 // indirect + github.com/tklauser/numcpus v0.10.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.36.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.36.0 // indirect + go.opentelemetry.io/otel/sdk v1.36.0 // indirect + go.opentelemetry.io/otel/trace v1.36.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.33.0 // indirect - golang.org/x/net v0.35.0 // indirect - golang.org/x/oauth2 v0.23.0 // indirect - golang.org/x/sync v0.11.0 // indirect - golang.org/x/sys v0.30.0 // indirect - golang.org/x/term v0.29.0 // indirect - golang.org/x/text v0.22.0 // indirect - golang.org/x/time v0.7.0 // indirect - golang.org/x/tools v0.30.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.46.0 // indirect + golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect + golang.org/x/mod v0.31.0 // indirect + golang.org/x/net v0.48.0 // indirect + golang.org/x/oauth2 v0.32.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.39.0 // indirect + golang.org/x/term v0.38.0 // indirect + golang.org/x/text v0.32.0 // indirect + golang.org/x/time v0.11.0 // indirect + golang.org/x/tools v0.40.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/protobuf v1.36.1 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect + google.golang.org/grpc v1.73.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.32.1 // indirect + k8s.io/apiextensions-apiserver v0.35.0 // indirect + k8s.io/apiserver v0.35.0 // indirect + k8s.io/component-base v0.35.0 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect - k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect - sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 438ed8d..7dac869 100644 --- a/go.sum +++ b/go.sum @@ -1,29 +1,51 @@ +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v5.6.0+incompatible h1:jBYDEEiFBPxA0v50tFdvOzQQTCvpL6mnFh5mB2/l16U= -github.com/evanphx/json-patch v5.6.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= -github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= +github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= -github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= -github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= @@ -37,81 +59,85 @@ github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+Gr github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= -github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= -github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= +github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= -github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= +github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= -github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= +github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU= +github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM= +github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683 h1:7UMa6KCCMjZEMDtTVdcGu0B1GmmC7QJKiCCjyTAWQy0= -github.com/lufia/plan9stats v0.0.0-20240909124753-873cd0166683/go.mod h1:ilwx/Dta8jXAgpFYFvSWEMwxmbWXyiUHkd5FwyKhb5k= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr325bN2FD2ISlRRztXibcX6e8f5FR5Dc= +github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY= -github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= -github.com/minio/madmin-go/v3 v3.0.90 h1:Lz6a6eT1h5QT54fkbsEJ0xcWuvBjE1IaNgxfkxe6Qxs= -github.com/minio/madmin-go/v3 v3.0.90/go.mod h1:pMLdj9OtN0CANNs5tdm6opvOlDFfj0WhbztboZAjRWE= -github.com/minio/madmin-go/v3 v3.0.99 h1:u+xEQxaZEp9s5CCCrgQus1aj/z/X/vnuJJcued0dwW0= -github.com/minio/madmin-go/v3 v3.0.99/go.mod h1:pMLdj9OtN0CANNs5tdm6opvOlDFfj0WhbztboZAjRWE= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= +github.com/minio/crc64nvme v1.1.0 h1:e/tAguZ+4cw32D+IO/8GSf5UVr9y+3eJcxZI2WOO/7Q= +github.com/minio/crc64nvme v1.1.0/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= +github.com/minio/madmin-go/v4 v4.6.0 h1:wJa//JX+GhghPqRiMsrU1tIUO8NGPnBEZtoUFSV/ZbQ= +github.com/minio/madmin-go/v4 v4.6.0/go.mod h1:dzLgRjfo2JOOcDsTt6FGFPuyzMR/4KSZcUVEbxUlSmw= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.84 h1:D1HVmAF8JF8Bpi6IU4V9vIEj+8pc+xU88EWMs2yed0E= -github.com/minio/minio-go/v7 v7.0.84/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= -github.com/minio/minio-go/v7 v7.0.88 h1:v8MoIJjwYxOkehp+eiLIuvXk87P2raUtoU5klrAAshs= -github.com/minio/minio-go/v7 v7.0.88/go.mod h1:33+O8h0tO7pCeCWwBVa07RhVVfB/3vS4kEX7rwYKmIg= +github.com/minio/minio-go/v7 v7.0.97 h1:lqhREPyfgHTB/ciX8k2r8k0D93WaFqxbJX36UZq5occ= +github.com/minio/minio-go/v7 v7.0.97/go.mod h1:re5VXuo0pwEtoNLsNuSr0RrLfT/MBtohwdaSmPPSRSk= +github.com/minio/pkg/v3 v3.4.0 h1:Jryq7huJHEEr02hxjJHXxIBxAEFSdJ64hmjw/ucYuIo= +github.com/minio/pkg/v3 v3.4.0/go.mod h1:Iv4ytZUZbcjIxjU57C7TbD7tgJo/5zXQYhFI+kCHQRQ= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/ginkgo/v2 v2.23.0 h1:FA1xjp8ieYDzlgS5ABTpdUDB7wtngggONc8a7ku2NqQ= -github.com/onsi/ginkgo/v2 v2.23.0/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c h1:dAMKvw0MlJT1GshSTtih8C2gDs04w8dReiOGXrGLNoY= -github.com/philhofer/fwd v1.1.3-0.20240916144458-20a13a1f6b7c/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= +github.com/onsi/ginkgo/v2 v2.27.3 h1:ICsZJ8JoYafeXFFlFAG75a7CxMsJHwgKwtO+82SE9L8= +github.com/onsi/ginkgo/v2 v2.27.3/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= +github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM= +github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -119,166 +145,175 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= -github.com/prometheus/client_golang v1.20.4 h1:Tgh3Yr67PaOv/uTqloMsCEdeuFTatm5zIq5+qNN23vI= -github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= -github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/prometheus/prom2json v1.4.1 h1:7McxdrHgPEOtMwWjkKtd0v5AhpR2Q6QAnlHKVxq0+tQ= -github.com/prometheus/prom2json v1.4.1/go.mod h1:CzOQykSKFxXuC7ELUZHOHQvwKesQ3eN0p2PWLhFitQM= -github.com/prometheus/prometheus v0.54.1 h1:vKuwQNjnYN2/mDoWfHXDhAsz/68q/dQDb+YbcEqU7MQ= -github.com/prometheus/prometheus v0.54.1/go.mod h1:xlLByHhk2g3ycakQGrMaU8K7OySZx98BzeCR99991NY= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= +github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/prometheus/prom2json v1.4.2 h1:PxCTM+Whqi/eykO1MKsEL0p/zMpxp9ybpsmdFamw6po= +github.com/prometheus/prom2json v1.4.2/go.mod h1:zuvPm7u3epZSbXPWHny6G+o8ETgu6eAK3oPr6yFkRWE= +github.com/prometheus/prometheus v0.304.1 h1:e4kpJMb2Vh/PcR6LInake+ofcvFYHT+bCfmBvOkaZbY= +github.com/prometheus/prometheus v0.304.1/go.mod h1:ioGx2SGKTY+fLnJSQCdTHqARVldGNS8OlIe3kvp98so= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU= github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/safchain/ethtool v0.4.1 h1:S6mEleTADqgynileXoiapt/nKnatyR6bmIHoF+h2ADo= -github.com/safchain/ethtool v0.4.1/go.mod h1:XLLnZmy4OCRTkksP/UiMjij96YmIsBfmBQcs7H6tA48= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/safchain/ethtool v0.6.1 h1:mhRnXE1H8fV8TTXh/HdqE4tXtb57r//BQh5pPYMuM5k= +github.com/safchain/ethtool v0.6.1/go.mod h1:JzoNbG8xeg/BeVeVoMCtCb3UPWoppZZbFpA+1WFh+M0= github.com/secure-io/sio-go v0.3.1 h1:dNvY9awjabXTYGsTF1PiCySl9Ltofk9GA3VdWlo7rRc= github.com/secure-io/sio-go v0.3.1/go.mod h1:+xbkjDzPjwh4Axd07pRKSNriS9SCiYksWnZqdnfpQxs= -github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= -github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= -github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= -github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= -github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= -github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= +github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tinylib/msgp v1.2.5 h1:WeQg1whrXRFiZusidTQqzETkRpGjFjcIhW6uqWH09po= -github.com/tinylib/msgp v1.2.5/go.mod h1:ykjzy2wzgrlvpDCRc4LA8UXy6D8bzMSuAF3WD57Gok0= -github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU= -github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY= -github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY= -github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tinylib/msgp v1.5.0 h1:GWnqAE54wmnlFazjq2+vgr736Akg58iiHImh+kPY2pc= +github.com/tinylib/msgp v1.5.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o= +github.com/tklauser/go-sysconf v0.3.15 h1:VE89k0criAymJ/Os65CSn1IXaol+1wrsFHEB8Ol49K4= +github.com/tklauser/go-sysconf v0.3.15/go.mod h1:Dmjwr6tYFIseJw7a3dRLJfsHAMXZ3nEnL/aZY+0IuI4= +github.com/tklauser/numcpus v0.10.0 h1:18njr6LDBk1zuna922MgdjQuJFjrdppsZG60sHGfjso= +github.com/tklauser/numcpus v0.10.0/go.mod h1:BiTKazU708GQTYF4mB+cmlpT2Is1gLk7XVuEeem8LsQ= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= +go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= +go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= +go.opentelemetry.io/otel/sdk v1.36.0 h1:b6SYIuLRs88ztox4EyrvRti80uXIFy+Sqzoh9kFULbs= +go.opentelemetry.io/otel/sdk v1.36.0/go.mod h1:+lC+mTgD+MUWfjJubi2vvXWcVxyr9rmlshZni72pXeY= +go.opentelemetry.io/otel/sdk/metric v1.36.0 h1:r0ntwwGosWGaa0CrSt8cuNuTcccMXERFwHX4dThiPis= +go.opentelemetry.io/otel/sdk/metric v1.36.0/go.mod h1:qTNOhFDfKRwX0yXOqJYegL5WRaW376QbB7P4Pb0qva4= +go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= +go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus= -golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= +golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= +golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= +golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= -golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= -golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs= -golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= +golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= -golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= -golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= -golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= -golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= -golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= -golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= -golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= -golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ= -golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= -golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= -golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= +golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= +golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= +golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= -google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= +google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= -k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= -k8s.io/api v0.32.3 h1:Hw7KqxRusq+6QSplE3NYG4MBxZw1BZnq4aP4cJVINls= -k8s.io/api v0.32.3/go.mod h1:2wEDTXADtm/HA7CCMD8D8bK4yuBUptzaRhYcYEEYA3k= -k8s.io/apiextensions-apiserver v0.32.0 h1:S0Xlqt51qzzqjKPxfgX1xh4HBZE+p8KKBq+k2SWNOE0= -k8s.io/apiextensions-apiserver v0.32.0/go.mod h1:86hblMvN5yxMvZrZFX2OhIHAuFIMJIZ19bTvzkP+Fmw= -k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= -k8s.io/apiextensions-apiserver v0.32.1/go.mod h1:sxWIGuGiYov7Io1fAS2X06NjMIk5CbRHc2StSmbaQto= -k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= -k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apimachinery v0.32.3 h1:JmDuDarhDmA/Li7j3aPrwhpNBA94Nvk5zLeOge9HH1U= -k8s.io/apimachinery v0.32.3/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= -k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= -k8s.io/client-go v0.32.3 h1:RKPVltzopkSgHS7aS98QdscAgtgah/+zmpAogooIqVU= -k8s.io/client-go v0.32.3/go.mod h1:3v0+3k4IcT9bXTc4V2rt+d2ZPPG700Xy6Oi0Gdl2PaY= +k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= +k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= +k8s.io/apiextensions-apiserver v0.35.0 h1:3xHk2rTOdWXXJM+RDQZJvdx0yEOgC0FgQ1PlJatA5T4= +k8s.io/apiextensions-apiserver v0.35.0/go.mod h1:E1Ahk9SADaLQ4qtzYFkwUqusXTcaV2uw3l14aqpL2LU= +k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= +k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.0 h1:CUGo5o+7hW9GcAEF3x3usT3fX4f9r8xmgQeCBDaOgX4= +k8s.io/apiserver v0.35.0/go.mod h1:QUy1U4+PrzbJaM3XGu2tQ7U9A4udRRo5cyxkFX0GEds= +k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= +k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +k8s.io/component-base v0.35.0 h1:+yBrOhzri2S1BVqyVSvcM3PtPyx5GUxCK2tinZz1G94= +k8s.io/component-base v0.35.0/go.mod h1:85SCX4UCa6SCFt6p3IKAPej7jSnF3L8EbfSyMZayJR0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= -k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= -k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.20.1 h1:JbGMAG/X94NeM3xvjenVUaBjy6Ui4Ogd/J5ZtjZnHaE= -sigs.k8s.io/controller-runtime v0.20.1/go.mod h1:BrP3w158MwvB3ZbNpaAcIKkHQ7YGpYnzpoSTZ8E14WU= -sigs.k8s.io/controller-runtime v0.20.3 h1:I6Ln8JfQjHH7JbtCD2HCYHoIzajoRxPNuvhvcDbZgkI= -sigs.k8s.io/controller-runtime v0.20.3/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= -sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= -sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= +k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= +sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/controller/bucket/controller.go b/internal/controller/bucket/controller.go index 593f4e9..849c8ad 100644 --- a/internal/controller/bucket/controller.go +++ b/internal/controller/bucket/controller.go @@ -31,9 +31,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" ) -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=buckets,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=buckets/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=buckets/finalizers,verbs=update +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=buckets,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=buckets/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=buckets/finalizers,verbs=update // BucketReconciler reconciles a Bucket object type BucketReconciler struct { @@ -49,6 +49,7 @@ type BucketReconciler struct { func (r *BucketReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&s3v1alpha1.Bucket{}). + Named("bucket"). // REF : https://sdk.operatorframework.io/docs/building-operators/golang/references/event-filtering/ WithEventFilter(predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { diff --git a/internal/controller/bucket/finalizer.go b/internal/controller/bucket/finalizer.go index 3517ac1..35160a8 100644 --- a/internal/controller/bucket/finalizer.go +++ b/internal/controller/bucket/finalizer.go @@ -43,7 +43,7 @@ func (r *BucketReconciler) handleDeletion( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -61,7 +61,7 @@ func (r *BucketReconciler) handleDeletion( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -78,7 +78,7 @@ func (r *BucketReconciler) handleDeletion( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } diff --git a/internal/controller/bucket/finalizer_test.go b/internal/controller/bucket/finalizer_test.go index 5a42383..6940c5c 100644 --- a/internal/controller/bucket/finalizer_test.go +++ b/internal/controller/bucket/finalizer_test.go @@ -35,7 +35,6 @@ import ( ) func TestHandleDelete(t *testing.T) { - // Set up a logger before running tests log.SetLogger(zap.New(zap.UseDevMode(true))) @@ -78,14 +77,14 @@ func TestHandleDelete(t *testing.T) { t.Run("ressource have been deleted", func(t *testing.T) { // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: bucketResource.Name, Namespace: bucketResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) policy := &s3v1alpha1.Bucket{} - err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ + err = testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", Name: "example-bucket", }, policy) assert.NotNil(t, err) assert.ErrorContains(t, err, "buckets.s3.onyxia.sh \"example-bucket\" not found") }) - } diff --git a/internal/controller/bucket/reconcile.go b/internal/controller/bucket/reconcile.go index df7ba0e..5ae258e 100644 --- a/internal/controller/bucket/reconcile.go +++ b/internal/controller/bucket/reconcile.go @@ -77,7 +77,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -94,7 +94,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -103,7 +103,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr // Add finalizer for this CR if !controllerutil.ContainsFinalizer(bucketResource, bucketFinalizer) { logger.Info("Adding finalizer to bucket resource", "bucketName", - bucketResource.Spec.Name, "NamespacedName", req.NamespacedName.String()) + bucketResource.Spec.Name, "NamespacedName", req.Namespace) if ok := controllerutil.AddFinalizer(bucketResource, bucketFinalizer); !ok { logger.Error( err, @@ -111,7 +111,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -123,7 +123,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -135,7 +135,7 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -147,12 +147,11 @@ func (r *BucketReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr logger.Info("bucketResource have been marked for deletion", "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.handleDeletion(ctx, req, bucketResource) } return r.handleReconciliation(ctx, req, bucketResource) - } func (r *BucketReconciler) handleReconciliation( @@ -192,7 +191,7 @@ func (r *BucketReconciler) handleReconciliation( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -211,7 +210,6 @@ func (r *BucketReconciler) handleReconciliation( } return r.handleUpdate(ctx, req, bucketResource) - } func (r *BucketReconciler) handleUpdate( @@ -236,7 +234,7 @@ func (r *BucketReconciler) handleUpdate( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -260,7 +258,7 @@ func (r *BucketReconciler) handleUpdate( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -284,7 +282,7 @@ func (r *BucketReconciler) handleUpdate( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -304,7 +302,7 @@ func (r *BucketReconciler) handleUpdate( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -326,7 +324,7 @@ func (r *BucketReconciler) handleUpdate( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -361,7 +359,7 @@ func (r *BucketReconciler) handleUpdate( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -384,7 +382,7 @@ func (r *BucketReconciler) handleUpdate( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -430,7 +428,7 @@ func (r *BucketReconciler) handleCreation( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -451,7 +449,7 @@ func (r *BucketReconciler) handleCreation( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -472,7 +470,7 @@ func (r *BucketReconciler) handleCreation( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -491,7 +489,7 @@ func (r *BucketReconciler) handleCreation( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -519,7 +517,7 @@ func (r *BucketReconciler) handleCreation( "bucketName", bucketResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, diff --git a/internal/controller/bucket/reconcile_test.go b/internal/controller/bucket/reconcile_test.go index bcfa17d..720ff8a 100644 --- a/internal/controller/bucket/reconcile_test.go +++ b/internal/controller/bucket/reconcile_test.go @@ -93,7 +93,8 @@ func TestHandleUpdate(t *testing.T) { Name: "existing-bucket", Paths: []string{"example"}, S3InstanceRef: "s3-operator/default", - Quota: s3v1alpha1.Quota{Default: *resource.NewQuantity(int64(10), resource.BinarySI)}}, + Quota: s3v1alpha1.Quota{Default: *resource.NewQuantity(int64(10), resource.BinarySI)}, + }, } // Create a fake client with a sample CR @@ -108,7 +109,8 @@ func TestHandleUpdate(t *testing.T) { Name: "existing-invalid-bucket", Paths: []string{"example", "non-existing"}, S3InstanceRef: "s3-operator/default", - Quota: s3v1alpha1.Quota{Default: *resource.NewQuantity(int64(100), resource.BinarySI)}}, + Quota: s3v1alpha1.Quota{Default: *resource.NewQuantity(int64(100), resource.BinarySI)}, + }, } // Add mock for s3Factory and client diff --git a/internal/controller/path/controller.go b/internal/controller/path/controller.go index 8c80136..cf3f79b 100644 --- a/internal/controller/path/controller.go +++ b/internal/controller/path/controller.go @@ -31,9 +31,9 @@ import ( s3v1alpha1 "github.com/InseeFrLab/s3-operator/api/v1alpha1" ) -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=paths,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=paths/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=paths/finalizers,verbs=update +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=paths,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=paths/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=paths/finalizers,verbs=update // PathReconciler reconciles a Path object type PathReconciler struct { @@ -49,6 +49,7 @@ type PathReconciler struct { func (r *PathReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&s3v1alpha1.Path{}). + Named("path"). // REF : https://sdk.operatorframework.io/docs/building-operators/golang/references/event-filtering/ WithEventFilter(predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { diff --git a/internal/controller/path/finalizer.go b/internal/controller/path/finalizer.go index 1f60c84..c3f3c52 100644 --- a/internal/controller/path/finalizer.go +++ b/internal/controller/path/finalizer.go @@ -43,7 +43,7 @@ func (r *PathReconciler) handleDeletion( "path", pathResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -64,7 +64,7 @@ func (r *PathReconciler) handleDeletion( "pathResource", pathResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -81,7 +81,7 @@ func (r *PathReconciler) handleDeletion( "pathResource", pathResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -101,14 +101,13 @@ func (r *PathReconciler) finalizePath(ctx context.Context, pathResource *s3v1alp pathResource.Namespace, pathResource.Spec.S3InstanceRef, ) - if err != nil { logger.Error(err, "An error occurred while getting s3Client") return err } if s3Client.GetConfig().PathDeletionEnabled { - var failedPaths []string = make([]string, 0) + var failedPaths = make([]string, 0) for _, path := range pathResource.Spec.Paths { pathExists, err := s3Client.PathExists(pathResource.Spec.BucketName, path) diff --git a/internal/controller/path/finalizer_test.go b/internal/controller/path/finalizer_test.go index 6074cf3..b1a683b 100644 --- a/internal/controller/path/finalizer_test.go +++ b/internal/controller/path/finalizer_test.go @@ -34,7 +34,6 @@ import ( ) func TestHandleDelete(t *testing.T) { - // Set up a logger before running tests log.SetLogger(zap.New(zap.UseDevMode(true))) @@ -77,14 +76,14 @@ func TestHandleDelete(t *testing.T) { t.Run("ressource have been deleted", func(t *testing.T) { // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: pathResource.Name, Namespace: pathResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) path := &s3v1alpha1.Path{} - err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ + err = testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", Name: "example-path", }, path) assert.NotNil(t, err) assert.ErrorContains(t, err, "paths.s3.onyxia.sh \"example-path\" not found") }) - } diff --git a/internal/controller/path/reconcile.go b/internal/controller/path/reconcile.go index e082e5f..96ac5d2 100644 --- a/internal/controller/path/reconcile.go +++ b/internal/controller/path/reconcile.go @@ -77,7 +77,7 @@ func (r *PathReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. "pathResourceName", pathResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -94,7 +94,7 @@ func (r *PathReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. "pathResourceName", pathResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -103,13 +103,13 @@ func (r *PathReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. // Add finalizer for this CR if !controllerutil.ContainsFinalizer(pathResource, pathFinalizer) { logger.Info("Adding finalizer to pathResource", "pathResourceName", - pathResource.Name, "NamespacedName", req.NamespacedName.String()) + pathResource.Name, "NamespacedName", req.Namespace) if ok := controllerutil.AddFinalizer(pathResource, pathFinalizer); !ok { logger.Error( err, "Failed to add finalizer into pathResource", "pathResourceName", - pathResource.Name, "NamespacedName", req.NamespacedName.String()) + pathResource.Name, "NamespacedName", req.Namespace) return ctrl.Result{Requeue: true}, nil } @@ -118,7 +118,7 @@ func (r *PathReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. err, "an error occurred when adding finalizer on pathResource", "pathResourceName", - pathResource.Name, "NamespacedName", req.NamespacedName.String(), + pathResource.Name, "NamespacedName", req.Namespace, ) return ctrl.Result{}, err } @@ -128,7 +128,7 @@ func (r *PathReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. err, "Failed to re-fetch pathResource", "pathResourceName", - pathResource.Name, "NamespacedName", req.NamespacedName.String(), + pathResource.Name, "NamespacedName", req.Namespace, ) return ctrl.Result{}, err } @@ -140,12 +140,11 @@ func (r *PathReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl. logger.Info("pathResource have been marked for deletion", "pathResourceName", pathResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.handleDeletion(ctx, req, pathResource) } return r.handleReconciliation(ctx, req, pathResource) - } func (r *PathReconciler) handleReconciliation( @@ -153,7 +152,6 @@ func (r *PathReconciler) handleReconciliation( req reconcile.Request, pathResource *s3v1alpha1.Path, ) (reconcile.Result, error) { - logger := log.FromContext(ctx) // Create S3Client @@ -188,7 +186,7 @@ func (r *PathReconciler) handleReconciliation( "bucketName", pathResource.Spec.BucketName, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -210,7 +208,7 @@ func (r *PathReconciler) handleReconciliation( logger.Error(errorBucketNotFound, errorBucketNotFound.Error(), "pathResourceName", pathResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -244,7 +242,7 @@ func (r *PathReconciler) handleReconciliation( "bucketName", pathResource.Spec.BucketName, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -267,7 +265,7 @@ func (r *PathReconciler) handleReconciliation( "bucketName", pathResource.Spec.BucketName, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, diff --git a/internal/controller/path/status.go b/internal/controller/path/status.go index 2456a8c..142542b 100644 --- a/internal/controller/path/status.go +++ b/internal/controller/path/status.go @@ -44,5 +44,4 @@ func (r *PathReconciler) SetReconciledCondition( err, r.ReconcilePeriod, ) - } diff --git a/internal/controller/policy/controller.go b/internal/controller/policy/controller.go index 01be910..9a3ceb9 100644 --- a/internal/controller/policy/controller.go +++ b/internal/controller/policy/controller.go @@ -49,6 +49,7 @@ type PolicyReconciler struct { func (r *PolicyReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). For(&s3v1alpha1.Policy{}). + Named("policy"). // REF : https://sdk.operatorframework.io/docs/building-operators/golang/references/event-filtering/ WithEventFilter(predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { diff --git a/internal/controller/policy/finalizer.go b/internal/controller/policy/finalizer.go index 2adb995..1639e42 100644 --- a/internal/controller/policy/finalizer.go +++ b/internal/controller/policy/finalizer.go @@ -67,7 +67,7 @@ func (r *PolicyReconciler) handleDeletion( "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -85,7 +85,7 @@ func (r *PolicyReconciler) handleDeletion( "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -97,7 +97,7 @@ func (r *PolicyReconciler) handleDeletion( "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } diff --git a/internal/controller/policy/finalizer_test.go b/internal/controller/policy/finalizer_test.go index 4f4a24f..42eb8a3 100644 --- a/internal/controller/policy/finalizer_test.go +++ b/internal/controller/policy/finalizer_test.go @@ -34,7 +34,6 @@ import ( ) func TestHandleDelete(t *testing.T) { - // Set up a logger before running tests log.SetLogger(zap.New(zap.UseDevMode(true))) @@ -77,14 +76,14 @@ func TestHandleDelete(t *testing.T) { t.Run("ressource have been deleted", func(t *testing.T) { // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: policyResource.Name, Namespace: policyResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) policy := &s3v1alpha1.Policy{} - err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ + err = testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", Name: "example-policy", }, policy) assert.NotNil(t, err) assert.ErrorContains(t, err, "policies.s3.onyxia.sh \"example-policy\" not found") }) - } diff --git a/internal/controller/policy/reconcile.go b/internal/controller/policy/reconcile.go index b01644c..8831eba 100644 --- a/internal/controller/policy/reconcile.go +++ b/internal/controller/policy/reconcile.go @@ -31,7 +31,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" s3v1alpha1 "github.com/InseeFrLab/s3-operator/api/v1alpha1" - "github.com/minio/madmin-go/v3" + "github.com/minio/madmin-go/v4" ) // Reconcile is part of the main kubernetes reconciliation loop which aims to @@ -80,7 +80,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "bucketName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -97,7 +97,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -106,7 +106,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr // Add finalizer for this CR if !controllerutil.ContainsFinalizer(policyResource, policyFinalizer) { logger.Info("Adding finalizer to policy resource", "PolicyName", - policyResource.Spec.Name, "NamespacedName", req.NamespacedName.String()) + policyResource.Spec.Name, "NamespacedName", req.Namespace) if ok := controllerutil.AddFinalizer(policyResource, policyFinalizer); !ok { logger.Error( err, @@ -114,7 +114,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -127,7 +127,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "policyResource", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -144,7 +144,7 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -156,13 +156,12 @@ func (r *PolicyReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr logger.Info("policyResource have been marked for deletion", "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.handleDeletion(ctx, req, policyResource) } // Policy lifecycle management (other than deletion) starts here return r.handleReconciliation(ctx, req, policyResource) - } func (r *PolicyReconciler) handleReconciliation( @@ -201,7 +200,7 @@ func (r *PolicyReconciler) handleReconciliation( "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -236,7 +235,6 @@ func (r *PolicyReconciler) handleUpdate( policyResource.Namespace, policyResource.Spec.S3InstanceRef, ) - if err != nil { logger.Error(err, "an error occurred while getting s3Client") return r.SetReconciledCondition( @@ -258,7 +256,7 @@ func (r *PolicyReconciler) handleUpdate( "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -278,7 +276,7 @@ func (r *PolicyReconciler) handleUpdate( "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -306,9 +304,9 @@ func (r *PolicyReconciler) handleUpdate( "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) - r.SetReconciledCondition( + if _, err := r.SetReconciledCondition( ctx, req, policyResource, @@ -318,7 +316,9 @@ func (r *PolicyReconciler) handleUpdate( policyResource.Spec.Name, ), err, - ) + ); err != nil { + return ctrl.Result{}, err + } } } @@ -333,7 +333,8 @@ func (r *PolicyReconciler) handleUpdate( } func (r *PolicyReconciler) handleCreation(ctx context.Context, req reconcile.Request, - policyResource *s3v1alpha1.Policy) (reconcile.Result, error) { + policyResource *s3v1alpha1.Policy, +) (reconcile.Result, error) { logger := log.FromContext(ctx) s3Client, err := r.S3Instancehelper.GetS3ClientForRessource( @@ -344,7 +345,6 @@ func (r *PolicyReconciler) handleCreation(ctx context.Context, req reconcile.Req policyResource.Namespace, policyResource.Spec.S3InstanceRef, ) - if err != nil { logger.Error(err, "An error occurred while getting s3Client") return r.SetReconciledCondition( @@ -361,7 +361,6 @@ func (r *PolicyReconciler) handleCreation(ctx context.Context, req reconcile.Req policyResource.Spec.Name, policyResource.Spec.PolicyContent, ) - if err != nil { logger.Error( err, @@ -369,7 +368,7 @@ func (r *PolicyReconciler) handleCreation(ctx context.Context, req reconcile.Req "policyName", policyResource.Spec.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, diff --git a/internal/controller/s3instance/controller.go b/internal/controller/s3instance/controller.go index bbd8fc2..944360a 100644 --- a/internal/controller/s3instance/controller.go +++ b/internal/controller/s3instance/controller.go @@ -30,9 +30,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/predicate" ) -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=s3instances,verbs=get;list;watch;create;update;patch;delete -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=s3instances/status,verbs=get;update;patch -//+kubebuilder:rbac:groups=s3.onyxia.sh,resources=s3instances/finalizers,verbs=update +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=s3instances,verbs=get;list;watch;create;update;patch;delete +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=s3instances/status,verbs=get;update;patch +// +kubebuilder:rbac:groups=s3.onyxia.sh,resources=s3instances/finalizers,verbs=update // S3InstanceReconciler reconciles a S3Instance object type S3InstanceReconciler struct { @@ -49,6 +49,7 @@ func (r *S3InstanceReconciler) SetupWithManager(mgr ctrl.Manager) error { // filterLogger := ctrl.Log.WithName("filterEvt") return ctrl.NewControllerManagedBy(mgr). For(&s3v1alpha1.S3Instance{}). + Named("s3instance"). // See : https://sdk.operatorframework.io/docs/building-operators/golang/references/event-filtering/ WithEventFilter(predicate.Funcs{ // Ignore updates to CR status in which case metadata.Generation does not change, diff --git a/internal/controller/s3instance/finalizer.go b/internal/controller/s3instance/finalizer.go index 2a5dae4..deff7f1 100644 --- a/internal/controller/s3instance/finalizer.go +++ b/internal/controller/s3instance/finalizer.go @@ -45,17 +45,17 @@ func (r *S3InstanceReconciler) handleS3InstanceDeletion( s3InstanceResource.GetName(), ) - // Vérifier les références existantes + // Check for references to this S3Instance if err := r.checkS3InstanceReferences(ctx, s3InstanceResource); err != nil { return ctrl.Result{}, err } - //Remove s3InstanceFinalizer. Once all finalizers have been removed, the object will be deleted. + // Remove s3InstanceFinalizer. Once all finalizers have been removed, the object will be deleted. if ok := controllerutil.RemoveFinalizer(s3InstanceResource, s3InstanceFinalizer); !ok { logger.Info( "Failed to remove finalizer for S3Instance", "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -70,7 +70,7 @@ func (r *S3InstanceReconciler) handleS3InstanceDeletion( err, "Failed to remove finalizer for S3Instance", "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } diff --git a/internal/controller/s3instance/reconcile.go b/internal/controller/s3instance/reconcile.go index 548cf3e..c659862 100644 --- a/internal/controller/s3instance/reconcile.go +++ b/internal/controller/s3instance/reconcile.go @@ -50,7 +50,7 @@ func (r *S3InstanceReconciler) Reconcile( logger.Info( fmt.Sprintf("The S3InstanceResource CR %s has been removed. NOOP", req.Name), "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, nil } @@ -58,7 +58,7 @@ func (r *S3InstanceReconciler) Reconcile( err, "Failed to get S3InstanceResource", "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -80,7 +80,7 @@ func (r *S3InstanceReconciler) Reconcile( err, "Failed to update s3InstanceResource status", "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -95,7 +95,7 @@ func (r *S3InstanceReconciler) Reconcile( err, "Failed to re-fetch s3Instance", "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -103,13 +103,13 @@ func (r *S3InstanceReconciler) Reconcile( // Add finalizer for this CR if !controllerutil.ContainsFinalizer(s3InstanceResource, s3InstanceFinalizer) { - logger.Info("Adding finalizer to s3Instance", "NamespacedName", req.NamespacedName.String()) + logger.Info("Adding finalizer to s3Instance", "NamespacedName", req.Namespace) if ok := controllerutil.AddFinalizer(s3InstanceResource, s3InstanceFinalizer); !ok { logger.Error( err, "Failed to add finalizer into the s3Instance", "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -134,7 +134,7 @@ func (r *S3InstanceReconciler) Reconcile( err, "Failed to re-fetch s3Instance", "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -150,7 +150,6 @@ func (r *S3InstanceReconciler) Reconcile( // Reconciliation starts here return r.handleReconciliation(ctx, req, s3InstanceResource) - } func (r *S3InstanceReconciler) handleReconciliation( @@ -161,7 +160,6 @@ func (r *S3InstanceReconciler) handleReconciliation( logger := log.FromContext(ctx) s3Client, err := r.S3Instancehelper.GetS3ClientFromS3Instance(ctx, r.Client, r.S3factory, s3InstanceResource) - if err != nil { logger.Error( err, @@ -169,7 +167,7 @@ func (r *S3InstanceReconciler) handleReconciliation( "s3InstanceSecretRefName", s3InstanceResource.Spec.SecretRef, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition(ctx, req, s3InstanceResource, s3v1alpha1.Unreachable, "Failed to generate S3Instance ", err) @@ -183,7 +181,7 @@ func (r *S3InstanceReconciler) handleReconciliation( "s3InstanceName", s3InstanceResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition(ctx, req, s3InstanceResource, s3v1alpha1.CreationFailure, "Failed to generate S3Instance ", err) @@ -197,5 +195,4 @@ func (r *S3InstanceReconciler) handleReconciliation( "S3Instance instance reconciled", nil, ) - } diff --git a/internal/controller/s3instance/reconcile_test.go b/internal/controller/s3instance/reconcile_test.go index 94ee103..b1cc955 100644 --- a/internal/controller/s3instance/reconcile_test.go +++ b/internal/controller/s3instance/reconcile_test.go @@ -99,7 +99,6 @@ func TestHandleCreate(t *testing.T) { } t.Run("no error", func(t *testing.T) { - // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3instanceResource.Name, Namespace: s3instanceResource.Namespace}} _, err := reconciler.Reconcile(context.TODO(), req) @@ -107,10 +106,10 @@ func TestHandleCreate(t *testing.T) { }) t.Run("finalizer is added", func(t *testing.T) { - // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3instanceResource.Name, Namespace: s3instanceResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) // FetchReconciledInstance reconciledInstance := &s3v1alpha1.S3Instance{} @@ -123,10 +122,10 @@ func TestHandleCreate(t *testing.T) { }) t.Run("status is reconciled", func(t *testing.T) { - // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3instanceResource.Name, Namespace: s3instanceResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) // FetchReconciledInstance reconciledInstance := &s3v1alpha1.S3Instance{} @@ -139,10 +138,10 @@ func TestHandleCreate(t *testing.T) { }) t.Run("reason is creation failure because of invalid client", func(t *testing.T) { - // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3instanceResourceInvalid.Name, Namespace: s3instanceResourceInvalid.Namespace}} - reconciler.Reconcile(context.TODO(), req) + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NotNil(t, err) // 4️⃣ FetchReconciledInstance reconciledInstance := &s3v1alpha1.S3Instance{} diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 53390d2..6847bed 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -33,15 +33,17 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log/zap" s3onyxiashv1alpha1 "github.com/InseeFrLab/s3-operator/api/v1alpha1" - //+kubebuilder:scaffold:imports + // +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 cfg *rest.Config -var k8sClient client.Client -var testEnv *envtest.Environment +var ( + cfg *rest.Config + k8sClient client.Client + testEnv *envtest.Environment +) func TestControllers(t *testing.T) { RegisterFailHandler(Fail) @@ -75,12 +77,11 @@ var _ = BeforeSuite(func() { err = s3onyxiashv1alpha1.AddToScheme(scheme.Scheme) Expect(err).NotTo(HaveOccurred()) - //+kubebuilder:scaffold:scheme + // +kubebuilder:scaffold:scheme k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) Expect(err).NotTo(HaveOccurred()) Expect(k8sClient).NotTo(BeNil()) - }) var _ = AfterSuite(func() { diff --git a/internal/controller/user/controller.go b/internal/controller/user/controller.go index 52ed853..629f6a0 100644 --- a/internal/controller/user/controller.go +++ b/internal/controller/user/controller.go @@ -57,17 +57,16 @@ func (r *S3UserReconciler) SetupWithManager(mgr ctrl.Manager) error { // filterLogger := ctrl.Log.WithName("filterEvt") return ctrl.NewControllerManagedBy(mgr). For(&s3v1alpha1.S3User{}). + Named("s3User"). // The "secret owning" implies the reconcile loop will be called whenever a Secret owned // by a S3User is created/updated/deleted. In other words, even when creating a single S3User, // there is going to be several iterations. Owns(&corev1.Secret{}). // See : https://sdk.operatorframework.io/docs/building-operators/golang/references/event-filtering/ WithEventFilter(predicate.Funcs{ - // Ignore updates to CR status in which case metadata.Generation does not change, // unless it is a change to the underlying Secret UpdateFunc: func(e event.UpdateEvent) bool { - // To check if the update event is tied to a change on secret, // we try to cast e.ObjectNew to a secret (only if it's not a S3User, which // should prevent any TypeAssertionError based panic). diff --git a/internal/controller/user/finalizer.go b/internal/controller/user/finalizer.go index d9389e4..35d6e8c 100644 --- a/internal/controller/user/finalizer.go +++ b/internal/controller/user/finalizer.go @@ -66,7 +66,7 @@ func (r *S3UserReconciler) handleDeletion( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -86,7 +86,7 @@ func (r *S3UserReconciler) handleDeletion( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -105,7 +105,7 @@ func (r *S3UserReconciler) handleDeletion( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, "secretName", linkedSecret.Name, ) @@ -118,17 +118,16 @@ func (r *S3UserReconciler) handleDeletion( err, ) } - } - //Remove userFinalizer. Once all finalizers have been removed, the object will be deleted. + // Remove userFinalizer. Once all finalizers have been removed, the object will be deleted. if ok := controllerutil.RemoveFinalizer(userResource, userFinalizer); !ok { logger.Info( "Failed to remove finalizer for user resource", "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -145,7 +144,7 @@ func (r *S3UserReconciler) handleDeletion( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } diff --git a/internal/controller/user/finalizer_test.go b/internal/controller/user/finalizer_test.go index 9803fa4..bd8b0ee 100644 --- a/internal/controller/user/finalizer_test.go +++ b/internal/controller/user/finalizer_test.go @@ -14,7 +14,6 @@ See the License for the specific language governing permissions and limitations under the License. */ - package user_controller_test import ( @@ -35,7 +34,6 @@ import ( ) func TestHandleDelete(t *testing.T) { - // Set up a logger before running tests log.SetLogger(zap.New(zap.UseDevMode(true))) @@ -98,9 +96,10 @@ func TestHandleDelete(t *testing.T) { S3factory: testUtils.S3Factory, } req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3UserResource.Name, Namespace: s3UserResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) - testUtils.Client.Delete(context.TODO(), s3UserResource) - + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) + err = testUtils.Client.Delete(context.TODO(), s3UserResource) + assert.NoError(t, err) t.Run("no error", func(t *testing.T) { // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3UserResource.Name, Namespace: s3UserResource.Namespace}} @@ -111,9 +110,10 @@ func TestHandleDelete(t *testing.T) { t.Run("ressource have been deleted", func(t *testing.T) { // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3UserResource.Name, Namespace: s3UserResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) s3UserResource := &s3v1alpha1.S3User{} - err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ + err = testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", Name: "example-user", }, s3UserResource) @@ -127,7 +127,5 @@ func TestHandleDelete(t *testing.T) { }, s3UserSecret) assert.NotNil(t, err) assert.ErrorContains(t, err, "secrets \"existing-valid-user-credentials\" not found") - }) - } diff --git a/internal/controller/user/reconcile.go b/internal/controller/user/reconcile.go index 7538fa1..2e613d5 100644 --- a/internal/controller/user/reconcile.go +++ b/internal/controller/user/reconcile.go @@ -21,6 +21,7 @@ import ( "context" "fmt" "slices" + "strings" corev1 "k8s.io/api/core/v1" k8sapierrors "k8s.io/apimachinery/pkg/api/errors" @@ -79,7 +80,7 @@ func (r *S3UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -96,7 +97,7 @@ func (r *S3UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -108,7 +109,7 @@ func (r *S3UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) if ok := controllerutil.AddFinalizer(userResource, userFinalizer); !ok { logger.Error( @@ -117,7 +118,7 @@ func (r *S3UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{Requeue: true}, nil } @@ -129,7 +130,7 @@ func (r *S3UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -146,7 +147,7 @@ func (r *S3UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return ctrl.Result{}, err } @@ -159,12 +160,11 @@ func (r *S3UserReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.handleDeletion(ctx, req, userResource) } return r.handleReconciliation(ctx, req, userResource) - } func (r *S3UserReconciler) handleReconciliation( @@ -202,7 +202,7 @@ func (r *S3UserReconciler) handleReconciliation( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -256,7 +256,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -274,7 +274,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -294,7 +294,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) err = s3Client.DeleteUser(userResource.Spec.AccessKey) @@ -304,7 +304,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -347,7 +347,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) err = s3Client.DeleteUser(userResource.Spec.AccessKey) @@ -357,7 +357,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -377,13 +377,13 @@ func (r *S3UserReconciler) handleUpdate( logger.Info("Checking user policies", "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) userPolicies, err := s3Client.GetUserPolicies(userResource.Spec.AccessKey) if err != nil { logger.Error(err, "Could not check the user's policies", "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -404,7 +404,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) policyToDelete = append(policyToDelete, policy) } @@ -418,7 +418,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) policyToAdd = append(policyToAdd, policy) } @@ -433,7 +433,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -455,7 +455,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( @@ -475,7 +475,6 @@ func (r *S3UserReconciler) handleUpdate( string(currentUserSecret.Data[userResource.Spec.SecretFieldNameAccessKey]), string(currentUserSecret.Data[userResource.Spec.SecretFieldNameSecretKey]), ) - if err != nil { logger.Error( err, @@ -483,7 +482,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -502,7 +501,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) err = r.deleteSecret(ctx, ¤tUserSecret) if err != nil { @@ -511,7 +510,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -528,7 +527,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) } @@ -539,7 +538,7 @@ func (r *S3UserReconciler) handleUpdate( "userResourceName", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -560,7 +559,7 @@ func (r *S3UserReconciler) handleUpdate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( @@ -609,7 +608,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( @@ -628,15 +627,27 @@ func (r *S3UserReconciler) handleCreate( userResource, map[string][]byte{ userResource.Spec.SecretFieldNameAccessKey: []byte(userResource.Spec.AccessKey), - userResource.Spec.SecretFieldNameSecretKey: []byte(secretKey)}, - ) + userResource.Spec.SecretFieldNameSecretKey: []byte(secretKey), + "s3PathStyleUrl": []byte( + fmt.Sprintf("%t", s3Client.GetConfig().PathStyle), + ), + "s3region": []byte(s3Client.GetConfig().Region), + "s3CACertificate": []byte(strings.Join(s3Client.GetConfig().CaCertificatesBase64, ",")), + "s3url": []byte(s3Client.GetConfig().S3Url), + "s3ConnectionURL": []byte(fmt.Sprintf("https://%s:%s@%s", + userResource.Spec.AccessKey, + secretKey, + strings.TrimPrefix(s3Client.GetConfig().Endpoint, "https://"), + ), + ), + }) if err != nil { // Error while creating the Kubernetes secret - requeue the request. logger.Error(err, "Could not generate Kubernetes secret", "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -669,7 +680,6 @@ func (r *S3UserReconciler) handleCreate( // Creating the user err = s3Client.CreateUser(userResource.Spec.AccessKey, secretKey) - if err != nil { logger.Error( err, @@ -677,7 +687,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -697,7 +707,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) err = r.Create(ctx, secret) if err != nil { @@ -707,7 +717,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -748,7 +758,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -764,7 +774,7 @@ func (r *S3UserReconciler) handleCreate( for _, ref := range existingK8sSecret.OwnerReferences { if ref.Kind == "S3User" { if ref.UID != userResource.UID { - err = fmt.Errorf("The secret matching the new S3User's spec is owned by a different S3User.") + err = fmt.Errorf("secret matching the new S3User's spec is owned by a different S3User") logger.Error(err, "S3User could not be created because of existing secret", "conflictingUser", @@ -774,7 +784,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -795,8 +805,8 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) - var cpData = *&existingK8sSecret.Data + req.Namespace) + cpData := existingK8sSecret.Data for k, v := range cpData { if k == userResource.Spec.SecretFieldNameSecretKey { secretKey = string(v) @@ -809,7 +819,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) } // Creating the user err = s3Client.CreateUser(userResource.Spec.AccessKey, secretKey) @@ -820,7 +830,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) return r.SetReconciledCondition( ctx, @@ -839,7 +849,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String(), + req.Namespace, ) err = r.Update(ctx, secret) if err != nil { @@ -848,7 +858,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, @@ -886,7 +896,7 @@ func (r *S3UserReconciler) handleCreate( // Case 3.3 : they are not valid, and the operator is configured keep the existing secret // The user will not be created, with no requeue and with two possible ways out : either toggle // OverrideExistingSecret on, or delete the S3User whose credentials are not working anyway. - err = fmt.Errorf("A secret with the same name already exists ; as the operator is configured to NOT override nor read any pre-existing secrets, this user will not be created on S3 backend until spec change (to target new secret), or until the operator configuration is changed to override existing secrets") + err = fmt.Errorf("secret with the same name already exists ; as the operator is configured to not override nor read any pre-existing secrets, this user will not be created on S3 backend until spec change (to target new secret), or until the operator configuration is changed to override existing secrets") logger.Error(err, "S3User could not be created because of existing secret", "secretName", @@ -894,7 +904,7 @@ func (r *S3UserReconciler) handleCreate( "userResource", userResource.Name, "NamespacedName", - req.NamespacedName.String()) + req.Namespace) return r.SetReconciledCondition( ctx, req, diff --git a/internal/controller/user/reconcile_test.go b/internal/controller/user/reconcile_test.go index ff05232..1a60f20 100644 --- a/internal/controller/user/reconcile_test.go +++ b/internal/controller/user/reconcile_test.go @@ -136,7 +136,6 @@ func TestExistingSecret(t *testing.T) { reconciler.ReadExistingSecret = false reconciler.OverrideExistingSecret = true t.Run("no error override existing secret (case 3.2a)", func(t *testing.T) { - existingSecret := &corev1.Secret{} err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", @@ -144,7 +143,6 @@ func TestExistingSecret(t *testing.T) { }, existingSecret) assert.NoError(t, err) - // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3UserResource.Name, Namespace: s3UserResource.Namespace}} _, err = reconciler.Reconcile(context.TODO(), req) @@ -159,7 +157,6 @@ func TestExistingSecret(t *testing.T) { assert.Equal(t, metav1.ConditionTrue, s3UserResourceUpdated.Status.Conditions[0].Status) assert.Contains(t, s3UserResourceUpdated.Status.Conditions[0].Message, "User reconciled") - newSecret := &corev1.Secret{} err = testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", @@ -168,7 +165,6 @@ func TestExistingSecret(t *testing.T) { assert.NoError(t, err) assert.Equal(t, string(newSecret.Data["accessKey"]), string(existingSecret.Data["accessKey"])) assert.NotEqual(t, string(newSecret.Data["secretKey"]), string(existingSecret.Data["secretKey"])) - }) reconciler.OverrideExistingSecret = false reconciler.ReadExistingSecret = true @@ -305,7 +301,6 @@ func TestHandleCreate(t *testing.T) { }) t.Run("secret is created", func(t *testing.T) { - secretCreated := &corev1.Secret{} err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", @@ -314,9 +309,8 @@ func TestHandleCreate(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "example-user", string(secretCreated.Data["accessKey"])) assert.GreaterOrEqual(t, len(string(secretCreated.Data["secretKey"])), 20) - + assert.NotEmpty(t, string(secretCreated.Data["s3ConnectionURL"])) }) - } func TestHandleUpdate(t *testing.T) { @@ -365,7 +359,6 @@ func TestHandleUpdate(t *testing.T) { }, } - // Add mock for s3Factory and client testUtils := TestUtils.NewTestUtils() testUtils.SetupMockedS3FactoryAndClient() @@ -426,7 +419,6 @@ func TestHandleUpdate(t *testing.T) { }, } - // Add mock for s3Factory and client testUtils := TestUtils.NewTestUtils() testUtils.SetupMockedS3FactoryAndClient() @@ -522,10 +514,10 @@ func TestHandleUpdate(t *testing.T) { t.Run("secret have changed", func(t *testing.T) { // Call Reconcile function req := ctrl.Request{NamespacedName: types.NamespacedName{Name: s3UserResource.Name, Namespace: s3UserResource.Namespace}} - reconciler.Reconcile(context.TODO(), req) - + _, err := reconciler.Reconcile(context.TODO(), req) + assert.NoError(t, err) secretCreated := &corev1.Secret{} - err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ + err = testUtils.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "default", Name: "existing-valid-user-secret", }, secretCreated) @@ -585,5 +577,4 @@ func TestHandleUpdate(t *testing.T) { assert.NoError(t, err) }) }) - } diff --git a/internal/controller/user/utils.go b/internal/controller/user/utils.go index f53007d..0217a75 100644 --- a/internal/controller/user/utils.go +++ b/internal/controller/user/utils.go @@ -95,19 +95,19 @@ func (r *S3UserReconciler) getUserLinkedSecrets( // handled differently. uid := userResource.GetUID() - var secretConfiguredName string = userResource.Spec.SecretName - var secretDefaultName string = userResource.Name + var secretConfiguredName = userResource.Spec.SecretName + var secretDefaultName = userResource.Name var notOwnedConfiguredSecret *corev1.Secret // cmp.Or takes the first non "zero" value, see https://pkg.go.dev/cmp#Or for _, secret := range secretsList.Items { - var secretLinked = false + secretLinked := false for _, ref := range secret.OwnerReferences { if ref.UID == uid { userOwnedSecretList = append(userOwnedSecretList, secret) secretLinked = true } } - if (!secretLinked) { + if !secretLinked { if secret.Name == secretConfiguredName { notOwnedConfiguredSecret = &secret } else if secret.Name == secretDefaultName && notOwnedConfiguredSecret == nil { @@ -143,12 +143,12 @@ func (r *S3UserReconciler) newSecretForCR( // Reusing the S3User's labels and annotations labels := map[string]string{} labels["app.kubernetes.io/created-by"] = "s3-operator" - for k, v := range userResource.ObjectMeta.Labels { + for k, v := range userResource.Labels { labels[k] = v } annotations := map[string]string{} - for k, v := range userResource.ObjectMeta.Annotations { + for k, v := range userResource.Annotations { annotations[k] = v } @@ -172,5 +172,4 @@ func (r *S3UserReconciler) newSecretForCR( } return secret, nil - } diff --git a/internal/helpers/S3instance_test.go b/internal/helpers/S3instance_test.go index 678416a..fab36b9 100644 --- a/internal/helpers/S3instance_test.go +++ b/internal/helpers/S3instance_test.go @@ -35,15 +35,15 @@ import ( ) func TestGetS3ClientForRessource(t *testing.T) { - // Set up a logger before running tests log.SetLogger(zap.New(zap.UseDevMode(true))) // Register the custom resource with the scheme sch := runtime.NewScheme() s := scheme.Scheme - s3v1alpha1.AddToScheme(s) - corev1.AddToScheme(s) - + err := s3v1alpha1.AddToScheme(s) + assert.NoError(t, err) + err = corev1.AddToScheme(s) + assert.NoError(t, err) s3Instance := &s3v1alpha1.S3Instance{ Spec: s3v1alpha1.S3InstanceSpec{ AllowedNamespaces: []string{"default", "test-*", "*-namespace", "*allowed*"}, @@ -122,8 +122,10 @@ func TestGetS3ClientFromS3instance(t *testing.T) { // Register the custom resource with the scheme sch := runtime.NewScheme() s := scheme.Scheme - s3v1alpha1.AddToScheme(s) - corev1.AddToScheme(s) + err := s3v1alpha1.AddToScheme(s) + assert.NoError(t, err) + err = corev1.AddToScheme(s) + assert.NoError(t, err) s3Instance := &s3v1alpha1.S3Instance{ Spec: s3v1alpha1.S3InstanceSpec{ diff --git a/internal/helpers/controller.go b/internal/helpers/controller.go index a243ada..aea661c 100644 --- a/internal/helpers/controller.go +++ b/internal/helpers/controller.go @@ -29,8 +29,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/reconcile" ) -type ControllerHelper struct { -} +type ControllerHelper struct{} func NewControllerHelper() *ControllerHelper { return &ControllerHelper{} @@ -54,7 +53,7 @@ func (c *ControllerHelper) SetReconciledCondition( var changed bool if err != nil { - logger.Error(err, message, "NamespacedName", req.NamespacedName.String()) + logger.Error(err, message, "NamespacedName", req.Namespace) changed = meta.SetStatusCondition( conditions, metav1.Condition{ @@ -66,7 +65,7 @@ func (c *ControllerHelper) SetReconciledCondition( }, ) } else { - logger.Info(message, "NamespacedName", req.NamespacedName.String()) + logger.Info(message, "NamespacedName", req.Namespace) changed = meta.SetStatusCondition( conditions, metav1.Condition{ @@ -81,7 +80,7 @@ func (c *ControllerHelper) SetReconciledCondition( if changed { if errStatusUpdate := statusWriter.Update(ctx, resource); errStatusUpdate != nil { - logger.Error(errStatusUpdate, "Failed to update resource status", "ObjectKind", resource.GetObjectKind(), "NamespacedName", req.NamespacedName.String()) + logger.Error(errStatusUpdate, "Failed to update resource status", "ObjectKind", resource.GetObjectKind(), "NamespacedName", req.Namespace) return reconcile.Result{}, errStatusUpdate } } diff --git a/internal/helpers/controller_test.go b/internal/helpers/controller_test.go index a71c4db..1f56e09 100644 --- a/internal/helpers/controller_test.go +++ b/internal/helpers/controller_test.go @@ -37,7 +37,6 @@ import ( ) func TestSetReconciledCondition(t *testing.T) { - log.SetLogger(zap.New(zap.UseDevMode(true))) s3instanceResource := &s3v1alpha1.S3Instance{ @@ -59,14 +58,14 @@ func TestSetReconciledCondition(t *testing.T) { DeletionTimestamp: &metav1.Time{Time: time.Now()}, }, } - testUtils := testUtils.NewTestUtils() - testUtils.SetupClient([]client.Object{s3instanceResource}) + tu := testUtils.NewTestUtils() + tu.SetupClient([]client.Object{s3instanceResource}) controllerHelper := helpers.NewControllerHelper() t.Run("no error", func(t *testing.T) { _, err := controllerHelper.SetReconciledCondition( context.TODO(), - testUtils.Client.Status(), + tu.Client.Status(), ctrl.Request{NamespacedName: types.NamespacedName{Name: s3instanceResource.Name, Namespace: s3instanceResource.Namespace}}, s3instanceResource, &s3instanceResource.Status.Conditions, @@ -80,7 +79,7 @@ func TestSetReconciledCondition(t *testing.T) { t.Run("ressource status have changed", func(t *testing.T) { s3instanceResourceUpdated := &s3v1alpha1.S3Instance{} - err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ + err := tu.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "s3-operator", Name: "default", }, s3instanceResourceUpdated) @@ -92,7 +91,7 @@ func TestSetReconciledCondition(t *testing.T) { t.Run("with error", func(t *testing.T) { _, err := controllerHelper.SetReconciledCondition( context.TODO(), - testUtils.Client.Status(), + tu.Client.Status(), ctrl.Request{NamespacedName: types.NamespacedName{Name: s3instanceResource.Name, Namespace: s3instanceResource.Namespace}}, s3instanceResource, &s3instanceResource.Status.Conditions, @@ -103,12 +102,11 @@ func TestSetReconciledCondition(t *testing.T) { ) assert.NotNil(t, err) - }) t.Run("ressource status have changed", func(t *testing.T) { s3instanceResourceUpdated := &s3v1alpha1.S3Instance{} - err := testUtils.Client.Get(context.TODO(), client.ObjectKey{ + err := tu.Client.Get(context.TODO(), client.ObjectKey{ Namespace: "s3-operator", Name: "default", }, s3instanceResourceUpdated) diff --git a/internal/helpers/password_generator.go b/internal/helpers/password_generator.go index b46adcc..5a8ed4f 100644 --- a/internal/helpers/password_generator.go +++ b/internal/helpers/password_generator.go @@ -23,8 +23,7 @@ import ( "strings" ) -type PasswordGenerator struct { -} +type PasswordGenerator struct{} func NewPasswordGenerator() *PasswordGenerator { return &PasswordGenerator{} diff --git a/internal/helpers/s3instance.go b/internal/helpers/s3instance.go index c74ebe7..2c16451 100644 --- a/internal/helpers/s3instance.go +++ b/internal/helpers/s3instance.go @@ -34,8 +34,8 @@ import ( func (s3InstanceHelper *S3InstanceHelper) GetS3ClientForRessource( ctx context.Context, - client client.Client, - s3factory s3factory.S3Factory, + cl client.Client, + s3Factory s3factory.S3Factory, ressourceName string, ressourceNamespace string, ressourceS3InstanceRef string, @@ -44,12 +44,11 @@ func (s3InstanceHelper *S3InstanceHelper) GetS3ClientForRessource( logger.Info(fmt.Sprintf("Resource refer to s3Instance %s", ressourceS3InstanceRef)) s3InstanceInfo := s3InstanceHelper.GetS3InstanceRefInfo(ressourceS3InstanceRef, ressourceNamespace) s3Instance := &s3v1alpha1.S3Instance{} - err := client.Get( + err := cl.Get( ctx, types.NamespacedName{Namespace: s3InstanceInfo.namespace, Name: s3InstanceInfo.name}, s3Instance, ) - if err != nil { if k8sapierrors.IsNotFound(err) { return nil, fmt.Errorf("S3Instance %s not found", s3InstanceInfo.name) @@ -73,18 +72,18 @@ func (s3InstanceHelper *S3InstanceHelper) GetS3ClientForRessource( return nil, fmt.Errorf("S3instance is not in a ready state") } - return s3InstanceHelper.GetS3ClientFromS3Instance(ctx, client, s3factory, s3Instance) + return s3InstanceHelper.GetS3ClientFromS3Instance(ctx, cl, s3Factory, s3Instance) } func (s3InstanceHelper *S3InstanceHelper) GetS3ClientFromS3Instance( ctx context.Context, - client client.Client, - s3factory s3factory.S3Factory, + cl client.Client, + s3Factory s3factory.S3Factory, s3InstanceResource *s3v1alpha1.S3Instance, ) (s3client.S3Client, error) { logger := log.FromContext(ctx) - s3InstanceSecretSecret, err := s3InstanceHelper.getS3InstanceAccessSecret(ctx, client, s3InstanceResource) + s3InstanceSecretSecret, err := s3InstanceHelper.getS3InstanceAccessSecret(ctx, cl, s3InstanceResource) if err != nil { logger.Error( err, @@ -99,7 +98,7 @@ func (s3InstanceHelper *S3InstanceHelper) GetS3ClientFromS3Instance( var s3InstanceCaCertificates []string if s3InstanceResource.Spec.CaCertSecretRef != "" { - s3InstanceCaCertSecret, err := s3InstanceHelper.getS3InstanceCaCertSecret(ctx, client, s3InstanceResource) + s3InstanceCaCertSecret, err := s3InstanceHelper.getS3InstanceCaCertSecret(ctx, cl, s3InstanceResource) if err != nil { logger.Error( err, @@ -133,16 +132,16 @@ func (s3InstanceHelper *S3InstanceHelper) GetS3ClientFromS3Instance( PathDeletionEnabled: s3InstanceResource.Spec.PathDeletionEnabled, } - return s3factory.GenerateS3Client(s3Config.S3Provider, s3Config) + return s3Factory.GenerateS3Client(s3Config.S3Provider, s3Config) } func (s3InstanceHelper *S3InstanceHelper) getS3InstanceAccessSecret( ctx context.Context, - client client.Client, + cl client.Client, s3InstanceResource *s3v1alpha1.S3Instance, ) (corev1.Secret, error) { s3InstanceSecret := &corev1.Secret{} - err := client.Get( + err := cl.Get( ctx, types.NamespacedName{ Namespace: s3InstanceResource.Namespace, @@ -165,7 +164,7 @@ func (s3InstanceHelper *S3InstanceHelper) getS3InstanceAccessSecret( func (s3InstanceHelper *S3InstanceHelper) getS3InstanceCaCertSecret( ctx context.Context, - client client.Client, + cl client.Client, s3InstanceResource *s3v1alpha1.S3Instance, ) (corev1.Secret, error) { logger := log.FromContext(ctx) @@ -177,7 +176,7 @@ func (s3InstanceHelper *S3InstanceHelper) getS3InstanceCaCertSecret( return *s3InstanceCaCertSecret, nil } - err := client.Get( + err := cl.Get( ctx, types.NamespacedName{ Namespace: s3InstanceResource.Namespace, @@ -247,8 +246,7 @@ func (s3InstanceInfo S3InstanceInfo) Equal(s3InstanceInfoName string, s3Instance return s3InstanceInfo.name == s3InstanceInfoName && s3InstanceInfo.namespace == s3InstanceInfoNamespace } -type S3InstanceHelper struct { -} +type S3InstanceHelper struct{} func NewS3InstanceHelper() *S3InstanceHelper { return &S3InstanceHelper{} diff --git a/internal/s3/client/impl/minioS3Client.go b/internal/s3/client/impl/minioS3Client.go index 7e54f7d..d311b6c 100644 --- a/internal/s3/client/impl/minioS3Client.go +++ b/internal/s3/client/impl/minioS3Client.go @@ -28,7 +28,7 @@ import ( "strings" s3client "github.com/InseeFrLab/s3-operator/internal/s3/client" - "github.com/minio/madmin-go/v3" + "github.com/minio/madmin-go/v4" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ctrl "sigs.k8s.io/controller-runtime" @@ -36,15 +36,17 @@ import ( type MinioS3Client struct { s3Config s3client.S3Config - client minio.Client - adminClient madmin.AdminClient + client *minio.Client + adminClient *madmin.AdminClient } func NewMinioS3Client(S3Config *s3client.S3Config) (*MinioS3Client, error) { s3Logger := ctrl.Log.WithValues("logger", "s3clientimplminio") s3Logger.Info("creating minio clients (regular and admin)") + minioClient, err := generateMinioClient( - S3Config.S3Url, + S3Config.Endpoint, + S3Config.Secure, S3Config.AccessKey, S3Config.SecretKey, S3Config.Region, @@ -55,7 +57,8 @@ func NewMinioS3Client(S3Config *s3client.S3Config) (*MinioS3Client, error) { return nil, err } adminClient, err := generateAdminMinioClient( - S3Config.S3Url, + S3Config.Endpoint, + S3Config.Secure, S3Config.AccessKey, S3Config.SecretKey, S3Config.CaCertificatesBase64, @@ -64,22 +67,18 @@ func NewMinioS3Client(S3Config *s3client.S3Config) (*MinioS3Client, error) { s3Logger.Error(err, "an error occurred while creating a new minio admin client") return nil, err } - return &MinioS3Client{*S3Config, *minioClient, *adminClient}, nil + return &MinioS3Client{*S3Config, minioClient, adminClient}, nil } func generateMinioClient( - url string, + endpoint string, + isSSL bool, accessKey string, secretKey string, region string, caCertificates []string, ) (*minio.Client, error) { s3Logger := ctrl.Log.WithValues("logger", "s3clientimplminio") - endpoint, isSSL, err := constructEndpointFromURL(url) - if err != nil { - s3Logger.Error(err, "an error occurred while creating a new minio client") - return nil, err - } minioOptions := &minio.Options{ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), @@ -100,17 +99,13 @@ func generateMinioClient( } func generateAdminMinioClient( - url string, + endpoint string, + isSSL bool, accessKey string, secretKey string, caCertificates []string, ) (*madmin.AdminClient, error) { s3Logger := ctrl.Log.WithValues("logger", "s3clientimplminio") - endpoint, isSSL, err := constructEndpointFromURL(url) - if err != nil { - s3Logger.Error(err, "an error occurred while creating a new minio admin client") - return nil, err - } minioOptions := &madmin.Options{ Creds: credentials.NewStaticV4(accessKey, secretKey, ""), @@ -130,15 +125,15 @@ func generateAdminMinioClient( return minioAdminClient, nil } -func constructEndpointFromURL(url string) (string, bool, error) { +func ConstructEndpointFromURL(url string) (string, bool, error) { parsedURL, err := neturl.Parse(url) if err != nil { return "", false, fmt.Errorf("cannot detect if url use ssl or not") } - var endpoint = parsedURL.Hostname() - if !((parsedURL.Scheme == "https" && parsedURL.Port() == "443") || - (parsedURL.Scheme == "http" && parsedURL.Port() == "80")) { + endpoint := parsedURL.Hostname() + if (parsedURL.Scheme != "https" || parsedURL.Port() != "443") && + (parsedURL.Scheme != "http" || parsedURL.Port() != "80") { endpoint = fmt.Sprintf("%s:%s", endpoint, parsedURL.Port()) } @@ -173,7 +168,6 @@ func addTlsClientConfigToMinioAdminOptions(caCertificates []string, minioOptions // caCertificateEncoded := base64.StdEncoding.EncodeToString(caCertificateAsByte) // rootCAs.AppendCertsFromPEM([]byte(caCertificateEncoded)) rootCAs.AppendCertsFromPEM([]byte(caCertificate)) - } minioOptions.Transport = &http.Transport{ @@ -259,7 +253,6 @@ func (minioS3Client *MinioS3Client) PathExists(bucketname string, path string) ( bucketname, "/"+path+"/"+".keep", minio.StatObjectOptions{}) - if err != nil { if minio.ToErrorResponse(err).StatusCode == 404 { // fmt.Println("The path does not exist") @@ -307,26 +300,24 @@ func (minioS3Client *MinioS3Client) GetQuota(name string) (int64, error) { if err != nil { s3Logger.Error(err, "error while getting quota on bucket", "bucket", name) } - return int64(bucketQuota.Quota), err + return int64(bucketQuota.Size), err } func (minioS3Client *MinioS3Client) SetQuota(name string, quota int64) error { s3Logger := ctrl.Log.WithValues("logger", "s3clientimplminio") s3Logger.Info("setting quota on bucket", "bucket", name, "quotaToSet", quota) - minioS3Client.adminClient.SetBucketQuota( + err := minioS3Client.adminClient.SetBucketQuota( context.Background(), name, - &madmin.BucketQuota{Quota: uint64(quota), Type: madmin.HardQuota}, + &madmin.BucketQuota{Size: uint64(quota), Type: madmin.HardQuota}, ) - return nil + return err } -//////////////////// +// ////////////////// // Policy methods // -//////////////////// - +// ////////////////// // Note regarding the implementation of policy existence check - // No method exposed by the madmin client is truly satisfying to test the existence of a policy // - InfoCannedPolicyV2 returns an error if the policy does not exist (as opposed to BucketExists, // for instance, see https://github.com/minio/minio-go/blob/v7.0.52/api-stat.go#L43-L45) @@ -341,8 +332,7 @@ func (minioS3Client *MinioS3Client) GetPolicyInfo(name string) (*madmin.PolicyIn s3Logger := ctrl.Log.WithValues("logger", "s3clientimplminio") s3Logger.Info("retrieving policy info", "policy", name) - policy, err := minioS3Client.adminClient.InfoCannedPolicyV2(context.Background(), name) - + policy, err := minioS3Client.adminClient.InfoCannedPolicy(context.Background(), name) if err != nil { // Not ideal (breaks if error nomenclature changes), but still // better than testing the error message as we did before @@ -389,10 +379,7 @@ func (minioS3Client *MinioS3Client) DeletePolicy(name string) error { return minioS3Client.adminClient.RemoveCannedPolicy(context.Background(), name) } -//////////////////// -// USER methods // -//////////////////// - +// USER methods func (minioS3Client *MinioS3Client) CreateUser(accessKey string, secretKey string) error { s3Logger := ctrl.Log.WithValues("logger", "s3clientimplminio") s3Logger.Info("Creating user", "accessKey", accessKey) @@ -427,7 +414,6 @@ func (minioS3Client *MinioS3Client) AddServiceAccountForUser( } return nil - } func (minioS3Client *MinioS3Client) UserExist(accessKey string) (bool, error) { @@ -483,9 +469,9 @@ func (minioS3Client *MinioS3Client) CheckUserCredentialsValid( ) (bool, error) { s3Logger := ctrl.Log.WithValues("logger", "s3clientimplminio") s3Logger.Info("Check credentials for user", "user", name, "accessKey", accessKey) - minioTestClient, err := generateMinioClient( - minioS3Client.s3Config.S3Url, + minioS3Client.s3Config.Endpoint, + minioS3Client.s3Config.Secure, accessKey, secretKey, minioS3Client.s3Config.Region, @@ -498,7 +484,8 @@ func (minioS3Client *MinioS3Client) CheckUserCredentialsValid( _, err = minioTestClient.ListBuckets(context.Background()) if err != nil { errAsResponse := minio.ToErrorResponse(err) - if errAsResponse.Code == "SignatureDoesNotMatch" { + switch errAsResponse.Code { + case "SignatureDoesNotMatch": s3Logger.Info( "the user credentials appear to be invalid", "accessKey", @@ -507,10 +494,10 @@ func (minioS3Client *MinioS3Client) CheckUserCredentialsValid( errAsResponse, ) return false, nil - } else if errAsResponse.Code == "InvalidAccessKeyId" { + case "InvalidAccessKeyId": s3Logger.Info("this accessKey does not exist on the s3 backend", "accessKey", accessKey, "s3BackendError", errAsResponse) return false, nil - } else { + default: s3Logger.Error(err, "an error occurred while checking if the S3 user's credentials were valid", "accessKey", accessKey, "code", errAsResponse.Code) return false, err } @@ -531,7 +518,6 @@ func (minioS3Client *MinioS3Client) RemovePoliciesFromUser( } _, err := minioS3Client.adminClient.DetachPolicy(context.Background(), opts) - if err != nil { errAsResp := madmin.ToErrorResponse(err) if errAsResp.Code == "XMinioAdminPolicyChangeAlreadyApplied" { diff --git a/internal/s3/client/impl/mockedS3Client.go b/internal/s3/client/impl/mockedS3Client.go index ea79403..31adb0f 100644 --- a/internal/s3/client/impl/mockedS3Client.go +++ b/internal/s3/client/impl/mockedS3Client.go @@ -18,7 +18,7 @@ package s3clientimpl import ( s3client "github.com/InseeFrLab/s3-operator/internal/s3/client" - "github.com/minio/madmin-go/v3" + "github.com/minio/madmin-go/v4" ctrl "sigs.k8s.io/controller-runtime" ) @@ -26,6 +26,8 @@ type MockedS3Client struct { s3Config s3client.S3Config } +var _ s3client.S3Client = (*MockedS3Client)(nil) + func (mockedS3Provider *MockedS3Client) BucketExists(name string) (bool, error) { s3Logger := ctrl.Log.WithValues("logger", "s3ClientImplMocked") s3Logger.Info("checking bucket existence", "bucket", name) @@ -154,6 +156,6 @@ func (mockedS3Provider *MockedS3Client) GetConfig() *s3client.S3Config { return &mockedS3Provider.s3Config } -func NewMockedS3Client() *MockedS3Client { - return &MockedS3Client{s3Config: s3client.S3Config{}} +func NewMockedS3Client(s3Config *s3client.S3Config) *MockedS3Client { + return &MockedS3Client{*s3Config} } diff --git a/internal/s3/client/s3client.go b/internal/s3/client/s3client.go index cc48515..179ba79 100644 --- a/internal/s3/client/s3client.go +++ b/internal/s3/client/s3client.go @@ -17,7 +17,7 @@ limitations under the License. package s3client import ( - "github.com/minio/madmin-go/v3" + "github.com/minio/madmin-go/v4" ) type S3Config struct { @@ -32,6 +32,9 @@ type S3Config struct { S3UserDeletionEnabled bool PathDeletionEnabled bool PolicyDeletionEnabled bool + Secure bool + Endpoint string + PathStyle bool } type S3Client interface { diff --git a/internal/s3/factory/impl/s3factoryImpl.go b/internal/s3/factory/impl/s3factoryImpl.go index 8cca6fe..3eaa78e 100644 --- a/internal/s3/factory/impl/s3factoryImpl.go +++ b/internal/s3/factory/impl/s3factoryImpl.go @@ -21,18 +21,27 @@ import ( s3client "github.com/InseeFrLab/s3-operator/internal/s3/client" s3clientImpl "github.com/InseeFrLab/s3-operator/internal/s3/client/impl" + ctrl "sigs.k8s.io/controller-runtime" ) -type S3Factory struct { -} +type S3Factory struct{} func NewS3Factory() *S3Factory { return &S3Factory{} } func (mockedS3Provider *S3Factory) GenerateS3Client(s3Provider string, s3Config *s3client.S3Config) (s3client.S3Client, error) { + s3Logger := ctrl.Log.WithValues("logger", "s3factory") + endpoint, isSSL, err := s3clientImpl.ConstructEndpointFromURL(s3Config.S3Url) + if err != nil { + s3Logger.Error(err, "an error occurred while creating a new minio client") + return nil, err + } + s3Config.Secure = isSSL + s3Config.Endpoint = endpoint + s3Config.PathStyle = false if s3Provider == "mockedS3Provider" { - return s3clientImpl.NewMockedS3Client(), nil + return s3clientImpl.NewMockedS3Client(s3Config), nil } if s3Provider == "minio" { return s3clientImpl.NewMinioS3Client(s3Config) diff --git a/test/mocks/S3FactoryMock.go b/test/mocks/S3FactoryMock.go index 60adc2a..c21d8ba 100644 --- a/test/mocks/S3FactoryMock.go +++ b/test/mocks/S3FactoryMock.go @@ -31,7 +31,8 @@ func NewMockedS3ClientFactory() *MockedS3ClientFactory { return &MockedS3ClientFactory{} } -func (m *MockedS3ClientFactory) GenerateS3Client(s3Provider string, s3Config *s3client.S3Config) (s3client.S3Client, error) { +func (m *MockedS3ClientFactory) GenerateS3Client(s3Provider string, + s3Config *s3client.S3Config) (s3client.S3Client, error) { args := m.Called(s3Provider, s3Config) return args.Get(0).(s3client.S3Client), args.Error(1) } diff --git a/test/mocks/mockedS3Client.go b/test/mocks/mockedS3Client.go index 8418004..79f6780 100644 --- a/test/mocks/mockedS3Client.go +++ b/test/mocks/mockedS3Client.go @@ -18,7 +18,7 @@ package mocks import ( s3client "github.com/InseeFrLab/s3-operator/internal/s3/client" - "github.com/minio/madmin-go/v3" + "github.com/minio/madmin-go/v4" "github.com/stretchr/testify/mock" ctrl "sigs.k8s.io/controller-runtime" ) @@ -28,6 +28,8 @@ type MockedS3Client struct { mock.Mock } +var _ s3client.S3Client = (*MockedS3Client)(nil) + func (mockedS3Provider *MockedS3Client) BucketExists(name string) (bool, error) { s3Logger := ctrl.Log.WithValues("logger", "mockedS3Client") s3Logger.Info("checking bucket existence", "bucket", name) @@ -92,7 +94,6 @@ func (mockedS3Provider *MockedS3Client) GetPolicyInfo(name string) (*madmin.Poli return nil, args.Error(1) } return args.Get(0).(*madmin.PolicyInfo), args.Error(1) - } func (mockedS3Provider *MockedS3Client) CreateOrUpdatePolicy(name string, content string) error { @@ -197,6 +198,6 @@ func (mockedS3Provider *MockedS3Client) GetConfig() *s3client.S3Config { return &mockedS3Provider.s3Config } -func NewMockedS3Client() *MockedS3Client { - return &MockedS3Client{s3Config: s3client.S3Config{}} +func NewMockedS3Client(s3Config s3client.S3Config) *MockedS3Client { + return &MockedS3Client{s3Config: s3Config} } diff --git a/test/utils/testUtils.go b/test/utils/testUtils.go index b899496..27365ef 100644 --- a/test/utils/testUtils.go +++ b/test/utils/testUtils.go @@ -23,7 +23,7 @@ import ( s3client "github.com/InseeFrLab/s3-operator/internal/s3/client" s3factory "github.com/InseeFrLab/s3-operator/internal/s3/factory" "github.com/InseeFrLab/s3-operator/test/mocks" - "github.com/minio/madmin-go/v3" + "github.com/minio/madmin-go/v4" "github.com/stretchr/testify/mock" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -42,7 +42,13 @@ func NewTestUtils() *TestUtils { } func (t *TestUtils) SetupMockedS3FactoryAndClient() { - mockedS3Client := mocks.NewMockedS3Client() + + mockedS3Client := mocks.NewMockedS3Client(s3client.S3Config{ + Region: "us-east-1", + S3Url: "https://minio.example.com", + Secure: true, + Endpoint: "minio.example.com", + }) mockedS3Client.On("BucketExists", "test-bucket").Return(false, nil) mockedS3Client.On("BucketExists", "existing-bucket").Return(true, nil) mockedS3Client.On("CreateBucket", "test-bucket").Return(nil) @@ -66,7 +72,8 @@ func (t *TestUtils) SetupMockedS3FactoryAndClient() { } ] }`) - mockedS3Client.On("GetPolicyInfo", "existing-policy").Return(&madmin.PolicyInfo{PolicyName: "existing-policy", Policy: existingPolicy}, nil) + mockedS3Client.On("GetPolicyInfo", "existing-policy").Return( + &madmin.PolicyInfo{PolicyName: "existing-policy", Policy: existingPolicy}, nil) mockedS3Client.On("CreateOrUpdatePolicy", "existing-policy", mock.AnythingOfType("string")).Return(nil) mockedS3Client.On("CreateOrUpdatePolicy", "example-policy", "").Return(nil) mockedS3Client.On("PathExists", "existing-bucket", "mypath").Return(false, nil) @@ -79,10 +86,29 @@ func (t *TestUtils) SetupMockedS3FactoryAndClient() { mockedS3Client.On("CreateUser", "existing-valid-user", mock.AnythingOfType("string")).Return(nil) mockedS3Client.On("AddPoliciesToUser", "existing-valid-user", mock.AnythingOfType("[]string")).Return(nil) - mockedS3Client.On("CheckUserCredentialsValid", "existing-valid-user", "existing-valid-user", "invalidSecret").Return(false, nil) - mockedS3Client.On("CheckUserCredentialsValid", "existing-valid-user", "existing-valid-user", "invalidSecret").Return(false, nil) - mockedS3Client.On("CheckUserCredentialsValid", "existing-valid-user", "existing-valid-user", "validSecret").Return(true, nil) - mockedS3Client.On("CheckUserCredentialsValid", "existing-valid-user", "existing-valid-user", mock.AnythingOfType("string")).Return(true, nil) + mockedS3Client.On( + "CheckUserCredentialsValid", + "existing-valid-user", + "existing-valid-user", + "invalidSecret", + ).Return(false, nil) + mockedS3Client.On( + "CheckUserCredentialsValid", + "existing-valid-user", + "existing-valid-user", + "invalidSecret", + ).Return(false, nil) + mockedS3Client.On("CheckUserCredentialsValid", + "existing-valid-user", + "existing-valid-user", + "validSecret", + ).Return(true, nil) + mockedS3Client.On( + "CheckUserCredentialsValid", + "existing-valid-user", + "existing-valid-user", + mock.AnythingOfType("string"), + ).Return(true, nil) mockedS3Client.On("GetQuota", "existing-bucket").Return(10, nil) mockedS3Client.On("GetQuota", "existing-invalid-bucket").Return(10, nil) mockedS3Client.On("SetQuota", "existing-invalid-bucket", int64(100)).Return(nil) @@ -92,12 +118,12 @@ func (t *TestUtils) SetupMockedS3FactoryAndClient() { mockedS3Client.On("PathExists", "existing-invalid-bucket", "non-existing").Return(false, nil) mockedS3Client.On("BucketExists", "existing-invalid-bucket").Return(true, nil) mockedS3Client.On("BucketExists", "non-existing-bucket").Return(false, nil) - + mockedS3Client.On("GetConfig").Return(&s3client.S3Config{S3Url: "https://minio.example.com"}) mockedS3Client.On("CreatePath", "existing-invalid-bucket", "non-existing").Return(nil) mockedS3Client.On("DeleteUser", "existing-valid-user").Return(nil) - mockedInvalidS3Client := mocks.NewMockedS3Client() + mockedInvalidS3Client := mocks.NewMockedS3Client(s3client.S3Config{}) mockedInvalidS3Client.On("BucketExists", "test-bucket").Return(false, nil) mockedInvalidS3Client.On("CreateBucket", "test-bucket").Return(nil) mockedInvalidS3Client.On("SetQuota", "test-bucket", int64(10)).Return(nil) @@ -175,14 +201,19 @@ func (t *TestUtils) GenerateBasicS3InstanceAndSecret() (*s3v1alpha1.S3Instance, func (t *TestUtils) SetupClient(objects []client.Object) { // Register the custom resource with the scheme sch := runtime.NewScheme() s := scheme.Scheme - s3v1alpha1.AddToScheme(s) - corev1.AddToScheme(s) - - client := fake.NewClientBuilder(). + err := s3v1alpha1.AddToScheme(s) + if err != nil { + panic(err) + } + err = corev1.AddToScheme(s) + if err != nil { + panic(err) + } + cl := fake.NewClientBuilder(). WithScheme(s). WithObjects(objects...). WithStatusSubresource(objects...). Build() - t.Client = client + t.Client = cl }