diff --git a/.github/workflows/releases.yaml b/.github/workflows/releases.yaml index 2bd3305..d9c685f 100644 --- a/.github/workflows/releases.yaml +++ b/.github/workflows/releases.yaml @@ -11,7 +11,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.25' id: go - name: Check out code into the Go module directory uses: actions/checkout@v2 @@ -30,4 +30,4 @@ jobs: uses: fnkr/github-action-ghr@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - GHR_PATH: bin/manager \ No newline at end of file + GHR_PATH: bin/manager diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 9a29a2b..5c3cc6e 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,11 +11,11 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.21' + go-version: '1.25' id: go - name: Check out code into the Go module directory uses: actions/checkout@v1 - name: Build run: make all - name: Test - run: make test \ No newline at end of file + run: make test diff --git a/.golangci.yaml b/.golangci.yaml index a5ac62d..b4b8267 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,8 +1,27 @@ +version: "2" linters: enable: - revive -linters-settings: - revive: - rules: - - name: dot-imports - disabled: true \ No newline at end of file + settings: + revive: + rules: + - name: dot-imports + disabled: true + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index 5b6ce47..376fdd2 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # 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.27.1 +ENVTEST_K8S_VERSION = 1.31.0 # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) @@ -29,7 +29,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. @@ -56,22 +56,26 @@ generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and fmt: ## Run go fmt against code. go fmt ./... -.PHONY: lint -lint: golangci ## Run golangci - $(LOCALBIN)/golangci-lint run - -.PHONY: gen-doc -gen-doc: crdoc ## Run golangci - $(LOCALBIN)/crdoc --resources config/crd/bases/ --output DOC.md - .PHONY: vet vet: ## Run go vet against code. go vet ./... +.PHONY: setup-kubebuilder-assets +setup-kubebuilder-assets: + $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path + .PHONY: test test: manifests generate fmt vet envtest lint ## Run tests. KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test ./... -coverprofile cover.out +.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 + ##@ Build .PHONY: build @@ -82,8 +86,8 @@ 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. @@ -93,12 +97,12 @@ docker-build: test ## Build docker image with the manager. docker-push: ## Push docker image with the manager. $(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 @@ -110,15 +114,11 @@ docker-buildx: test ## Build and push docker image for the manager for cross-pla - $(CONTAINER_TOOL) buildx rm project-v3-builder rm Dockerfile.cross -HELMIFY ?= $(LOCALBIN)/helmify - -.PHONY: helmify -helmify: $(HELMIFY) ## Download helmify locally if necessary. -$(HELMIFY): $(LOCALBIN) - test -s $(LOCALBIN)/helmify || GOBIN=$(LOCALBIN) go install github.com/arttor/helmify/cmd/helmify@latest - -helm: manifests kustomize helmify - $(KUSTOMIZE) build config/default | $(HELMIFY) +.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 @@ -155,56 +155,46 @@ KUBECTL ?= kubectl KUSTOMIZE ?= $(LOCALBIN)/kustomize CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen ENVTEST ?= $(LOCALBIN)/setup-envtest -KIND ?= $(LOCALBIN)/kind -KINDCLUSTER ?= ndb -KINDCONFIG ?= $(shell pwd)/.kubecfg +GOLANGCI_LINT = $(LOCALBIN)/golangci-lint ## Tool Versions -KUSTOMIZE_VERSION ?= v5.0.1 -CONTROLLER_TOOLS_VERSION ?= v0.14.0 -KIND_VERSION ?= v0.20.0 +KUSTOMIZE_VERSION ?= v5.7.1 +CONTROLLER_TOOLS_VERSION ?= v0.19.0 +ENVTEST_VERSION ?= release-0.22 +GOLANGCI_LINT_VERSION ?= v2.6.2 .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 || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kustomize/kustomize/v5@$(KUSTOMIZE_VERSION) + $(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: 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 - -.PHONY: golangci -golangci: ## Run golangci - test -s $(LOCALBIN)/golangci-lint || GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest - -.PHONY: crdoc -crdoc: ## Run crdoc - test -s $(LOCALBIN)/crdoc|| GOBIN=$(LOCALBIN) go install fybrik.io/crdoc@v0.6.3 - -$(KIND): $(LOCALBIN) - test -s $(LOCALBIN)/kind || GOBIN=$(LOCALBIN) GO111MODULE=on go install sigs.k8s.io/kind@$(KIND_VERSION) - -.PHONY: kind-init -kind-init: $(KINDCONFIG) -$(KINDCONFIG): $(KIND) - $(KIND) create cluster --name $(KINDCLUSTER) --config hack/cluster.yml --kubeconfig $(KINDCONFIG) - -.PHONY: kind-load -kind-load: $(KINDCONFIG) docker-build - $(KIND) load docker-image $(IMG) --name $(KINDCLUSTER) - -.PHONY: kind-cleanup -kind-cleanup: $(KIND) - $(KIND) delete cluster --name $(KINDCLUSTER) - rm -f $(KINDCONFIG) + $(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 diff --git a/README.md b/README.md index cdd010a..b4eb377 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,22 @@ UnDeploy the controller from the cluster: make undeploy ``` +## Application manager client + + +Install the swagger code generator + +```sh +CGO_ENABLED=0 go install github.com/go-swagger/go-swagger/cmd/swagger@latest +``` + +Regenerate the client code + +```sh +cd ./internal +swagger generate client --spec ../app_manager_openapi.yml -m appmgr -c appmgrcli +``` + ## Contributing // TODO(user): Add detailed information on how you would like others to contribute to this project diff --git a/api/v1alpha1/applicationdisruptionbudget_types.go b/api/v1alpha1/applicationdisruptionbudget_types.go index 6d1a740..95e3955 100644 --- a/api/v1alpha1/applicationdisruptionbudget_types.go +++ b/api/v1alpha1/applicationdisruptionbudget_types.go @@ -42,10 +42,16 @@ type ApplicationDisruptionBudgetSpec struct { // It perform a POST http request containing the NodeDisruption that is being validated. // Maintenance will proceed only if the endpoint responds 2XX. // +kubebuilder:validation:Optional - HealthHook HealthHookSpec `json:"healthHook,omitempty"` + HealthHook HookSpec `json:"healthHook,omitempty"` + + // HookV2BasePath holds the base path for the prepare, ready, cancel hooks that will be + // called at different stages of the NodeDisruption lifecycle. + // A POST http request containing a Disruption that is being reconciled is sent ot each of the hooks. + // +kubebuilder:validation:Optional + HookV2BasePath HookSpec `json:"hookV2BasePath,omitempty"` } -type HealthHookSpec struct { +type HookSpec struct { // a PEM encoded CA bundle which will be used to validate the webhook's server certificate. If unspecified, system trust roots on the apiserver are used. CaBundle string `json:"caBundle,omitempty"` // URL that will be called by the hook, in standard URL form (`scheme://host:port/path`). diff --git a/api/v1alpha1/nodedisruption_types.go b/api/v1alpha1/nodedisruption_types.go index c02c3d0..fccc727 100644 --- a/api/v1alpha1/nodedisruption_types.go +++ b/api/v1alpha1/nodedisruption_types.go @@ -49,7 +49,16 @@ type NodeDisruptionSpec struct { // Label query over nodes that will be impacted by the disruption NodeSelector metav1.LabelSelector `json:"nodeSelector,omitempty"` - Retry RetrySpec `json:"retry,omitempty"` + + // StartDate when the disruption should start + StartDate metav1.Time `json:"startDate,omitempty"` + + // Duration of the disruption once granted + Duration metav1.Duration `json:"duration,omitempty"` + + // Retry configuration allows to retry a disruption after rejection until the given deadline + Retry RetrySpec `json:"retry,omitempty"` + // Type of the node disruption Type string `json:"type,omitempty"` } @@ -92,6 +101,7 @@ type NodeDisruptionStatus struct { type DisruptedBudgetStatus struct { Reference NamespacedName `json:"reference,omitempty"` Reason string `json:"reason"` + Preparing bool `json:"preparing"` Ok bool `json:"ok"` } diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 8ffcaf9..e6504f0 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -89,6 +89,7 @@ func (in *ApplicationDisruptionBudgetSpec) DeepCopyInto(out *ApplicationDisrupti in.PodSelector.DeepCopyInto(&out.PodSelector) in.PVCSelector.DeepCopyInto(&out.PVCSelector) out.HealthHook = in.HealthHook + out.HookV2BasePath = in.HookV2BasePath } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ApplicationDisruptionBudgetSpec. @@ -158,16 +159,16 @@ func (in *DisruptionBudgetStatus) DeepCopy() *DisruptionBudgetStatus { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *HealthHookSpec) DeepCopyInto(out *HealthHookSpec) { +func (in *HookSpec) DeepCopyInto(out *HookSpec) { *out = *in } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HealthHookSpec. -func (in *HealthHookSpec) DeepCopy() *HealthHookSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HookSpec. +func (in *HookSpec) DeepCopy() *HookSpec { if in == nil { return nil } - out := new(HealthHookSpec) + out := new(HookSpec) in.DeepCopyInto(out) return out } @@ -325,6 +326,8 @@ func (in *NodeDisruptionList) DeepCopyObject() runtime.Object { func (in *NodeDisruptionSpec) DeepCopyInto(out *NodeDisruptionSpec) { *out = *in in.NodeSelector.DeepCopyInto(&out.NodeSelector) + in.StartDate.DeepCopyInto(&out.StartDate) + out.Duration = in.Duration in.Retry.DeepCopyInto(&out.Retry) } diff --git a/app_manager_openapi.yml b/app_manager_openapi.yml new file mode 100644 index 0000000..6227065 --- /dev/null +++ b/app_manager_openapi.yml @@ -0,0 +1,145 @@ +swagger: "2.0" +info: + title: Swagger Node Distruption Client - OpenAPI 3.0 + description: |- + API to be implemented in the application manager, i.e. the client side of the Node Distruption logic. + version: 1.0.0 +host: localhost +schemes: + - https +externalDocs: + description: Find out more about Node Disruption v2 + url: https://github.com/criteo/node-disruption-controller +securityDefinitions: + Bearer: + type: apiKey + name: Authorization + in: header + description: JWT Token +security: [] +tags: + - name: disruption +definitions: + Disruption: + description: Disprution describes a disruption of nodes by a given source. + type: object + properties: + uid: + type: string + description: Unique ID for this disruption + example: f45ac82c-7571-4770-8de1-dfdb0bf598de + name: + type: string + description: Human readable identifier of the disruption + example: hw-maint-123 + type: + type: string + description: Type of disruption + example: decommission + nodes: + type: array + description: Nodes affected by this disruption + items: + type: string + example: + - hello.example.com + createdBy: + type: string + description: Source of the disruption + example: maintenance-manager + creationDate: + type: string + format: date-time + description: When the disruption was created + startDate: + type: string + format: date-time + description: Expected start date of the disruption + deadlineDate: + type: string + format: date-time + description: Start date + duration pre-calculated + duration: + type: integer + format: int64 + example: 3600 + description: For how long in seconds the disruption is expected to last after the start date. + Error: + description: Error details when available + type: string + example: "unable to contact internal tools" +parameters: + disruptionParam: + in: body + name: body + description: Disruption + schema: + $ref: '#/definitions/Disruption' +responses: + RetryAfter: + description: Cannot be processed right now, retry later / after Retry-After value + headers: + Retry-After: + description: Delay in seconds. + type: integer + UnexpectedError: + description: Unexpected error + schema: + $ref: "#/definitions/Error" +# securitySchemes: +# jwt: +# type: http +# scheme: bearer +# description: "Use JWT token provided as bearer token" +paths: + /prepare: + post: + summary: Prepare application for a disruption. + operationId: prepareApplication + tags: + - disruption + consumes: + - application/json + parameters: + - $ref: '#/parameters/disruptionParam' + responses: + '200': + description: Accepted, preparation planned. + '424': + description: Unable to accept due to internal constraints. + '425': + $ref: '#/responses/RetryAfter' + default: + $ref: '#/responses/UnexpectedError' + /ready: + post: + summary: Check application readiness for a disruption. + operationId: checkReadiness + tags: + - disruption + consumes: + - application/json + parameters: + - $ref: '#/parameters/disruptionParam' + responses: + '200': + description: Ready. + '425': + $ref: '#/responses/RetryAfter' + default: + $ref: '#/responses/UnexpectedError' + /cancel: + post: + summary: Cancel application preparation for a disruption. + operationId: cancelPreparation + tags: + - disruption + consumes: + - application/json + parameters: + - $ref: '#/parameters/disruptionParam' + responses: + '200': + description: Acknowledged. + default: + $ref: '#/responses/UnexpectedError' diff --git a/cmd/main.go b/cmd/main.go index 7832570..4011661 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -32,6 +32,8 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "sigs.k8s.io/controller-runtime/pkg/metrics/server" + "sigs.k8s.io/controller-runtime/pkg/webhook" nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1" "github.com/criteo/node-disruption-controller/internal/controller" @@ -80,8 +82,8 @@ func main() { mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ Scheme: scheme, - MetricsBindAddress: metricsAddr, - Port: 9443, + Metrics: server.Options{BindAddress: metricsAddr}, + WebhookServer: webhook.NewServer(webhook.Options{Port: 9443}), HealthProbeBindAddress: probeAddr, LeaderElection: enableLeaderElection, LeaderElectionID: "493e6e3e.criteo.com", diff --git a/config/crd/bases/nodedisruption.criteo.com_applicationdisruptionbudgets.yaml b/config/crd/bases/nodedisruption.criteo.com_applicationdisruptionbudgets.yaml index bd2f461..346c22f 100644 --- a/config/crd/bases/nodedisruption.criteo.com_applicationdisruptionbudgets.yaml +++ b/config/crd/bases/nodedisruption.criteo.com_applicationdisruptionbudgets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: applicationdisruptionbudgets.nodedisruption.criteo.com spec: group: nodedisruption.criteo.com @@ -69,6 +69,22 @@ spec: URL form (`scheme://host:port/path`). type: string type: object + hookV2BasePath: + description: |- + HookV2BasePath holds the base path for the prepare, ready, cancel hooks that will be + called at different stages of the NodeDisruption lifecycle. + A POST http request containing a Disruption that is being reconciled is sent ot each of the hooks. + properties: + caBundle: + description: a PEM encoded CA bundle which will be used to validate + the webhook's server certificate. If unspecified, system trust + roots on the apiserver are used. + type: string + url: + description: URL that will be called by the hook, in standard + URL form (`scheme://host:port/path`). + type: string + type: object maxDisruptions: description: A NodeDisruption is allowed if at most "maxDisruptions" nodes selected by selectors are unavailable after the disruption. @@ -103,11 +119,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -148,11 +166,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string diff --git a/config/crd/bases/nodedisruption.criteo.com_nodedisruptionbudgets.yaml b/config/crd/bases/nodedisruption.criteo.com_nodedisruptionbudgets.yaml index b418220..1f19012 100644 --- a/config/crd/bases/nodedisruption.criteo.com_nodedisruptionbudgets.yaml +++ b/config/crd/bases/nodedisruption.criteo.com_nodedisruptionbudgets.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: nodedisruptionbudgets.nodedisruption.criteo.com spec: group: nodedisruption.criteo.com @@ -93,11 +93,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string diff --git a/config/crd/bases/nodedisruption.criteo.com_nodedisruptions.yaml b/config/crd/bases/nodedisruption.criteo.com_nodedisruptions.yaml index 3645a88..2589945 100644 --- a/config/crd/bases/nodedisruption.criteo.com_nodedisruptions.yaml +++ b/config/crd/bases/nodedisruption.criteo.com_nodedisruptions.yaml @@ -3,7 +3,7 @@ apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: annotations: - controller-gen.kubebuilder.io/version: v0.14.0 + controller-gen.kubebuilder.io/version: v0.19.0 name: nodedisruptions.nodedisruption.criteo.com spec: group: nodedisruption.criteo.com @@ -45,6 +45,9 @@ spec: spec: description: NodeDisruptionSpec defines the desired state of NodeDisruption properties: + duration: + description: Duration of the disruption once granted + type: string nodeSelector: description: Label query over nodes that will be impacted by the disruption properties: @@ -74,11 +77,13 @@ spec: items: type: string type: array + x-kubernetes-list-type: atomic required: - key - operator type: object type: array + x-kubernetes-list-type: atomic matchLabels: additionalProperties: type: string @@ -90,7 +95,8 @@ spec: type: object x-kubernetes-map-type: atomic retry: - description: Configure the retrying behavior of a NodeDisruption + description: Retry configuration allows to retry a disruption after + rejection until the given deadline properties: deadline: description: Deadline after which the disruption is not retried @@ -100,6 +106,10 @@ spec: description: Enable retrying type: boolean type: object + startDate: + description: StartDate when the disruption should start + format: date-time + type: string type: description: Type of the node disruption type: string @@ -114,6 +124,8 @@ spec: properties: ok: type: boolean + preparing: + type: boolean reason: type: string reference: @@ -133,6 +145,7 @@ spec: type: object required: - ok + - preparing - reason type: object type: array diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 010a85f..641679f 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -19,57 +19,7 @@ rules: - nodedisruption.criteo.com resources: - applicationdisruptionbudgets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - nodedisruption.criteo.com - resources: - - applicationdisruptionbudgets/finalizers - verbs: - - update -- apiGroups: - - nodedisruption.criteo.com - resources: - - applicationdisruptionbudgets/status - verbs: - - get - - patch - - update -- apiGroups: - - nodedisruption.criteo.com - resources: - nodedisruptionbudgets - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - nodedisruption.criteo.com - resources: - - nodedisruptionbudgets/finalizers - verbs: - - update -- apiGroups: - - nodedisruption.criteo.com - resources: - - nodedisruptionbudgets/status - verbs: - - get - - patch - - update -- apiGroups: - - nodedisruption.criteo.com - resources: - nodedisruptions verbs: - create @@ -82,12 +32,16 @@ rules: - apiGroups: - nodedisruption.criteo.com resources: + - applicationdisruptionbudgets/finalizers + - nodedisruptionbudgets/finalizers - nodedisruptions/finalizers verbs: - update - apiGroups: - nodedisruption.criteo.com resources: + - applicationdisruptionbudgets/status + - nodedisruptionbudgets/status - nodedisruptions/status verbs: - get diff --git a/go.mod b/go.mod index 24285ac..af9ee02 100644 --- a/go.mod +++ b/go.mod @@ -1,80 +1,102 @@ module github.com/criteo/node-disruption-controller -go 1.20 +go 1.25.4 require ( - github.com/onsi/ginkgo/v2 v2.9.5 - github.com/onsi/gomega v1.27.7 - github.com/stretchr/testify v1.8.1 - k8s.io/api v0.27.2 - k8s.io/apimachinery v0.27.2 - k8s.io/client-go v0.27.2 - sigs.k8s.io/controller-runtime v0.15.0 + github.com/go-openapi/errors v0.22.4 + github.com/go-openapi/runtime v0.29.2 + github.com/go-openapi/strfmt v0.25.0 + github.com/go-openapi/validate v0.25.1 + github.com/onsi/ginkgo/v2 v2.22.0 + github.com/onsi/gomega v1.36.1 + github.com/stretchr/testify v1.11.1 + k8s.io/api v0.34.2 + k8s.io/apimachinery v0.34.2 + k8s.io/client-go v0.34.2 + sigs.k8s.io/controller-runtime v0.22.4 ) require ( github.com/evanphx/json-patch v4.12.0+incompatible // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-openapi/analysis v0.24.1 // indirect + github.com/go-openapi/loads v0.23.2 // indirect + github.com/go-openapi/spec v0.22.1 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/fileutils v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/mangling v0.25.4 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.1 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.mongodb.org/mongo-driver v1.17.6 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/sync v0.18.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.1 // indirect ) require ( github.com/beorn7/perks v1.0.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/emicklei/go-restful/v3 v3.9.0 // indirect - github.com/evanphx/json-patch/v5 v5.6.0 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/go-logr/logr v1.2.4 // indirect - github.com/go-logr/zapr v1.2.4 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/jsonreference v0.20.1 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.3 // indirect + github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/swag v0.25.4 github.com/gogo/protobuf v1.3.2 // indirect github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/gofuzz v1.1.0 // indirect - github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/imdario/mergo v0.3.6 // indirect - github.com/josharian/intern v1.0.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect + github.com/google/uuid v1.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.4 // 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/pkg/errors v0.9.1 // indirect - github.com/prometheus/client_golang v1.15.1 // indirect - github.com/prometheus/client_model v0.4.0 // indirect - github.com/prometheus/common v0.42.0 // indirect - github.com/prometheus/procfs v0.9.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.24.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect - gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/protobuf v1.30.0 // indirect + github.com/prometheus/client_golang v1.23.2 + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.67.4 // indirect + github.com/prometheus/procfs v0.19.2 // indirect + github.com/spf13/pflag v1.0.10 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + golang.org/x/net v0.47.0 // indirect + golang.org/x/oauth2 v0.33.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/term v0.37.0 // indirect + golang.org/x/text v0.31.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.38.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.27.2 // indirect - k8s.io/component-base v0.27.2 // indirect - k8s.io/klog/v2 v2.90.1 // indirect - k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f // indirect - k8s.io/utils v0.0.0-20230209194617-a36077c30491 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect - sigs.k8s.io/yaml v1.3.0 // indirect + k8s.io/apiextensions-apiserver v0.34.2 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e // indirect + k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 8063d57..b3a48cd 100644 --- a/go.sum +++ b/go.sum @@ -1,287 +1,245 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 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/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= -github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/evanphx/json-patch/v5 v5.6.0 h1:b91NhWfaz02IuVxO9faSllyAtNXHMPkC5J8sJCLunww= -github.com/evanphx/json-patch/v5 v5.6.0/go.mod h1:G79N1coSVB93tBe7j6PhzjmR3/2VvlbKOFpnXhI9Bw4= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= -github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/zapr v1.2.4 h1:QHVo+6stLbfJmYGkQ7uGHUCu5hnAFAj6mDe6Ea0SeOo= -github.com/go-logr/zapr v1.2.4/go.mod h1:FyHWQIzQORZ0QVE1BtVHv3cKtNLuXsbNLtpuhNapBOA= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.1 h1:FBLnyygC4/IZZr893oiomc9XaghoveYTrLC1F86HID8= -github.com/go-openapi/jsonreference v0.20.1/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +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.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/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-openapi/analysis v0.24.1 h1:Xp+7Yn/KOnVWYG8d+hPksOYnCYImE3TieBa7rBOesYM= +github.com/go-openapi/analysis v0.24.1/go.mod h1:dU+qxX7QGU1rl7IYhBC8bIfmWQdX4Buoea4TGtxXY84= +github.com/go-openapi/errors v0.22.4 h1:oi2K9mHTOb5DPW2Zjdzs/NIvwi2N3fARKaTJLdNabaM= +github.com/go-openapi/errors v0.22.4/go.mod h1:z9S8ASTUqx7+CP1Q8dD8ewGH/1JWFFLX/2PmAYNQLgk= +github.com/go-openapi/jsonpointer v0.22.3 h1:dKMwfV4fmt6Ah90zloTbUKWMD+0he+12XYAsPotrkn8= +github.com/go-openapi/jsonpointer v0.22.3/go.mod h1:0lBbqeRsQ5lIanv3LHZBrmRGHLHcQoOXQnf88fHlGWo= +github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= +github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= +github.com/go-openapi/loads v0.23.2 h1:rJXAcP7g1+lWyBHC7iTY+WAF0rprtM+pm8Jxv1uQJp4= +github.com/go-openapi/loads v0.23.2/go.mod h1:IEVw1GfRt/P2Pplkelxzj9BYFajiWOtY2nHZNj4UnWY= +github.com/go-openapi/runtime v0.29.2 h1:UmwSGWNmWQqKm1c2MGgXVpC2FTGwPDQeUsBMufc5Yj0= +github.com/go-openapi/runtime v0.29.2/go.mod h1:biq5kJXRJKBJxTDJXAa00DOTa/anflQPhT0/wmjuy+0= +github.com/go-openapi/spec v0.22.1 h1:beZMa5AVQzRspNjvhe5aG1/XyBSMeX1eEOs7dMoXh/k= +github.com/go-openapi/spec v0.22.1/go.mod h1:c7aeIQT175dVowfp7FeCvXXnjN/MrpaONStibD2WtDA= +github.com/go-openapi/strfmt v0.25.0 h1:7R0RX7mbKLa9EYCTHRcCuIPcaqlyQiWNPTXwClK0saQ= +github.com/go-openapi/strfmt v0.25.0/go.mod h1:nNXct7OzbwrMY9+5tLX4I21pzcmE6ccMGXl3jFdPfn8= +github.com/go-openapi/swag v0.25.4 h1:OyUPUFYDPDBMkqyxOTkqDYFnrhuhi9NR6QVUvIochMU= +github.com/go-openapi/swag v0.25.4/go.mod h1:zNfJ9WZABGHCFg2RnY0S4IOkAcVTzJ6z2Bi+Q4i6qFQ= +github.com/go-openapi/swag/cmdutils v0.25.4 h1:8rYhB5n6WawR192/BfUu2iVlxqVR9aRgGJP6WaBoW+4= +github.com/go-openapi/swag/cmdutils v0.25.4/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/fileutils v0.25.4 h1:2oI0XNW5y6UWZTC7vAxC8hmsK/tOkWXHJQH4lKjqw+Y= +github.com/go-openapi/swag/fileutils v0.25.4/go.mod h1:cdOT/PKbwcysVQ9Tpr0q20lQKH7MGhOEb6EwmHOirUk= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/mangling v0.25.4 h1:2b9kBJk9JvPgxr36V23FxJLdwBrpijI26Bx5JH4Hp48= +github.com/go-openapi/swag/mangling v0.25.4/go.mod h1:6dxwu6QyORHpIIApsdZgb6wBk/DPU15MdyYj/ikn0Hg= +github.com/go-openapi/swag/netutils v0.25.4 h1:Gqe6K71bGRb3ZQLusdI8p/y1KLgV4M/k+/HzVSqT8H0= +github.com/go-openapi/swag/netutils v0.25.4/go.mod h1:m2W8dtdaoX7oj9rEttLyTeEFFEBvnAx9qHd5nJEBzYg= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-openapi/validate v0.25.1 h1:sSACUI6Jcnbo5IWqbYHgjibrhhmt3vR6lCzKZnmAgBw= +github.com/go-openapi/validate v0.25.1/go.mod h1:RMVyVFYte0gbSTaZ0N4KmTn6u/kClvAFp+mAVfS/DQc= +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/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4= github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54= -github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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.7.1 h1:SisTfuFKJSKM5CPZkffwi6coztzzeYUhc3v4yxLWH8c= +github.com/google/gnostic-models v0.7.1/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.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= -github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -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/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-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +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/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/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 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.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= +github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= +github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI= -github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY= -github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU= -github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= -github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= -github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= -github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -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/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8= +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.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= +github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +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/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 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.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -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/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/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= -go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUpsss= +go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +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.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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 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/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.33.0 h1:4Q+qn+E5z8gPRJfmRy7C2gGG3T4jIprK6aSYgTXGRpo= +golang.org/x/oauth2 v0.33.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= 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.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.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-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 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.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= 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= -gomodules.xyz/jsonpatch/v2 v2.3.0 h1:8NFhfS6gzxNqjLIYnZxg319wZ5Qjnx4m/CcX+Klzazc= -gomodules.xyz/jsonpatch/v2 v2.3.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= +gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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.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.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/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= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.27.2 h1:+H17AJpUMvl+clT+BPnKf0E3ksMAzoBBg7CntpSuADo= -k8s.io/api v0.27.2/go.mod h1:ENmbocXfBT2ADujUXcBhHV55RIT31IIEvkntP6vZKS4= -k8s.io/apiextensions-apiserver v0.27.2 h1:iwhyoeS4xj9Y7v8YExhUwbVuBhMr3Q4bd/laClBV6Bo= -k8s.io/apiextensions-apiserver v0.27.2/go.mod h1:Oz9UdvGguL3ULgRdY9QMUzL2RZImotgxvGjdWRq6ZXQ= -k8s.io/apimachinery v0.27.2 h1:vBjGaKKieaIreI+oQwELalVG4d8f3YAMNpWLzDXkxeg= -k8s.io/apimachinery v0.27.2/go.mod h1:XNfZ6xklnMCOGGFNqXG7bUrQCoR04dh/E7FprV6pb+E= -k8s.io/client-go v0.27.2 h1:vDLSeuYvCHKeoQRhCXjxXO45nHVv2Ip4Fe0MfioMrhE= -k8s.io/client-go v0.27.2/go.mod h1:tY0gVmUsHrAmjzHX9zs7eCjxcBsf8IiNe7KQ52biTcQ= -k8s.io/component-base v0.27.2 h1:neju+7s/r5O4x4/txeUONNTS9r1HsPbyoPBAtHsDCpo= -k8s.io/component-base v0.27.2/go.mod h1:5UPk7EjfgrfgRIuDBFtsEFAe4DAvP3U+M8RTzoSJkpo= -k8s.io/klog/v2 v2.90.1 h1:m4bYOKall2MmOiRaR1J+We67Do7vm9KiQVlT96lnHUw= -k8s.io/klog/v2 v2.90.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f h1:2kWPakN3i/k81b0gvD5C5FJ2kxm1WrQFanWchyKuqGg= -k8s.io/kube-openapi v0.0.0-20230501164219-8b0f38b5fd1f/go.mod h1:byini6yhqGC14c3ebc/QwanvYwhuMWF6yz2F8uwW8eg= -k8s.io/utils v0.0.0-20230209194617-a36077c30491 h1:r0BAOLElQnnFhE/ApUsg3iHdVYYPBjNSSOMowRZxxsY= -k8s.io/utils v0.0.0-20230209194617-a36077c30491/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.15.0 h1:ML+5Adt3qZnMSYxZ7gAverBLNPSMQEibtzAgp0UPojU= -sigs.k8s.io/controller-runtime v0.15.0/go.mod h1:7ngYvp1MLT+9GeZ+6lH3LOlcHkp/+tzA/fmHa4iq9kk= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE= -sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E= -sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= -sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= +k8s.io/api v0.34.2 h1:fsSUNZhV+bnL6Aqrp6O7lMTy6o5x2C4XLjnh//8SLYY= +k8s.io/api v0.34.2/go.mod h1:MMBPaWlED2a8w4RSeanD76f7opUoypY8TFYkSM+3XHw= +k8s.io/apiextensions-apiserver v0.34.2 h1:WStKftnGeoKP4AZRz/BaAAEJvYp4mlZGN0UCv+uvsqo= +k8s.io/apiextensions-apiserver v0.34.2/go.mod h1:398CJrsgXF1wytdaanynDpJ67zG4Xq7yj91GrmYN2SE= +k8s.io/apimachinery v0.34.2 h1:zQ12Uk3eMHPxrsbUJgNF8bTauTVR2WgqJsTmwTE/NW4= +k8s.io/apimachinery v0.34.2/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.2 h1:Co6XiknN+uUZqiddlfAjT68184/37PS4QAzYvQvDR8M= +k8s.io/client-go v0.34.2/go.mod h1:2VYDl1XXJsdcAxw7BenFslRQX28Dxz91U9MWKjX97fE= +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-20251125145642-4e65d59e963e h1:iW9ChlU0cU16w8MpVYjXk12dqQ4BPFBEgif+ap7/hqQ= +k8s.io/kube-openapi v0.0.0-20251125145642-4e65d59e963e/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/controller-runtime v0.22.4 h1:GEjV7KV3TY8e+tJ2LCTxUTanW4z/FmNB7l327UfMq9A= +sigs.k8s.io/controller-runtime v0.22.4/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= +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.1 h1:JrhdFMqOd/+3ByqlP2I45kTOZmTRLBUm5pvRjeheg7E= +sigs.k8s.io/structured-merge-diff/v6 v6.3.1/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/appmgr/disruption.go b/internal/appmgr/disruption.go new file mode 100644 index 0000000..9b157fc --- /dev/null +++ b/internal/appmgr/disruption.go @@ -0,0 +1,138 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package appmgr + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// Disruption Disprution describes a disruption of nodes by a given source. +// +// swagger:model Disruption +type Disruption struct { + + // Source of the disruption + // Example: maintenance-manager + CreatedBy string `json:"createdBy,omitempty"` + + // When the disruption was created + // Format: date-time + CreationDate strfmt.DateTime `json:"creationDate,omitempty"` + + // Start date + duration pre-calculated + // Format: date-time + DeadlineDate strfmt.DateTime `json:"deadlineDate,omitempty"` + + // For how long in seconds the disruption is expected to last after the start date. + // Example: 3600 + Duration int64 `json:"duration,omitempty"` + + // Human readable identifier of the disruption + // Example: hw-maint-123 + Name string `json:"name,omitempty"` + + // Nodes affected by this disruption + // Example: ["hello.example.com"] + Nodes []string `json:"nodes"` + + // Expected start date of the disruption + // Format: date-time + StartDate strfmt.DateTime `json:"startDate,omitempty"` + + // Type of disruption + // Example: decommission + Type string `json:"type,omitempty"` + + // Unique ID for this disruption + // Example: f45ac82c-7571-4770-8de1-dfdb0bf598de + UID string `json:"uid,omitempty"` +} + +// Validate validates this disruption +func (m *Disruption) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCreationDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDeadlineDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateStartDate(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Disruption) validateCreationDate(formats strfmt.Registry) error { + if swag.IsZero(m.CreationDate) { // not required + return nil + } + + if err := validate.FormatOf("creationDate", "body", "date-time", m.CreationDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *Disruption) validateDeadlineDate(formats strfmt.Registry) error { + if swag.IsZero(m.DeadlineDate) { // not required + return nil + } + + if err := validate.FormatOf("deadlineDate", "body", "date-time", m.DeadlineDate.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *Disruption) validateStartDate(formats strfmt.Registry) error { + if swag.IsZero(m.StartDate) { // not required + return nil + } + + if err := validate.FormatOf("startDate", "body", "date-time", m.StartDate.String(), formats); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this disruption based on context it is used +func (m *Disruption) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Disruption) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Disruption) UnmarshalBinary(b []byte) error { + var res Disruption + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/internal/appmgr/error.go b/internal/appmgr/error.go new file mode 100644 index 0000000..8432b0f --- /dev/null +++ b/internal/appmgr/error.go @@ -0,0 +1,28 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package appmgr + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/strfmt" +) + +// Error Error details when available +// Example: unable to contact internal tools +// +// swagger:model Error +type Error string + +// Validate validates this error +func (m Error) Validate(formats strfmt.Registry) error { + return nil +} + +// ContextValidate validates this error based on context it is used +func (m Error) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} diff --git a/internal/appmgrcli/disruption/cancel_preparation_parameters.go b/internal/appmgrcli/disruption/cancel_preparation_parameters.go new file mode 100644 index 0000000..7209ce8 --- /dev/null +++ b/internal/appmgrcli/disruption/cancel_preparation_parameters.go @@ -0,0 +1,153 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package disruption + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/criteo/node-disruption-controller/internal/appmgr" +) + +// NewCancelPreparationParams creates a new CancelPreparationParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewCancelPreparationParams() *CancelPreparationParams { + return &CancelPreparationParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewCancelPreparationParamsWithTimeout creates a new CancelPreparationParams object +// with the ability to set a timeout on a request. +func NewCancelPreparationParamsWithTimeout(timeout time.Duration) *CancelPreparationParams { + return &CancelPreparationParams{ + timeout: timeout, + } +} + +// NewCancelPreparationParamsWithContext creates a new CancelPreparationParams object +// with the ability to set a context for a request. +func NewCancelPreparationParamsWithContext(ctx context.Context) *CancelPreparationParams { + return &CancelPreparationParams{ + Context: ctx, + } +} + +// NewCancelPreparationParamsWithHTTPClient creates a new CancelPreparationParams object +// with the ability to set a custom HTTPClient for a request. +func NewCancelPreparationParamsWithHTTPClient(client *http.Client) *CancelPreparationParams { + return &CancelPreparationParams{ + HTTPClient: client, + } +} + +/* +CancelPreparationParams contains all the parameters to send to the API endpoint + + for the cancel preparation operation. + + Typically these are written to a http.Request. +*/ +type CancelPreparationParams struct { + + /* Body. + + Disruption + */ + Body *appmgr.Disruption + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the cancel preparation params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *CancelPreparationParams) WithDefaults() *CancelPreparationParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the cancel preparation params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *CancelPreparationParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the cancel preparation params +func (o *CancelPreparationParams) WithTimeout(timeout time.Duration) *CancelPreparationParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the cancel preparation params +func (o *CancelPreparationParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the cancel preparation params +func (o *CancelPreparationParams) WithContext(ctx context.Context) *CancelPreparationParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the cancel preparation params +func (o *CancelPreparationParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the cancel preparation params +func (o *CancelPreparationParams) WithHTTPClient(client *http.Client) *CancelPreparationParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the cancel preparation params +func (o *CancelPreparationParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the cancel preparation params +func (o *CancelPreparationParams) WithBody(body *appmgr.Disruption) *CancelPreparationParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the cancel preparation params +func (o *CancelPreparationParams) SetBody(body *appmgr.Disruption) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *CancelPreparationParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/internal/appmgrcli/disruption/cancel_preparation_responses.go b/internal/appmgrcli/disruption/cancel_preparation_responses.go new file mode 100644 index 0000000..1776299 --- /dev/null +++ b/internal/appmgrcli/disruption/cancel_preparation_responses.go @@ -0,0 +1,172 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package disruption + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + + "github.com/criteo/node-disruption-controller/internal/appmgr" +) + +// CancelPreparationReader is a Reader for the CancelPreparation structure. +type CancelPreparationReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *CancelPreparationReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewCancelPreparationOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + default: + result := NewCancelPreparationDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewCancelPreparationOK creates a CancelPreparationOK with default headers values +func NewCancelPreparationOK() *CancelPreparationOK { + return &CancelPreparationOK{} +} + +/* +CancelPreparationOK describes a response with status code 200, with default header values. + +Acknowledged. +*/ +type CancelPreparationOK struct { +} + +// IsSuccess returns true when this cancel preparation o k response has a 2xx status code +func (o *CancelPreparationOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this cancel preparation o k response has a 3xx status code +func (o *CancelPreparationOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this cancel preparation o k response has a 4xx status code +func (o *CancelPreparationOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this cancel preparation o k response has a 5xx status code +func (o *CancelPreparationOK) IsServerError() bool { + return false +} + +// IsCode returns true when this cancel preparation o k response a status code equal to that given +func (o *CancelPreparationOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the cancel preparation o k response +func (o *CancelPreparationOK) Code() int { + return 200 +} + +func (o *CancelPreparationOK) Error() string { + return fmt.Sprintf("[POST /cancel][%d] cancelPreparationOK", 200) +} + +func (o *CancelPreparationOK) String() string { + return fmt.Sprintf("[POST /cancel][%d] cancelPreparationOK", 200) +} + +func (o *CancelPreparationOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewCancelPreparationDefault creates a CancelPreparationDefault with default headers values +func NewCancelPreparationDefault(code int) *CancelPreparationDefault { + return &CancelPreparationDefault{ + _statusCode: code, + } +} + +/* +CancelPreparationDefault describes a response with status code -1, with default header values. + +Unexpected error +*/ +type CancelPreparationDefault struct { + _statusCode int + + Payload appmgr.Error +} + +// IsSuccess returns true when this cancel preparation default response has a 2xx status code +func (o *CancelPreparationDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this cancel preparation default response has a 3xx status code +func (o *CancelPreparationDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this cancel preparation default response has a 4xx status code +func (o *CancelPreparationDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this cancel preparation default response has a 5xx status code +func (o *CancelPreparationDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this cancel preparation default response a status code equal to that given +func (o *CancelPreparationDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the cancel preparation default response +func (o *CancelPreparationDefault) Code() int { + return o._statusCode +} + +func (o *CancelPreparationDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /cancel][%d] cancelPreparation default %s", o._statusCode, payload) +} + +func (o *CancelPreparationDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /cancel][%d] cancelPreparation default %s", o._statusCode, payload) +} + +func (o *CancelPreparationDefault) GetPayload() appmgr.Error { + return o.Payload +} + +func (o *CancelPreparationDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/internal/appmgrcli/disruption/check_readiness_parameters.go b/internal/appmgrcli/disruption/check_readiness_parameters.go new file mode 100644 index 0000000..621cb72 --- /dev/null +++ b/internal/appmgrcli/disruption/check_readiness_parameters.go @@ -0,0 +1,153 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package disruption + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/criteo/node-disruption-controller/internal/appmgr" +) + +// NewCheckReadinessParams creates a new CheckReadinessParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewCheckReadinessParams() *CheckReadinessParams { + return &CheckReadinessParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewCheckReadinessParamsWithTimeout creates a new CheckReadinessParams object +// with the ability to set a timeout on a request. +func NewCheckReadinessParamsWithTimeout(timeout time.Duration) *CheckReadinessParams { + return &CheckReadinessParams{ + timeout: timeout, + } +} + +// NewCheckReadinessParamsWithContext creates a new CheckReadinessParams object +// with the ability to set a context for a request. +func NewCheckReadinessParamsWithContext(ctx context.Context) *CheckReadinessParams { + return &CheckReadinessParams{ + Context: ctx, + } +} + +// NewCheckReadinessParamsWithHTTPClient creates a new CheckReadinessParams object +// with the ability to set a custom HTTPClient for a request. +func NewCheckReadinessParamsWithHTTPClient(client *http.Client) *CheckReadinessParams { + return &CheckReadinessParams{ + HTTPClient: client, + } +} + +/* +CheckReadinessParams contains all the parameters to send to the API endpoint + + for the check readiness operation. + + Typically these are written to a http.Request. +*/ +type CheckReadinessParams struct { + + /* Body. + + Disruption + */ + Body *appmgr.Disruption + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the check readiness params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *CheckReadinessParams) WithDefaults() *CheckReadinessParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the check readiness params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *CheckReadinessParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the check readiness params +func (o *CheckReadinessParams) WithTimeout(timeout time.Duration) *CheckReadinessParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the check readiness params +func (o *CheckReadinessParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the check readiness params +func (o *CheckReadinessParams) WithContext(ctx context.Context) *CheckReadinessParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the check readiness params +func (o *CheckReadinessParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the check readiness params +func (o *CheckReadinessParams) WithHTTPClient(client *http.Client) *CheckReadinessParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the check readiness params +func (o *CheckReadinessParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the check readiness params +func (o *CheckReadinessParams) WithBody(body *appmgr.Disruption) *CheckReadinessParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the check readiness params +func (o *CheckReadinessParams) SetBody(body *appmgr.Disruption) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *CheckReadinessParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/internal/appmgrcli/disruption/check_readiness_responses.go b/internal/appmgrcli/disruption/check_readiness_responses.go new file mode 100644 index 0000000..4897cdf --- /dev/null +++ b/internal/appmgrcli/disruption/check_readiness_responses.go @@ -0,0 +1,251 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package disruption + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "github.com/criteo/node-disruption-controller/internal/appmgr" +) + +// CheckReadinessReader is a Reader for the CheckReadiness structure. +type CheckReadinessReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *CheckReadinessReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewCheckReadinessOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 425: + result := NewCheckReadinessStatus425() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewCheckReadinessDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewCheckReadinessOK creates a CheckReadinessOK with default headers values +func NewCheckReadinessOK() *CheckReadinessOK { + return &CheckReadinessOK{} +} + +/* +CheckReadinessOK describes a response with status code 200, with default header values. + +Ready. +*/ +type CheckReadinessOK struct { +} + +// IsSuccess returns true when this check readiness o k response has a 2xx status code +func (o *CheckReadinessOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this check readiness o k response has a 3xx status code +func (o *CheckReadinessOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this check readiness o k response has a 4xx status code +func (o *CheckReadinessOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this check readiness o k response has a 5xx status code +func (o *CheckReadinessOK) IsServerError() bool { + return false +} + +// IsCode returns true when this check readiness o k response a status code equal to that given +func (o *CheckReadinessOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the check readiness o k response +func (o *CheckReadinessOK) Code() int { + return 200 +} + +func (o *CheckReadinessOK) Error() string { + return fmt.Sprintf("[POST /ready][%d] checkReadinessOK", 200) +} + +func (o *CheckReadinessOK) String() string { + return fmt.Sprintf("[POST /ready][%d] checkReadinessOK", 200) +} + +func (o *CheckReadinessOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewCheckReadinessStatus425 creates a CheckReadinessStatus425 with default headers values +func NewCheckReadinessStatus425() *CheckReadinessStatus425 { + return &CheckReadinessStatus425{} +} + +/* +CheckReadinessStatus425 describes a response with status code 425, with default header values. + +Cannot be processed right now, retry later / after Retry-After value +*/ +type CheckReadinessStatus425 struct { + + /* Delay in seconds. + */ + RetryAfter int64 +} + +// IsSuccess returns true when this check readiness status425 response has a 2xx status code +func (o *CheckReadinessStatus425) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this check readiness status425 response has a 3xx status code +func (o *CheckReadinessStatus425) IsRedirect() bool { + return false +} + +// IsClientError returns true when this check readiness status425 response has a 4xx status code +func (o *CheckReadinessStatus425) IsClientError() bool { + return true +} + +// IsServerError returns true when this check readiness status425 response has a 5xx status code +func (o *CheckReadinessStatus425) IsServerError() bool { + return false +} + +// IsCode returns true when this check readiness status425 response a status code equal to that given +func (o *CheckReadinessStatus425) IsCode(code int) bool { + return code == 425 +} + +// Code gets the status code for the check readiness status425 response +func (o *CheckReadinessStatus425) Code() int { + return 425 +} + +func (o *CheckReadinessStatus425) Error() string { + return fmt.Sprintf("[POST /ready][%d] checkReadinessStatus425", 425) +} + +func (o *CheckReadinessStatus425) String() string { + return fmt.Sprintf("[POST /ready][%d] checkReadinessStatus425", 425) +} + +func (o *CheckReadinessStatus425) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // hydrates response header Retry-After + hdrRetryAfter := response.GetHeader("Retry-After") + + if hdrRetryAfter != "" { + valretryAfter, err := swag.ConvertInt64(hdrRetryAfter) + if err != nil { + return errors.InvalidType("Retry-After", "header", "int64", hdrRetryAfter) + } + o.RetryAfter = valretryAfter + } + + return nil +} + +// NewCheckReadinessDefault creates a CheckReadinessDefault with default headers values +func NewCheckReadinessDefault(code int) *CheckReadinessDefault { + return &CheckReadinessDefault{ + _statusCode: code, + } +} + +/* +CheckReadinessDefault describes a response with status code -1, with default header values. + +Unexpected error +*/ +type CheckReadinessDefault struct { + _statusCode int + + Payload appmgr.Error +} + +// IsSuccess returns true when this check readiness default response has a 2xx status code +func (o *CheckReadinessDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this check readiness default response has a 3xx status code +func (o *CheckReadinessDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this check readiness default response has a 4xx status code +func (o *CheckReadinessDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this check readiness default response has a 5xx status code +func (o *CheckReadinessDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this check readiness default response a status code equal to that given +func (o *CheckReadinessDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the check readiness default response +func (o *CheckReadinessDefault) Code() int { + return o._statusCode +} + +func (o *CheckReadinessDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /ready][%d] checkReadiness default %s", o._statusCode, payload) +} + +func (o *CheckReadinessDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /ready][%d] checkReadiness default %s", o._statusCode, payload) +} + +func (o *CheckReadinessDefault) GetPayload() appmgr.Error { + return o.Payload +} + +func (o *CheckReadinessDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/internal/appmgrcli/disruption/disruption_client.go b/internal/appmgrcli/disruption/disruption_client.go new file mode 100644 index 0000000..ecdce38 --- /dev/null +++ b/internal/appmgrcli/disruption/disruption_client.go @@ -0,0 +1,195 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package disruption + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "github.com/go-openapi/runtime" + httptransport "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" +) + +// New creates a new disruption API client. +func New(transport runtime.ClientTransport, formats strfmt.Registry) ClientService { + return &Client{transport: transport, formats: formats} +} + +// New creates a new disruption API client with basic auth credentials. +// It takes the following parameters: +// - host: http host (github.com). +// - basePath: any base path for the API client ("/v1", "/v3"). +// - scheme: http scheme ("http", "https"). +// - user: user for basic authentication header. +// - password: password for basic authentication header. +func NewClientWithBasicAuth(host, basePath, scheme, user, password string) ClientService { + transport := httptransport.New(host, basePath, []string{scheme}) + transport.DefaultAuthentication = httptransport.BasicAuth(user, password) + return &Client{transport: transport, formats: strfmt.Default} +} + +// New creates a new disruption API client with a bearer token for authentication. +// It takes the following parameters: +// - host: http host (github.com). +// - basePath: any base path for the API client ("/v1", "/v3"). +// - scheme: http scheme ("http", "https"). +// - bearerToken: bearer token for Bearer authentication header. +func NewClientWithBearerToken(host, basePath, scheme, bearerToken string) ClientService { + transport := httptransport.New(host, basePath, []string{scheme}) + transport.DefaultAuthentication = httptransport.BearerToken(bearerToken) + return &Client{transport: transport, formats: strfmt.Default} +} + +/* +Client for disruption API +*/ +type Client struct { + transport runtime.ClientTransport + formats strfmt.Registry +} + +// ClientOption may be used to customize the behavior of Client methods. +type ClientOption func(*runtime.ClientOperation) + +// ClientService is the interface for Client methods +type ClientService interface { + CancelPreparation(params *CancelPreparationParams, opts ...ClientOption) (*CancelPreparationOK, error) + + CheckReadiness(params *CheckReadinessParams, opts ...ClientOption) (*CheckReadinessOK, error) + + PrepareApplication(params *PrepareApplicationParams, opts ...ClientOption) (*PrepareApplicationOK, error) + + SetTransport(transport runtime.ClientTransport) +} + +/* +CancelPreparation cancels application preparation for a disruption +*/ +func (a *Client) CancelPreparation(params *CancelPreparationParams, opts ...ClientOption) (*CancelPreparationOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewCancelPreparationParams() + } + op := &runtime.ClientOperation{ + ID: "cancelPreparation", + Method: "POST", + PathPattern: "/cancel", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"https"}, + Params: params, + Reader: &CancelPreparationReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*CancelPreparationOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*CancelPreparationDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +CheckReadiness checks application readiness for a disruption +*/ +func (a *Client) CheckReadiness(params *CheckReadinessParams, opts ...ClientOption) (*CheckReadinessOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewCheckReadinessParams() + } + op := &runtime.ClientOperation{ + ID: "checkReadiness", + Method: "POST", + PathPattern: "/ready", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"https"}, + Params: params, + Reader: &CheckReadinessReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*CheckReadinessOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*CheckReadinessDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +/* +PrepareApplication prepares application for a disruption +*/ +func (a *Client) PrepareApplication(params *PrepareApplicationParams, opts ...ClientOption) (*PrepareApplicationOK, error) { + // NOTE: parameters are not validated before sending + if params == nil { + params = NewPrepareApplicationParams() + } + op := &runtime.ClientOperation{ + ID: "prepareApplication", + Method: "POST", + PathPattern: "/prepare", + ProducesMediaTypes: []string{"application/json"}, + ConsumesMediaTypes: []string{"application/json"}, + Schemes: []string{"https"}, + Params: params, + Reader: &PrepareApplicationReader{formats: a.formats}, + Context: params.Context, + Client: params.HTTPClient, + } + for _, opt := range opts { + opt(op) + } + result, err := a.transport.Submit(op) + if err != nil { + return nil, err + } + + // only one success response has to be checked + success, ok := result.(*PrepareApplicationOK) + if ok { + return success, nil + } + + // unexpected success response. + // + // a default response is provided: fill this and return an error + unexpectedSuccess := result.(*PrepareApplicationDefault) + + return nil, runtime.NewAPIError("unexpected success response: content available as default response in error", unexpectedSuccess, unexpectedSuccess.Code()) +} + +// SetTransport changes the transport on the client +func (a *Client) SetTransport(transport runtime.ClientTransport) { + a.transport = transport +} diff --git a/internal/appmgrcli/disruption/prepare_application_parameters.go b/internal/appmgrcli/disruption/prepare_application_parameters.go new file mode 100644 index 0000000..e400350 --- /dev/null +++ b/internal/appmgrcli/disruption/prepare_application_parameters.go @@ -0,0 +1,153 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package disruption + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "net/http" + "time" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + cr "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/criteo/node-disruption-controller/internal/appmgr" +) + +// NewPrepareApplicationParams creates a new PrepareApplicationParams object, +// with the default timeout for this client. +// +// Default values are not hydrated, since defaults are normally applied by the API server side. +// +// To enforce default values in parameter, use SetDefaults or WithDefaults. +func NewPrepareApplicationParams() *PrepareApplicationParams { + return &PrepareApplicationParams{ + timeout: cr.DefaultTimeout, + } +} + +// NewPrepareApplicationParamsWithTimeout creates a new PrepareApplicationParams object +// with the ability to set a timeout on a request. +func NewPrepareApplicationParamsWithTimeout(timeout time.Duration) *PrepareApplicationParams { + return &PrepareApplicationParams{ + timeout: timeout, + } +} + +// NewPrepareApplicationParamsWithContext creates a new PrepareApplicationParams object +// with the ability to set a context for a request. +func NewPrepareApplicationParamsWithContext(ctx context.Context) *PrepareApplicationParams { + return &PrepareApplicationParams{ + Context: ctx, + } +} + +// NewPrepareApplicationParamsWithHTTPClient creates a new PrepareApplicationParams object +// with the ability to set a custom HTTPClient for a request. +func NewPrepareApplicationParamsWithHTTPClient(client *http.Client) *PrepareApplicationParams { + return &PrepareApplicationParams{ + HTTPClient: client, + } +} + +/* +PrepareApplicationParams contains all the parameters to send to the API endpoint + + for the prepare application operation. + + Typically these are written to a http.Request. +*/ +type PrepareApplicationParams struct { + + /* Body. + + Disruption + */ + Body *appmgr.Disruption + + timeout time.Duration + Context context.Context + HTTPClient *http.Client +} + +// WithDefaults hydrates default values in the prepare application params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PrepareApplicationParams) WithDefaults() *PrepareApplicationParams { + o.SetDefaults() + return o +} + +// SetDefaults hydrates default values in the prepare application params (not the query body). +// +// All values with no default are reset to their zero value. +func (o *PrepareApplicationParams) SetDefaults() { + // no default values defined for this parameter +} + +// WithTimeout adds the timeout to the prepare application params +func (o *PrepareApplicationParams) WithTimeout(timeout time.Duration) *PrepareApplicationParams { + o.SetTimeout(timeout) + return o +} + +// SetTimeout adds the timeout to the prepare application params +func (o *PrepareApplicationParams) SetTimeout(timeout time.Duration) { + o.timeout = timeout +} + +// WithContext adds the context to the prepare application params +func (o *PrepareApplicationParams) WithContext(ctx context.Context) *PrepareApplicationParams { + o.SetContext(ctx) + return o +} + +// SetContext adds the context to the prepare application params +func (o *PrepareApplicationParams) SetContext(ctx context.Context) { + o.Context = ctx +} + +// WithHTTPClient adds the HTTPClient to the prepare application params +func (o *PrepareApplicationParams) WithHTTPClient(client *http.Client) *PrepareApplicationParams { + o.SetHTTPClient(client) + return o +} + +// SetHTTPClient adds the HTTPClient to the prepare application params +func (o *PrepareApplicationParams) SetHTTPClient(client *http.Client) { + o.HTTPClient = client +} + +// WithBody adds the body to the prepare application params +func (o *PrepareApplicationParams) WithBody(body *appmgr.Disruption) *PrepareApplicationParams { + o.SetBody(body) + return o +} + +// SetBody adds the body to the prepare application params +func (o *PrepareApplicationParams) SetBody(body *appmgr.Disruption) { + o.Body = body +} + +// WriteToRequest writes these params to a swagger request +func (o *PrepareApplicationParams) WriteToRequest(r runtime.ClientRequest, reg strfmt.Registry) error { + + if err := r.SetTimeout(o.timeout); err != nil { + return err + } + var res []error + if o.Body != nil { + if err := r.SetBodyParam(o.Body); err != nil { + return err + } + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} diff --git a/internal/appmgrcli/disruption/prepare_application_responses.go b/internal/appmgrcli/disruption/prepare_application_responses.go new file mode 100644 index 0000000..086ab1a --- /dev/null +++ b/internal/appmgrcli/disruption/prepare_application_responses.go @@ -0,0 +1,313 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package disruption + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "encoding/json" + stderrors "errors" + "fmt" + "io" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + + "github.com/criteo/node-disruption-controller/internal/appmgr" +) + +// PrepareApplicationReader is a Reader for the PrepareApplication structure. +type PrepareApplicationReader struct { + formats strfmt.Registry +} + +// ReadResponse reads a server response into the received o. +func (o *PrepareApplicationReader) ReadResponse(response runtime.ClientResponse, consumer runtime.Consumer) (any, error) { + switch response.Code() { + case 200: + result := NewPrepareApplicationOK() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return result, nil + case 424: + result := NewPrepareApplicationFailedDependency() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + case 425: + result := NewPrepareApplicationStatus425() + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + return nil, result + default: + result := NewPrepareApplicationDefault(response.Code()) + if err := result.readResponse(response, consumer, o.formats); err != nil { + return nil, err + } + if response.Code()/100 == 2 { + return result, nil + } + return nil, result + } +} + +// NewPrepareApplicationOK creates a PrepareApplicationOK with default headers values +func NewPrepareApplicationOK() *PrepareApplicationOK { + return &PrepareApplicationOK{} +} + +/* +PrepareApplicationOK describes a response with status code 200, with default header values. + +Accepted, preparation planned. +*/ +type PrepareApplicationOK struct { +} + +// IsSuccess returns true when this prepare application o k response has a 2xx status code +func (o *PrepareApplicationOK) IsSuccess() bool { + return true +} + +// IsRedirect returns true when this prepare application o k response has a 3xx status code +func (o *PrepareApplicationOK) IsRedirect() bool { + return false +} + +// IsClientError returns true when this prepare application o k response has a 4xx status code +func (o *PrepareApplicationOK) IsClientError() bool { + return false +} + +// IsServerError returns true when this prepare application o k response has a 5xx status code +func (o *PrepareApplicationOK) IsServerError() bool { + return false +} + +// IsCode returns true when this prepare application o k response a status code equal to that given +func (o *PrepareApplicationOK) IsCode(code int) bool { + return code == 200 +} + +// Code gets the status code for the prepare application o k response +func (o *PrepareApplicationOK) Code() int { + return 200 +} + +func (o *PrepareApplicationOK) Error() string { + return fmt.Sprintf("[POST /prepare][%d] prepareApplicationOK", 200) +} + +func (o *PrepareApplicationOK) String() string { + return fmt.Sprintf("[POST /prepare][%d] prepareApplicationOK", 200) +} + +func (o *PrepareApplicationOK) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPrepareApplicationFailedDependency creates a PrepareApplicationFailedDependency with default headers values +func NewPrepareApplicationFailedDependency() *PrepareApplicationFailedDependency { + return &PrepareApplicationFailedDependency{} +} + +/* +PrepareApplicationFailedDependency describes a response with status code 424, with default header values. + +Unable to accept due to internal constraints. +*/ +type PrepareApplicationFailedDependency struct { +} + +// IsSuccess returns true when this prepare application failed dependency response has a 2xx status code +func (o *PrepareApplicationFailedDependency) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this prepare application failed dependency response has a 3xx status code +func (o *PrepareApplicationFailedDependency) IsRedirect() bool { + return false +} + +// IsClientError returns true when this prepare application failed dependency response has a 4xx status code +func (o *PrepareApplicationFailedDependency) IsClientError() bool { + return true +} + +// IsServerError returns true when this prepare application failed dependency response has a 5xx status code +func (o *PrepareApplicationFailedDependency) IsServerError() bool { + return false +} + +// IsCode returns true when this prepare application failed dependency response a status code equal to that given +func (o *PrepareApplicationFailedDependency) IsCode(code int) bool { + return code == 424 +} + +// Code gets the status code for the prepare application failed dependency response +func (o *PrepareApplicationFailedDependency) Code() int { + return 424 +} + +func (o *PrepareApplicationFailedDependency) Error() string { + return fmt.Sprintf("[POST /prepare][%d] prepareApplicationFailedDependency", 424) +} + +func (o *PrepareApplicationFailedDependency) String() string { + return fmt.Sprintf("[POST /prepare][%d] prepareApplicationFailedDependency", 424) +} + +func (o *PrepareApplicationFailedDependency) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + return nil +} + +// NewPrepareApplicationStatus425 creates a PrepareApplicationStatus425 with default headers values +func NewPrepareApplicationStatus425() *PrepareApplicationStatus425 { + return &PrepareApplicationStatus425{} +} + +/* +PrepareApplicationStatus425 describes a response with status code 425, with default header values. + +Cannot be processed right now, retry later / after Retry-After value +*/ +type PrepareApplicationStatus425 struct { + + /* Delay in seconds. + */ + RetryAfter int64 +} + +// IsSuccess returns true when this prepare application status425 response has a 2xx status code +func (o *PrepareApplicationStatus425) IsSuccess() bool { + return false +} + +// IsRedirect returns true when this prepare application status425 response has a 3xx status code +func (o *PrepareApplicationStatus425) IsRedirect() bool { + return false +} + +// IsClientError returns true when this prepare application status425 response has a 4xx status code +func (o *PrepareApplicationStatus425) IsClientError() bool { + return true +} + +// IsServerError returns true when this prepare application status425 response has a 5xx status code +func (o *PrepareApplicationStatus425) IsServerError() bool { + return false +} + +// IsCode returns true when this prepare application status425 response a status code equal to that given +func (o *PrepareApplicationStatus425) IsCode(code int) bool { + return code == 425 +} + +// Code gets the status code for the prepare application status425 response +func (o *PrepareApplicationStatus425) Code() int { + return 425 +} + +func (o *PrepareApplicationStatus425) Error() string { + return fmt.Sprintf("[POST /prepare][%d] prepareApplicationStatus425", 425) +} + +func (o *PrepareApplicationStatus425) String() string { + return fmt.Sprintf("[POST /prepare][%d] prepareApplicationStatus425", 425) +} + +func (o *PrepareApplicationStatus425) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // hydrates response header Retry-After + hdrRetryAfter := response.GetHeader("Retry-After") + + if hdrRetryAfter != "" { + valretryAfter, err := swag.ConvertInt64(hdrRetryAfter) + if err != nil { + return errors.InvalidType("Retry-After", "header", "int64", hdrRetryAfter) + } + o.RetryAfter = valretryAfter + } + + return nil +} + +// NewPrepareApplicationDefault creates a PrepareApplicationDefault with default headers values +func NewPrepareApplicationDefault(code int) *PrepareApplicationDefault { + return &PrepareApplicationDefault{ + _statusCode: code, + } +} + +/* +PrepareApplicationDefault describes a response with status code -1, with default header values. + +Unexpected error +*/ +type PrepareApplicationDefault struct { + _statusCode int + + Payload appmgr.Error +} + +// IsSuccess returns true when this prepare application default response has a 2xx status code +func (o *PrepareApplicationDefault) IsSuccess() bool { + return o._statusCode/100 == 2 +} + +// IsRedirect returns true when this prepare application default response has a 3xx status code +func (o *PrepareApplicationDefault) IsRedirect() bool { + return o._statusCode/100 == 3 +} + +// IsClientError returns true when this prepare application default response has a 4xx status code +func (o *PrepareApplicationDefault) IsClientError() bool { + return o._statusCode/100 == 4 +} + +// IsServerError returns true when this prepare application default response has a 5xx status code +func (o *PrepareApplicationDefault) IsServerError() bool { + return o._statusCode/100 == 5 +} + +// IsCode returns true when this prepare application default response a status code equal to that given +func (o *PrepareApplicationDefault) IsCode(code int) bool { + return o._statusCode == code +} + +// Code gets the status code for the prepare application default response +func (o *PrepareApplicationDefault) Code() int { + return o._statusCode +} + +func (o *PrepareApplicationDefault) Error() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /prepare][%d] prepareApplication default %s", o._statusCode, payload) +} + +func (o *PrepareApplicationDefault) String() string { + payload, _ := json.Marshal(o.Payload) + return fmt.Sprintf("[POST /prepare][%d] prepareApplication default %s", o._statusCode, payload) +} + +func (o *PrepareApplicationDefault) GetPayload() appmgr.Error { + return o.Payload +} + +func (o *PrepareApplicationDefault) readResponse(response runtime.ClientResponse, consumer runtime.Consumer, formats strfmt.Registry) error { + + // response payload + if err := consumer.Consume(response.Body(), &o.Payload); err != nil && !stderrors.Is(err, io.EOF) { + return err + } + + return nil +} diff --git a/internal/appmgrcli/swagger_node_distruption_client_open_api_30_client.go b/internal/appmgrcli/swagger_node_distruption_client_open_api_30_client.go new file mode 100644 index 0000000..0d9d55e --- /dev/null +++ b/internal/appmgrcli/swagger_node_distruption_client_open_api_30_client.go @@ -0,0 +1,112 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package appmgrcli + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "github.com/go-openapi/runtime" + httptransport "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + + "github.com/criteo/node-disruption-controller/internal/appmgrcli/disruption" +) + +// Default swagger node distruption client open API 30 HTTP client. +var Default = NewHTTPClient(nil) + +const ( + // DefaultHost is the default Host + // found in Meta (info) section of spec file + DefaultHost string = "localhost" + // DefaultBasePath is the default BasePath + // found in Meta (info) section of spec file + DefaultBasePath string = "/" +) + +// DefaultSchemes are the default schemes found in Meta (info) section of spec file +var DefaultSchemes = []string{"https"} + +// NewHTTPClient creates a new swagger node distruption client open API 30 HTTP client. +func NewHTTPClient(formats strfmt.Registry) *SwaggerNodeDistruptionClientOpenAPI30 { + return NewHTTPClientWithConfig(formats, nil) +} + +// NewHTTPClientWithConfig creates a new swagger node distruption client open API 30 HTTP client, +// using a customizable transport config. +func NewHTTPClientWithConfig(formats strfmt.Registry, cfg *TransportConfig) *SwaggerNodeDistruptionClientOpenAPI30 { + // ensure nullable parameters have default + if cfg == nil { + cfg = DefaultTransportConfig() + } + + // create transport and client + transport := httptransport.New(cfg.Host, cfg.BasePath, cfg.Schemes) + return New(transport, formats) +} + +// New creates a new swagger node distruption client open API 30 client +func New(transport runtime.ClientTransport, formats strfmt.Registry) *SwaggerNodeDistruptionClientOpenAPI30 { + // ensure nullable parameters have default + if formats == nil { + formats = strfmt.Default + } + + cli := new(SwaggerNodeDistruptionClientOpenAPI30) + cli.Transport = transport + cli.Disruption = disruption.New(transport, formats) + return cli +} + +// DefaultTransportConfig creates a TransportConfig with the +// default settings taken from the meta section of the spec file. +func DefaultTransportConfig() *TransportConfig { + return &TransportConfig{ + Host: DefaultHost, + BasePath: DefaultBasePath, + Schemes: DefaultSchemes, + } +} + +// TransportConfig contains the transport related info, +// found in the meta section of the spec file. +type TransportConfig struct { + Host string + BasePath string + Schemes []string +} + +// WithHost overrides the default host, +// provided by the meta section of the spec file. +func (cfg *TransportConfig) WithHost(host string) *TransportConfig { + cfg.Host = host + return cfg +} + +// WithBasePath overrides the default basePath, +// provided by the meta section of the spec file. +func (cfg *TransportConfig) WithBasePath(basePath string) *TransportConfig { + cfg.BasePath = basePath + return cfg +} + +// WithSchemes overrides the default schemes, +// provided by the meta section of the spec file. +func (cfg *TransportConfig) WithSchemes(schemes []string) *TransportConfig { + cfg.Schemes = schemes + return cfg +} + +// SwaggerNodeDistruptionClientOpenAPI30 is a client for swagger node distruption client open API 30 +type SwaggerNodeDistruptionClientOpenAPI30 struct { + Disruption disruption.ClientService + + Transport runtime.ClientTransport +} + +// SetTransport changes the transport on the client and all its subresources +func (c *SwaggerNodeDistruptionClientOpenAPI30) SetTransport(transport runtime.ClientTransport) { + c.Transport = transport + c.Disruption.SetTransport(transport) +} diff --git a/internal/controller/applicationdisruptionbudget_controller.go b/internal/controller/applicationdisruptionbudget_controller.go index 4367324..717d9c3 100644 --- a/internal/controller/applicationdisruptionbudget_controller.go +++ b/internal/controller/applicationdisruptionbudget_controller.go @@ -23,32 +23,39 @@ import ( "fmt" "io" "net/http" + "net/url" "reflect" "strconv" "time" - "k8s.io/apimachinery/pkg/api/errors" + nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1" + "github.com/criteo/node-disruption-controller/internal/appmgr" + "github.com/criteo/node-disruption-controller/internal/appmgrcli/disruption" + "github.com/criteo/node-disruption-controller/pkg/resolver" + httptransport "github.com/go-openapi/runtime/client" + "github.com/go-openapi/strfmt" + "github.com/prometheus/client_golang/prometheus" corev1 "k8s.io/api/core/v1" + errorsk8s "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1" - "github.com/criteo/node-disruption-controller/pkg/resolver" - "github.com/prometheus/client_golang/prometheus" ) // ApplicationDisruptionBudgetReconciler reconciles a ApplicationDisruptionBudget object type ApplicationDisruptionBudgetReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + HTTPCli *http.Client } //+kubebuilder:rbac:groups=nodedisruption.criteo.com,resources=applicationdisruptionbudgets,verbs=get;list;watch;create;update;patch;delete @@ -69,7 +76,7 @@ type ApplicationDisruptionBudgetReconciler struct { func (r *ApplicationDisruptionBudgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) adb := &nodedisruptionv1alpha1.ApplicationDisruptionBudget{} - err := r.Client.Get(ctx, req.NamespacedName, adb) + err := r.Get(ctx, req.NamespacedName, adb) ref := nodedisruptionv1alpha1.NamespacedName{ Namespace: req.Namespace, Name: req.Name, @@ -77,7 +84,7 @@ func (r *ApplicationDisruptionBudgetReconciler) Reconcile(ctx context.Context, r } if err != nil { - if errors.IsNotFound(err) { + if errorsk8s.IsNotFound(err) { // If the resource was not found, nothing has to be done PruneADBMetrics(ref) return ctrl.Result{}, nil @@ -125,7 +132,7 @@ func (r *ApplicationDisruptionBudgetReconciler) MapFuncBuilder() handler.MapFunc // Look for all ADBs in the namespace, then see if they match the object return func(ctx context.Context, object client.Object) (requests []reconcile.Request) { adbs := nodedisruptionv1alpha1.ApplicationDisruptionBudgetList{} - err := r.Client.List(ctx, &adbs, &client.ListOptions{Namespace: object.GetNamespace()}) + err := r.List(ctx, &adbs, &client.ListOptions{Namespace: object.GetNamespace()}) if err != nil { // We cannot return an error so at least it should be logged logger := log.FromContext(context.Background()) @@ -150,6 +157,7 @@ func (r *ApplicationDisruptionBudgetReconciler) MapFuncBuilder() handler.MapFunc // SetupWithManager sets up the controller with the Manager. func (r *ApplicationDisruptionBudgetReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). + WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}). // TODO(j.clerc): refactor tests to avoid skipping name validation For(&nodedisruptionv1alpha1.ApplicationDisruptionBudget{}). Watches( &corev1.Pod{}, @@ -170,6 +178,7 @@ type ApplicationDisruptionBudgetResolver struct { ApplicationDisruptionBudget *nodedisruptionv1alpha1.ApplicationDisruptionBudget Client client.Client Resolver resolver.Resolver + hookCli disruption.ClientService } // Sync ensure the budget's status is up to date @@ -216,6 +225,96 @@ func (r *ApplicationDisruptionBudgetResolver) GetNamespacedName() nodedisruption } } +func (r *ApplicationDisruptionBudgetResolver) V2HooksReady() bool { + return r.ApplicationDisruptionBudget.Spec.HookV2BasePath.URL != "" +} + +func (r *ApplicationDisruptionBudgetResolver) CallPrepareHook(ctx context.Context, nd nodedisruptionv1alpha1.NodeDisruption, timeout time.Duration) error { + svc, err := r.hookClient() + if err != nil { + return err + } + + _, err = svc.PrepareApplication(&disruption.PrepareApplicationParams{ + Body: r.hookBody(nd), + HTTPClient: &http.Client{Timeout: timeout}, + }) + if err == nil { + return nil + } + if e, ok := err.(*disruption.PrepareApplicationStatus425); ok { + return fmt.Errorf("retry later, in %v seconds", e.RetryAfter) + } + return err +} + +func (r *ApplicationDisruptionBudgetResolver) CallReadyHook(ctx context.Context, nd nodedisruptionv1alpha1.NodeDisruption, timeout time.Duration) error { + svc, err := r.hookClient() + if err != nil { + return err + } + + _, err = svc.CheckReadiness(&disruption.CheckReadinessParams{ + Body: r.hookBody(nd), + HTTPClient: &http.Client{Timeout: timeout}, + }) + if err == nil { + return nil + } + if e, ok := err.(*disruption.CheckReadinessStatus425); ok { + return fmt.Errorf("retry later, in %v seconds", e.RetryAfter) + } + return err +} + +func (r *ApplicationDisruptionBudgetResolver) CallCancelHook(ctx context.Context, nd nodedisruptionv1alpha1.NodeDisruption, timeout time.Duration) error { + svc, err := r.hookClient() + if err != nil { + return err + } + + _, err = svc.CancelPreparation(&disruption.CancelPreparationParams{ + Body: r.hookBody(nd), + HTTPClient: &http.Client{Timeout: timeout}, + }) + return err +} + +func (r *ApplicationDisruptionBudgetResolver) hookClient() (disruption.ClientService, error) { + if r.hookCli != nil { + return r.hookCli, nil + } + + u, err := url.Parse(r.ApplicationDisruptionBudget.Spec.HookV2BasePath.URL) + if err != nil { + return nil, err + } + transport := httptransport.New(u.Host, u.Path, []string{u.Scheme}) + r.hookCli = disruption.New(transport, strfmt.Default) + return r.hookCli, nil +} + +func (r *ApplicationDisruptionBudgetResolver) hookBody(nd nodedisruptionv1alpha1.NodeDisruption) *appmgr.Disruption { + // Workaround potentially missing labels map without a custom method. + labels := nd.Labels + if labels == nil { + labels = map[string]string{} + } + watchedNodes := resolver.NewNodeSetFromStringList(r.ApplicationDisruptionBudget.Status.WatchedNodes) + dis := &appmgr.Disruption{ + UID: string(nd.UID), + Name: nd.Name, + CreationDate: strfmt.DateTime(nd.CreationTimestamp.Time), + StartDate: strfmt.DateTime(nd.Spec.StartDate.Time), + Duration: int64(nd.Spec.Duration.Seconds()), + DeadlineDate: strfmt.DateTime(nd.Spec.Retry.Deadline.Time), + CreatedBy: labels["app.kubernetes.io/created-by"], + Type: nd.Spec.Type, + Nodes: resolver.NodeSetToStringList(watchedNodes.Intersection(resolver.NewNodeSetFromStringList(nd.Status.DisruptedNodes))), + } + return dis +} + // Call a lifecycle hook in order to synchronously validate a Node Disruption func (r *ApplicationDisruptionBudgetResolver) CallHealthHook(ctx context.Context, nd nodedisruptionv1alpha1.NodeDisruption, timeout time.Duration) error { if r.ApplicationDisruptionBudget.Spec.HealthHook.URL == "" { diff --git a/internal/controller/budget.go b/internal/controller/budget.go index 69f24d9..caa0bd9 100644 --- a/internal/controller/budget.go +++ b/internal/controller/budget.go @@ -17,11 +17,19 @@ type Budget interface { IsImpacted(resolver.NodeSet) bool // Return the number of disruption allowed considering a list of current node disruptions TolerateDisruption(resolver.NodeSet) bool + // Return true if the budget has v2 hooks configured (for prepare, ready, cancel) + V2HooksReady() bool + // Call the prepare hook to trigger the preparation of the application for disruption + CallPrepareHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error + // Call the ready hook to validate that the application is ready for disruption + CallReadyHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error + // Call the cancel hook to cancel any preparation for disruption + CallCancelHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error // Call a lifecycle hook in order to synchronously validate a Node Disruption CallHealthHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error // Apply the budget's status to Kubernetes UpdateStatus(context.Context) error - // Get the name, namespace and kind of bduget + // Get the name, namespace and kind of budget GetNamespacedName() nodedisruptionv1alpha1.NamespacedName } @@ -49,11 +57,12 @@ func UpdateBudgetStatusMetrics(ref nodedisruptionv1alpha1.NamespacedName, status for _, disruption := range status.Disruptions { nd_state := 0 state := nodedisruptionv1alpha1.NodeDisruptionState(disruption.State) - if state == nodedisruptionv1alpha1.Pending { + switch state { + case nodedisruptionv1alpha1.Pending: nd_state = 0 - } else if state == nodedisruptionv1alpha1.Rejected { + case nodedisruptionv1alpha1.Rejected: nd_state = -1 - } else if state == nodedisruptionv1alpha1.Granted { + case nodedisruptionv1alpha1.Granted: nd_state = 1 } DisruptionBudgetDisruptions.WithLabelValues(ref.Namespace, ref.Name, ref.Kind, disruption.Name).Set(float64(nd_state)) diff --git a/internal/controller/budget_test.go b/internal/controller/budget_test.go index f4ca49a..87b6f4a 100644 --- a/internal/controller/budget_test.go +++ b/internal/controller/budget_test.go @@ -36,6 +36,22 @@ func (m *MockBudget) TolerateDisruption(resolver.NodeSet) bool { return m.tolerate } +func (m *MockBudget) V2HooksReady() bool { + return false +} + +func (m *MockBudget) CallPrepareHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error { + return nil +} + +func (m *MockBudget) CallReadyHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error { + return nil +} + +func (m *MockBudget) CallCancelHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error { + return nil +} + // Check health make a synchronous health check on the underlying resource of a budget func (m *MockBudget) CallHealthHook(context.Context, nodedisruptionv1alpha1.NodeDisruption, time.Duration) error { m.healthChecked = true @@ -78,7 +94,7 @@ func TestValidateWithBudgetConstraintsNoImpactedBudget(t *testing.T) { } budgets := []controller.Budget{&budget1, &budget2} - anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets) + anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets, nil) assert.False(t, anyFailed) assert.Equal(t, len(statuses), 0) } @@ -110,7 +126,7 @@ func TestValidationImpactedAllOk(t *testing.T) { budgets := []controller.Budget{&budget1, &budget2} - anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets) + anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets, nil) assert.False(t, anyFailed) assert.Equal(t, len(statuses), 2) } @@ -142,7 +158,7 @@ func TestValidateWithBudgetConstraintsFailAtDisruption(t *testing.T) { budgets := []controller.Budget{&budget1, &budget2} - anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets) + anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets, nil) assert.True(t, anyFailed) assert.Equal(t, len(statuses), 1) assert.False(t, statuses[0].Ok) @@ -179,7 +195,7 @@ func TestValidateWithBudgetConstraintsFailAtHealth(t *testing.T) { budgets := []controller.Budget{&budget1, &budget2} - anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets) + anyFailed, statuses := reconciler.ValidateWithBudgetConstraints(context.Background(), budgets, nil) assert.True(t, anyFailed) assert.False(t, statuses[0].Ok) assert.NotEmpty(t, statuses[0].Reason, "Rejected budget should provide a reason") diff --git a/internal/controller/nodedisruption_controller.go b/internal/controller/nodedisruption_controller.go index f447132..bfd1925 100644 --- a/internal/controller/nodedisruption_controller.go +++ b/internal/controller/nodedisruption_controller.go @@ -20,19 +20,23 @@ import ( "context" "fmt" "reflect" + "slices" "time" nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1" "github.com/criteo/node-disruption-controller/pkg/resolver" + "github.com/prometheus/client_golang/prometheus" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" - "k8s.io/utils/strings/slices" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const ( @@ -75,11 +79,11 @@ func (r *NodeDisruptionReconciler) Reconcile(ctx context.Context, req ctrl.Reque clusterResult := ctrl.Result{} nd := &nodedisruptionv1alpha1.NodeDisruption{} - err := r.Client.Get(ctx, req.NamespacedName, nd) + err := r.Get(ctx, req.NamespacedName, nd) if err != nil { if errors.IsNotFound(err) { - PruneNodeDisruptionMetrics(req.NamespacedName.Name) + PruneNodeDisruptionMetrics(req.Name) // If the ressource was not found, nothing has to be done return clusterResult, nil } @@ -128,17 +132,18 @@ func PruneNodeDisruptionMetrics(nd_name string) { // UpdateNodeDisruptionMetrics update metrics for a Node Disruption func UpdateNodeDisruptionMetrics(nd *nodedisruptionv1alpha1.NodeDisruption) { nd_state := 0 - if nd.Status.State == nodedisruptionv1alpha1.Pending { + switch nd.Status.State { + case nodedisruptionv1alpha1.Pending: nd_state = 0 NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Pending), nd.Spec.Type).Set(1) NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Granted), nd.Spec.Type).Set(0) NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Rejected), nd.Spec.Type).Set(0) - } else if nd.Status.State == nodedisruptionv1alpha1.Rejected { + case nodedisruptionv1alpha1.Rejected: nd_state = -1 NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Pending), nd.Spec.Type).Set(0) NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Rejected), nd.Spec.Type).Set(1) NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Granted), nd.Spec.Type).Set(0) - } else if nd.Status.State == nodedisruptionv1alpha1.Granted { + case nodedisruptionv1alpha1.Granted: nd_state = 1 NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Pending), nd.Spec.Type).Set(0) NodeDisruptionStateAsLabel.WithLabelValues(nd.Name, string(nodedisruptionv1alpha1.Rejected), nd.Spec.Type).Set(0) @@ -164,6 +169,7 @@ func (r *NodeDisruptionReconciler) SetupWithManager(mgr ctrl.Manager) error { r.Recorder = mgr.GetEventRecorderFor("node-disruption-controller") return ctrl.NewControllerManagedBy(mgr). For(&nodedisruptionv1alpha1.NodeDisruption{}). + WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}). // TODO(j.clerc): refactor tests to avoid skipping name validation Complete(r) } @@ -201,9 +207,10 @@ func (ndr *SingleNodeDisruptionReconciler) TryTransitionState(ctx context.Contex if err != nil { return err } - if ndr.NodeDisruption.Status.State == nodedisruptionv1alpha1.Granted { + switch ndr.NodeDisruption.Status.State { + case nodedisruptionv1alpha1.Granted: NodeDisruptionGrantedTotal.WithLabelValues(ndr.NodeDisruption.Spec.Type).Inc() - } else if ndr.NodeDisruption.Status.State == nodedisruptionv1alpha1.Rejected { + case nodedisruptionv1alpha1.Rejected: NodeDisruptionRejectedTotal.WithLabelValues(ndr.NodeDisruption.Spec.Type).Inc() } } @@ -240,7 +247,7 @@ func (ndr *SingleNodeDisruptionReconciler) tryTransitionToGranted(ctx context.Co } if !anyFailed { - anyFailed, statuses = ndr.ValidateWithBudgetConstraints(ctx, budgets) + anyFailed, statuses = ndr.ValidateWithBudgetConstraints(ctx, budgets, ndr.NodeDisruption.Status.DisruptedDisruptionBudgets) } if !anyFailed { @@ -249,12 +256,19 @@ func (ndr *SingleNodeDisruptionReconciler) tryTransitionToGranted(ctx context.Co if !nextRetryDate.IsZero() { state = nodedisruptionv1alpha1.Pending } else { + anyPreparing := slices.IndexFunc(statuses, func(s nodedisruptionv1alpha1.DisruptedBudgetStatus) bool { + return s.Preparing + }) for _, status := range statuses { - if !status.Ok { + if !status.Preparing && !status.Ok { logger.Info("Disruption rejected", "status", status) } } - state = nodedisruptionv1alpha1.Rejected + if anyPreparing >= 0 { + state = nodedisruptionv1alpha1.Pending + } else { + state = nodedisruptionv1alpha1.Rejected + } } } @@ -357,7 +371,7 @@ func (ndr *SingleNodeDisruptionReconciler) ValidateWithInternalConstraints(ctx c } // ValidateBudgetConstraints check that the Node Disruption is valid against the budgets defined in the cluster -func (ndr *SingleNodeDisruptionReconciler) ValidateWithBudgetConstraints(ctx context.Context, budgets []Budget) (anyFailed bool, statuses []nodedisruptionv1alpha1.DisruptedBudgetStatus) { +func (ndr *SingleNodeDisruptionReconciler) ValidateWithBudgetConstraints(ctx context.Context, budgets []Budget, currentStatuses []nodedisruptionv1alpha1.DisruptedBudgetStatus) (anyFailed bool, statuses []nodedisruptionv1alpha1.DisruptedBudgetStatus) { disruptedNodes := resolver.NewNodeSetFromStringList(ndr.NodeDisruption.Status.DisruptedNodes) anyFailed = false @@ -387,26 +401,84 @@ func (ndr *SingleNodeDisruptionReconciler) ValidateWithBudgetConstraints(ctx con } for _, budget := range impactedBudgets { - err := budget.CallHealthHook(ctx, ndr.NodeDisruption, ndr.Config.HealthHookTimeout) - ref := budget.GetNamespacedName() - if err != nil { - anyFailed = true - status := nodedisruptionv1alpha1.DisruptedBudgetStatus{ - Reference: ref, - Reason: fmt.Sprintf("Unhealthy: %s", err), - Ok: false, + if budget.V2HooksReady() { + idx := slices.IndexFunc(currentStatuses, func(s nodedisruptionv1alpha1.DisruptedBudgetStatus) bool { + return s.Reference == budget.GetNamespacedName() + }) + st := nodedisruptionv1alpha1.DisruptedBudgetStatus{} + if idx > -1 { + st = currentStatuses[idx] + } + status := ndr.v2HookCheck(ctx, budget, st) + if !status.Ok { + anyFailed = true + } + statuses = append(statuses, status) + } else { + status := ndr.legacyHookCheck(ctx, budget) + if !status.Ok { + anyFailed = true } statuses = append(statuses, status) - DisruptionBudgetRejectedTotal.WithLabelValues(ref.Namespace, ref.Name, ref.Kind).Inc() - break } - DisruptionBudgetGrantedTotal.WithLabelValues(ref.Namespace, ref.Name, ref.Kind).Inc() - statuses = append(statuses, nodedisruptionv1alpha1.DisruptedBudgetStatus{ - Reference: budget.GetNamespacedName(), - Reason: "", - Ok: true, - }) } return anyFailed, statuses } + +func (ndr *SingleNodeDisruptionReconciler) v2HookCheck(ctx context.Context, budget Budget, currentStatus nodedisruptionv1alpha1.DisruptedBudgetStatus) nodedisruptionv1alpha1.DisruptedBudgetStatus { + logger := log.FromContext(ctx) + + ref := budget.GetNamespacedName() + if !currentStatus.Ok && !currentStatus.Preparing { + if err := budget.CallPrepareHook(ctx, ndr.NodeDisruption, ndr.Config.HealthHookTimeout); err != nil { + logger.Error(err, "failed to call prepare hook") + return nodedisruptionv1alpha1.DisruptedBudgetStatus{ + Reference: ref, + Reason: fmt.Sprintf("cannot prepare disruption: %s", err), + Ok: false, + Preparing: false, + } + } + return nodedisruptionv1alpha1.DisruptedBudgetStatus{ + Reference: ref, + Preparing: true, + Ok: false, + } + } else if currentStatus.Preparing { + if err := budget.CallReadyHook(ctx, ndr.NodeDisruption, ndr.Config.HealthHookTimeout); err != nil { + logger.Error(err, "failed to call ready hook") + return nodedisruptionv1alpha1.DisruptedBudgetStatus{ + Reference: ref, + Reason: fmt.Sprintf("not ready for disruption: %s", err), + Ok: false, + Preparing: true, + } + } + } + return nodedisruptionv1alpha1.DisruptedBudgetStatus{ + Reference: ref, + Ok: true, + Preparing: true, + } +} + +func (ndr *SingleNodeDisruptionReconciler) legacyHookCheck(ctx context.Context, budget Budget) nodedisruptionv1alpha1.DisruptedBudgetStatus { + err := budget.CallHealthHook(ctx, ndr.NodeDisruption, ndr.Config.HealthHookTimeout) + ref := budget.GetNamespacedName() + if err != nil { + status := nodedisruptionv1alpha1.DisruptedBudgetStatus{ + Reference: ref, + Reason: fmt.Sprintf("Unhealthy: %s", err), + Ok: false, + } + DisruptionBudgetRejectedTotal.WithLabelValues(ref.Namespace, ref.Name, ref.Kind).Inc() + return status + } + DisruptionBudgetGrantedTotal.WithLabelValues(ref.Namespace, ref.Name, ref.Kind).Inc() + return nodedisruptionv1alpha1.DisruptedBudgetStatus{ + Reference: budget.GetNamespacedName(), + Reason: "", + Ok: true, + } +} diff --git a/internal/controller/nodedisruption_controller_test.go b/internal/controller/nodedisruption_controller_test.go index d0ead10..dbed1c3 100644 --- a/internal/controller/nodedisruption_controller_test.go +++ b/internal/controller/nodedisruption_controller_test.go @@ -22,9 +22,11 @@ import ( "fmt" "io" "net/http" + "net/http/httptest" "time" nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1" + "github.com/criteo/node-disruption-controller/internal/appmgr" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" @@ -61,9 +63,7 @@ func clearAllNodeDisruptionResources() { func startReconcilerWithConfig(config NodeDisruptionReconcilerConfig) context.CancelFunc { k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{ - MetricsBindAddress: "127.0.0.1:8081", - PprofBindAddress: "127.0.0.1:8082", - Scheme: scheme.Scheme, + Scheme: scheme.Scheme, }) Expect(err).ToNot(HaveOccurred()) err = (&NodeDisruptionReconciler{ @@ -101,15 +101,11 @@ func startReconcilerWithConfig(config NodeDisruptionReconcilerConfig) context.Ca } } -func startDummyHTTPServer(handle http.HandlerFunc, listenAddr string) (cancelFn func()) { - testServer := http.NewServeMux() - srv := &http.Server{Addr: listenAddr, Handler: testServer} - testServer.HandleFunc("/", handle) - go func() { - defer GinkgoRecover() - _ = srv.ListenAndServe() - }() - return func() { _ = srv.Shutdown(context.Background()) } +func startDummyHTTPServer(handle http.HandlerFunc) *httptest.Server { + m := http.NewServeMux() + m.HandleFunc("/", handle) + srv := httptest.NewServer(m) + return srv } func createNodeDisruption(name string, namespace string, nodeSelectorLabel map[string]string, disruptionType string, ctx context.Context) { @@ -231,10 +227,106 @@ var _ = Describe("NodeDisruption controller", func() { }) }) + When("using v2 hook workflow", func() { + It("calls the prepare and ready hooks before granting disruption", func() { + mockBasePath := "/api/v2" + + dis := appmgr.Disruption{} + By("Starting an http server to receive the hook") + prepareCalledCnt := 0 + readyCalledCnt := 0 + cancelCalledCnt := 0 + + checkHookFn := func(w http.ResponseWriter, req *http.Request) { + switch req.URL.Path { + case mockBasePath + "/prepare": + prepareCalledCnt++ + case mockBasePath + "/ready": + readyCalledCnt++ + case mockBasePath + "/cancel": + cancelCalledCnt++ + } + err := json.NewDecoder(req.Body).Decode(&dis) + Expect(err).Should(Succeed()) + // Validate that the hook is called with valid headers + Expect(req.Header.Get("Content-Type")).Should(Equal("application/json")) + w.WriteHeader(http.StatusOK) + } + + mockServer := startDummyHTTPServer(checkHookFn) + defer mockServer.Close() + + By("creating a budget that accepts one disruption") + ndb := &nodedisruptionv1alpha1.ApplicationDisruptionBudget{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "nodedisruption.criteo.com/v1alpha1", + Kind: "ApplicationDisruptionBudget", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "test", + Namespace: "default", + }, + Spec: nodedisruptionv1alpha1.ApplicationDisruptionBudgetSpec{ + PodSelector: metav1.LabelSelector{MatchLabels: podLabels}, + MaxDisruptions: 1, + HookV2BasePath: nodedisruptionv1alpha1.HookSpec{ + URL: mockServer.URL + mockBasePath, + }, + }, + } + Expect(k8sClient.Create(ctx, ndb)).Should(Succeed()) + + By("checking the ApplicationDisruptionBudget in synchronized") + ADBLookupKey := types.NamespacedName{Name: "test", Namespace: "default"} + createdADB := &nodedisruptionv1alpha1.ApplicationDisruptionBudget{} + Eventually(func() []string { + err := k8sClient.Get(ctx, ADBLookupKey, createdADB) + Expect(err).Should(Succeed()) + return createdADB.Status.WatchedNodes + }, timeout, interval).Should(Equal([]string{"node1"})) + + By("creating a new NodeDisruption") + disruption := &nodedisruptionv1alpha1.NodeDisruption{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "nodedisruption.criteo.com/v1alpha1", + Kind: "NodeDisruption", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: NDName, + Namespace: NDNamespace, + }, + Spec: nodedisruptionv1alpha1.NodeDisruptionSpec{ + NodeSelector: metav1.LabelSelector{MatchLabels: nodeLabels1}, + }, + } + Expect(k8sClient.Create(ctx, disruption.DeepCopy())).Should(Succeed()) + + NDLookupKey := types.NamespacedName{Name: NDName, Namespace: NDNamespace} + createdDisruption := &nodedisruptionv1alpha1.NodeDisruption{} + + By("checking the NodeDisruption is being granted") + Eventually(func() nodedisruptionv1alpha1.NodeDisruptionState { + err := k8sClient.Get(ctx, NDLookupKey, createdDisruption) + if err != nil { + panic("should be able to get") + } + return createdDisruption.Status.State + }, timeout, interval).Should(Equal(nodedisruptionv1alpha1.Granted)) + + By("ensure the disrupted node selector is correct") + Expect(createdDisruption.Status.DisruptedNodes).Should(Equal([]string{"node1", "node2"})) + + By("checking that the v2 hooks are properly called") + Expect(prepareCalledCnt).Should(Equal(1)) + Expect(readyCalledCnt).Should(Equal(1)) + Expect(cancelCalledCnt).Should(Equal(0)) + Expect(dis.Name).Should(Equal(NDName)) + }) + }) + When("there are no budgets in the cluster", func() { It("calls the lifecycle hook", func() { - mockHost := "localhost:8120" - mockURL := "/testurl" + mockPath := "/testurl" By("Starting an http server to receive the hook") var ( @@ -250,12 +342,12 @@ var _ = Describe("NodeDisruption controller", func() { hookURL = req.URL.String() hookCallCount++ // Validate that the hook is called with valid headers - Expect(req.Header["Content-Type"][0]).Should(Equal("application/json")) + Expect(req.Header.Get("Content-Type")).Should(Equal("application/json")) w.WriteHeader(http.StatusOK) } - httpCancel := startDummyHTTPServer(checkHookFn, mockHost) - defer httpCancel() + mockServer := startDummyHTTPServer(checkHookFn) + defer mockServer.Close() By("creating a budget that accepts one disruption") ndb := &nodedisruptionv1alpha1.ApplicationDisruptionBudget{ @@ -270,8 +362,8 @@ var _ = Describe("NodeDisruption controller", func() { Spec: nodedisruptionv1alpha1.ApplicationDisruptionBudgetSpec{ PodSelector: metav1.LabelSelector{MatchLabels: podLabels}, MaxDisruptions: 1, - HealthHook: nodedisruptionv1alpha1.HealthHookSpec{ - URL: fmt.Sprintf("http://%s%s", mockHost, mockURL), + HealthHook: nodedisruptionv1alpha1.HookSpec{ + URL: mockServer.URL + mockPath, }, }, } @@ -319,7 +411,7 @@ var _ = Describe("NodeDisruption controller", func() { By("checking that the lifecyclehook was properly called") Expect(hookCallCount).Should(Equal(1)) - Expect(hookURL).Should(Equal(mockURL)) + Expect(hookURL).Should(Equal(mockPath)) HookDisruption := &nodedisruptionv1alpha1.NodeDisruption{} Expect(json.Unmarshal(hookBody, HookDisruption)).Should(Succeed()) Expect(HookDisruption.Name).Should(Equal(disruption.Name)) diff --git a/internal/controller/nodedisruptionbudget_controller.go b/internal/controller/nodedisruptionbudget_controller.go index f043696..121616b 100644 --- a/internal/controller/nodedisruptionbudget_controller.go +++ b/internal/controller/nodedisruptionbudget_controller.go @@ -22,21 +22,23 @@ import ( "reflect" "time" + nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1" + + "github.com/criteo/node-disruption-controller/pkg/resolver" + "github.com/prometheus/client_golang/prometheus" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + "k8s.io/utils/ptr" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" - - nodedisruptionv1alpha1 "github.com/criteo/node-disruption-controller/api/v1alpha1" - "github.com/criteo/node-disruption-controller/pkg/resolver" - "github.com/prometheus/client_golang/prometheus" ) // NodeDisruptionBudgetReconciler reconciles a NodeDisruptionBudget object @@ -61,7 +63,7 @@ type NodeDisruptionBudgetReconciler struct { func (r *NodeDisruptionBudgetReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx) ndb := &nodedisruptionv1alpha1.NodeDisruptionBudget{} - err := r.Client.Get(ctx, req.NamespacedName, ndb) + err := r.Get(ctx, req.NamespacedName, ndb) ref := nodedisruptionv1alpha1.NamespacedName{ Namespace: req.Namespace, Name: req.Name, @@ -118,7 +120,7 @@ func (r *NodeDisruptionBudgetReconciler) MapFuncBuilder() handler.MapFunc { // Look for all NDBs in the namespace, then see if they match the object return func(ctx context.Context, object client.Object) (requests []reconcile.Request) { ndbs := nodedisruptionv1alpha1.NodeDisruptionBudgetList{} - err := r.Client.List(ctx, &ndbs, &client.ListOptions{Namespace: object.GetNamespace()}) + err := r.List(ctx, &ndbs, &client.ListOptions{Namespace: object.GetNamespace()}) if err != nil { // We cannot return an error so at least it should be logged logger := log.FromContext(context.Background()) @@ -143,6 +145,7 @@ func (r *NodeDisruptionBudgetReconciler) MapFuncBuilder() handler.MapFunc { // SetupWithManager sets up the controller with the Manager. func (r *NodeDisruptionBudgetReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). + WithOptions(controller.TypedOptions[reconcile.Request]{SkipNameValidation: ptr.To(true)}). // TODO(j.clerc): refactor tests to avoid skipping name validation For(&nodedisruptionv1alpha1.NodeDisruptionBudget{}). Watches( &corev1.Node{}, @@ -197,6 +200,22 @@ func (r *NodeDisruptionBudgetResolver) TolerateDisruption(disruptedNodes resolve return r.NodeDisruptionBudget.Status.DisruptionsAllowed-disruptedNodesCount >= 0 } +func (r *NodeDisruptionBudgetResolver) V2HooksReady() bool { + return false +} + +func (r *NodeDisruptionBudgetResolver) CallPrepareHook(ctx context.Context, nd nodedisruptionv1alpha1.NodeDisruption, timeout time.Duration) error { + return nil +} + +func (r *NodeDisruptionBudgetResolver) CallReadyHook(ctx context.Context, nd nodedisruptionv1alpha1.NodeDisruption, timeout time.Duration) error { + return nil +} + +func (r *NodeDisruptionBudgetResolver) CallCancelHook(ctx context.Context, nd nodedisruptionv1alpha1.NodeDisruption, timeout time.Duration) error { + return nil +} + // Call a lifecycle hook in order to synchronously validate a Node Disruption func (r *NodeDisruptionBudgetResolver) CallHealthHook(_ context.Context, _ nodedisruptionv1alpha1.NodeDisruption, _ time.Duration) error { return nil diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index ed1ea1e..e0e1980 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -85,7 +85,7 @@ func newPVC(name, namespace, pvName string, labels map[string]string) corev1.Per Spec: corev1.PersistentVolumeClaimSpec{ VolumeName: pvName, AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce}, - Resources: corev1.ResourceRequirements{Requests: resources}, + Resources: corev1.VolumeResourceRequirements{Requests: resources}, }, Status: corev1.PersistentVolumeClaimStatus{ Phase: corev1.ClaimBound, diff --git a/pkg/resolver/resolver.go b/pkg/resolver/resolver.go index a914b7c..db798a4 100644 --- a/pkg/resolver/resolver.go +++ b/pkg/resolver/resolver.go @@ -248,7 +248,7 @@ func (r *Resolver) GetNodeFromNodeSelector(ctx context.Context, nodeSelector met } for _, node := range nodes.Items { - nodeNames.Insert(node.ObjectMeta.Name) + nodeNames.Insert(node.Name) } return nodeSet, nil }