diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..e48836a0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: gomod + directory: "/" + schedule: { interval: weekly } + - package-ecosystem: github-actions + directory: "/" + schedule: { interval: weekly } + - package-ecosystem: docker + directory: "/" + schedule: { interval: weekly } diff --git a/.github/workflows/on-pr-test.yml b/.github/workflows/on-pr-test.yml index 15b90be4..2a14780d 100644 --- a/.github/workflows/on-pr-test.yml +++ b/.github/workflows/on-pr-test.yml @@ -5,19 +5,21 @@ on: paths-ignore: - "docs/**" +permissions: + contents: read + jobs: test: runs-on: ubuntu-latest - strategy: - matrix: - go-version: [1.24] - steps: - - name: Checkout commit - uses: actions/checkout@v2 - - name: Set up Go ${{ matrix.go-version }} - uses: actions/setup-go@v2 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: - go-version: ${{ matrix.go-version }} - - name: Execute tests - run: make test-cov + go-version: "1.26" + cache: true + - name: Build + run: go build ./... + - name: Vet + run: go vet ./... + - name: Test + run: go test ./... -race diff --git a/.github/workflows/on-release-publish.yml b/.github/workflows/on-release-publish.yml index 73ba8c6f..51e1b2e8 100644 --- a/.github/workflows/on-release-publish.yml +++ b/.github/workflows/on-release-publish.yml @@ -9,15 +9,17 @@ on: jobs: create-release: runs-on: ubuntu-latest + permissions: + contents: write steps: - name: Checkout commit - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Update VERSION file run: |- echo "${{ github.ref_name }}" > VERSION - name: Create Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -26,26 +28,33 @@ jobs: # [machine-controller-manager-provider-hcloud] ${{ github.ref_name }} on-publish: + needs: create-release runs-on: ubuntu-latest + permissions: + contents: read + packages: write steps: - name: Checkout commit - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Update VERSION file run: |- echo "${{ github.ref_name }}" > VERSION + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} password: ${{ secrets.GITHUB_TOKEN }} - name: Publish image - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v6 with: push: true + platforms: linux/amd64,linux/arm64 tags: | - ghcr.io/23technologies/machine-controller-manager-provider-hcloud:latest - ghcr.io/23technologies/machine-controller-manager-provider-hcloud:${{ github.ref_name }} + ghcr.io/${{ github.repository_owner }}/machine-controller-manager-provider-hcloud:latest + ghcr.io/${{ github.repository_owner }}/machine-controller-manager-provider-hcloud:${{ github.ref_name }} diff --git a/Dockerfile b/Dockerfile index 96a078a8..4fafb077 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ ############# builder ############# -FROM golang:1.24.4 AS builder +FROM golang:1.26.2 AS builder -WORKDIR /go/src/github.com/23technologies/machine-controller-manager-provider-hcloud +WORKDIR /go/src/github.com/opendefensecloud/machine-controller-manager-provider-hcloud COPY . . RUN make install diff --git a/Makefile b/Makefile index 55d306e7..dd155d2c 100644 --- a/Makefile +++ b/Makefile @@ -61,7 +61,7 @@ start: .PHONY: install install: - @LD_FLAGS="-w -X github.com/23technologies/$(NAME)/pkg/version.Version=$(VERSION)" \ + @LD_FLAGS="-w -X github.com/opendefensecloud/$(NAME)/pkg/version.Version=$(VERSION)" \ bash $(GARDENER_HACK_DIR)/install.sh ./... .PHONY: generate diff --git a/VERSION b/VERSION index bffd7fcf..0f8e4715 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v0.3.0-dev +v0.4.0-odp.1 diff --git a/cmd/machine-controller/main.go b/cmd/machine-controller/main.go index 5d2c7047..96489a85 100644 --- a/cmd/machine-controller/main.go +++ b/cmd/machine-controller/main.go @@ -24,7 +24,7 @@ import ( _ "github.com/gardener/machine-controller-manager/pkg/util/client/metrics/prometheus" // for client metric registration "github.com/spf13/pflag" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/cmd" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/cmd" ) // main is the executable entry point. diff --git a/docs/superpowers/plans/2026-07-19-catch-up-extension-toolchain.md b/docs/superpowers/plans/2026-07-19-catch-up-extension-toolchain.md new file mode 100644 index 00000000..7943c4fa --- /dev/null +++ b/docs/superpowers/plans/2026-07-19-catch-up-extension-toolchain.md @@ -0,0 +1,731 @@ +# Catch Up to Extension Toolchain Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Bring `machine-controller-manager-provider-hcloud` up to the exact toolchain and `providerSpec` contract of `opendefensecloud/gardener-extension-provider-hcloud`, migrate to hcloud-go v2, rename the module to `opendefensecloud`, and fold in the P0 correctness fixes — producing a new release the extension can pin. + +**Architecture:** The Go source imports **no `gardener/gardener` packages** (verified) — only MCM, hcloud-go, and k8s. So the version catch-up is (a) a build-tooling/go.sum bump for gardener + code-generation, (b) a straight recompile against MCM v0.62 (driver interface unchanged), (c) a real SDK migration hcloud-go v1→v2 where resource IDs change `int`→`int64`, and (d) additive `providerSpec` fields (`tags`, `extraConfig`) plus the P0 bug fixes. Everything below must land together for the module to compile, so it ships as one coordinated branch cut as `v0.4.0-odp.1`. + +**Tech Stack:** Go 1.26.2, gardener/gardener v1.146.4 (build tooling only), machine-controller-manager v0.62.1, hetznercloud/hcloud-go/v2 v2.44.0, k8s.io/* v0.35.5, sigs.k8s.io/controller-runtime v0.23.3, onsi/ginkgo/v2 v2.32.0 + gomega v1.42.0, go.uber.org/mock v0.6.0. + +## Global Constraints + +- Module path MUST become `github.com/opendefensecloud/machine-controller-manager-provider-hcloud` (was `github.com/23technologies/...`). No `23technologies` string may remain in `cmd/`, `pkg/`, `Makefile`, or `Dockerfile` after Task 1. +- Dependency versions are pinned to the EXACT values the extension's `go.mod` uses (listed in Tech Stack). Do not float to newer. +- hcloud-go MUST be the `/v2` module (`github.com/hetznercloud/hcloud-go/v2/hcloud`). No import of the v1 path (`github.com/hetznercloud/hcloud-go/hcloud`) may remain. +- All resource IDs (server, floating IP, placement group) are `int64` after Task 3. +- The MCM driver interface is unchanged v0.58→v0.62: keep `CreateMachine`, `InitializeMachine`, `DeleteMachine`, `GetMachineStatus`, `ListMachines`, `GetVolumeIDs`; keep the `Unimplemented` stubs. +- `providerSpec` fields the provider MUST accept (contract from the extension's `charts/internal/machineclass/templates/machineclass.yaml`): `cluster`, `zone`, `serverType`, `imageName`, `sshFingerprint`, `placementGroupID`, `networkName`, `floatingPoolName`, `tags` (map[string]string), `extraConfig` (map[string]string). +- After release, the extension's `charts/images.yaml` tag `machine-controller-manager-provider-hcloud` (currently `v0.3.2-odp.1`) must be bumped to the new tag — tracked as a handoff item, not part of this repo's tasks. +- Every task ends green: `go build ./...` and `go test ./...` pass before commit. + +--- + +## Task 1: Rename module 23technologies → opendefensecloud + +**Files:** +- Modify: `go.mod:1` (module line) +- Modify: all `.go` files under `cmd/` and `pkg/` importing the old path (13 import sites across `pkg/hcloud/*`, `pkg/hcloud/apis/*`, `cmd/machine-controller/main.go`, `pkg/cmd/app.go`) +- Modify: `Makefile:64` (LD_FLAGS `-X` path) +- Modify: `Dockerfile:4` (WORKDIR) + +**Interfaces:** +- Produces: the module import prefix `github.com/opendefensecloud/machine-controller-manager-provider-hcloud`, used by every later task's import lines. + +- [ ] **Step 1: Rewrite the module path and all references** + +```bash +OLD='github.com/23technologies/machine-controller-manager-provider-hcloud' +NEW='github.com/opendefensecloud/machine-controller-manager-provider-hcloud' +go mod edit -module "$NEW" +grep -rl "$OLD" cmd pkg Makefile Dockerfile | xargs sed -i '' "s#$OLD#$NEW#g" +``` + +- [ ] **Step 2: Verify no stale references remain** + +Run: `grep -rn "23technologies" cmd pkg Makefile Dockerfile` +Expected: no output (exit 1). + +- [ ] **Step 3: Build to confirm the rename compiles (still on old deps)** + +Run: `go build ./...` +Expected: exit 0 (this task changes no dependencies, only the self-import prefix). + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "chore: rename module 23technologies -> opendefensecloud" +``` + +--- + +## Task 2: Bump dependencies to the extension's pinned versions + +This task is **compile-driven and iterative** — bump, tidy, build, fix. The known likely breakage points are called out; fix them as the compiler surfaces them. + +**Files:** +- Modify: `go.mod` (go directive + require versions), `go.sum` (regenerated) +- Modify: `hack/tools.go` (gardener hack tooling moved to submodules in recent gardener; drop deprecated `golang.org/x/lint`) +- Modify: `Dockerfile:2` (builder image → `golang:1.26.2`) + +**Interfaces:** +- Produces: go.mod on `go 1.26.2`, gardener v1.146.4, MCM v0.62.1, hcloud-go/v2 v2.44.0, k8s v0.35.5, controller-runtime v0.23.3, ginkgo v2.32.0, gomega v1.42.0, go.uber.org/mock v0.6.0. Task 3 depends on the hcloud-go/v2 module being present. + +- [ ] **Step 1: Set the go directive and toolchain** + +```bash +go mod edit -go=1.26.2 -toolchain=go1.26.2 +``` + +- [ ] **Step 2: Pin the core dependencies to the extension's versions** + +```bash +go mod edit \ + -require=github.com/gardener/gardener@v1.146.4 \ + -require=github.com/gardener/machine-controller-manager@v0.62.1 \ + -require=github.com/hetznercloud/hcloud-go/v2@v2.44.0 \ + -require=k8s.io/api@v0.35.5 \ + -require=k8s.io/apimachinery@v0.35.5 \ + -require=k8s.io/client-go@v0.35.5 \ + -require=k8s.io/code-generator@v0.35.5 \ + -require=k8s.io/component-base@v0.35.5 \ + -require=sigs.k8s.io/controller-runtime@v0.23.3 \ + -require=github.com/onsi/ginkgo/v2@v2.32.0 \ + -require=github.com/onsi/gomega@v1.42.0 \ + -require=go.uber.org/mock@v0.6.0 +go mod edit -droprequire=golang.org/x/lint +``` + +- [ ] **Step 3: Update `hack/tools.go` for the new gardener module layout and drop golint** + +In recent gardener the `hack` tooling is consumed via the `github.com/gardener/gardener/hack/tools` submodule and `golang.org/x/lint/golint` is dropped. Replace the file body's import block with: + +```go +import ( + _ "github.com/ahmetb/gen-crd-api-reference-docs" + _ "github.com/gardener/gardener/hack" + _ "github.com/gardener/gardener/hack/api-reference/template" + _ "github.com/onsi/ginkgo/v2" + _ "github.com/onsi/gomega" + _ "k8s.io/code-generator" + _ "sigs.k8s.io/controller-runtime" +) +``` + +> If `go mod tidy` in Step 4 reports that `github.com/gardener/gardener/hack` or `.../hack/.ci` has moved to a submodule (e.g. `github.com/gardener/gardener/hack/tools`), follow the error: add the submodule `require` the extension's go.mod uses (`github.com/gardener/gardener/hack/tools v1.146.4`, `github.com/gardener/gardener/pkg/apis v1.146.4`) and adjust the blank imports to the paths that actually resolve. The extension repo's `go.mod` + `hack/tools.go` are the reference. + +- [ ] **Step 4: Tidy and download** + +Run: `go mod tidy && go mod download` +Expected: completes; go.sum regenerated. Resolve any "missing go.sum entry" / "module moved" errors by following the tidy output (see note in Step 3). + +- [ ] **Step 5: Build — expect hcloud-go v1 breakage only** + +Run: `go build ./... 2>&1 | head -40` +Expected: the ONLY errors are in the 5 files still importing the hcloud-go **v1** path (`client.go`, `utils.go`, `machine_controller.go`, `mock/test_env.go`, `mock/machine_controller.go`) — those are fixed in Task 3. There should be no MCM or k8s API breakage (MCM interface unchanged; k8s minor bump is source-compatible for the small surface used in `cmd/`/`pkg/cmd`). If MCM/k8s errors appear, fix them minimally here (e.g. renamed helper in `pkg/cmd/app.go`). + +- [ ] **Step 6: Bump the Dockerfile builder image** + +In `Dockerfile:2` change `FROM golang:1.24.4 AS builder` to: + +```dockerfile +FROM golang:1.26.2 AS builder +``` + +- [ ] **Step 7: Commit (build still red on hcloud-go — that's expected and noted in the message)** + +```bash +git add go.mod go.sum hack/tools.go Dockerfile +git commit -m "chore: bump deps to extension pins (gardener v1.146.4, MCM v0.62.1, k8s v0.35.5, go 1.26.2); hcloud-go v2 migration follows" +``` + +--- + +## Task 3: Migrate hcloud-go v1 → v2 (int → int64) + +**Files:** +- Modify: `pkg/hcloud/apis/client.go:24`, `pkg/hcloud/apis/utils.go:28-29`, `pkg/hcloud/machine_controller.go:30`, `pkg/hcloud/apis/mock/test_env.go:24`, `pkg/hcloud/apis/mock/machine_controller.go` (imports) +- Modify: `pkg/hcloud/types.go:20-23` (ID field types) +- Modify: `pkg/hcloud/apis/transcoder/provider_id.go` (ID type + string conversions) +- Test: `pkg/hcloud/apis/transcoder/provider_id_test.go` (int64 literals) + +**Interfaces:** +- Produces: `transcoder.DecodeServerIDFromProviderID(providerID string) (int64, error)`, `transcoder.EncodeProviderID(zone string, serverID int64) string`, `transcoder.ServerData{ID int64; Zone string}`, `hcloud.CreateMachineMethodData{ServerID int64; FloatingIPID int64}` — consumed by `machine_controller.go` in Tasks 4 & 5. + +- [ ] **Step 1: Update the test to expect int64 (write the failing expectation first)** + +In `pkg/hcloud/apis/transcoder/provider_id_test.go`, ensure the decode assertions compare against `int64`. Add/adjust: + +```go +It("should decode the server ID as int64", func() { + id, err := DecodeServerIDFromProviderID("hcloud:///hel1-dc2/12345") + Expect(err).NotTo(HaveOccurred()) + Expect(id).To(Equal(int64(12345))) +}) +``` + +- [ ] **Step 2: Run it to confirm it fails to compile (old signature returns int)** + +Run: `go test ./pkg/hcloud/apis/transcoder/... 2>&1 | head` +Expected: compile error — `int64` does not match the current `int` return of `DecodeServerIDFromProviderID`. + +- [ ] **Step 3: Rewrite the hcloud-go import path in all 5 files** + +```bash +grep -rl 'hetznercloud/hcloud-go/hcloud' pkg | \ + xargs sed -i '' 's#github.com/hetznercloud/hcloud-go/hcloud#github.com/hetznercloud/hcloud-go/v2/hcloud#g' +``` + +- [ ] **Step 4: Change ID field types to int64 in `pkg/hcloud/types.go`** + +```go +type CreateMachineMethodData struct { + ServerID int64 + FloatingIPID int64 +} +``` + +- [ ] **Step 5: Update `pkg/hcloud/apis/transcoder/provider_id.go` to int64** + +`ServerData.ID` → `int64`; `DecodeServerIDFromProviderID` returns `(int64, error)` with error sentinel `-1`; parse with `ParseInt`; format with `FormatInt`; `EncodeProviderID` takes `int64`: + +```go +// ServerData describes a server +type ServerData struct { + ID int64 + Zone string +} +``` + +In `DecodeServerDataFromProviderID`, replace the `strconv.Atoi` block: + +```go + serverID, err := strconv.ParseInt(providerIDData[1], 10, 64) + if err != nil { + return nil, fmt.Errorf("ServerID found is invalid: %v", err) + } +``` + +`DecodeServerIDFromProviderID`: + +```go +func DecodeServerIDFromProviderID(providerID string) (int64, error) { + serverData, err := DecodeServerDataFromProviderID(providerID) + if err != nil { + return -1, err + } + return serverData.ID, nil +} +``` + +`DecodeServerIDAsStringFromProviderID` conversion: + +```go + return strconv.FormatInt(serverData.ID, 10), nil +``` + +`EncodeProviderID`: + +```go +func EncodeProviderID(zone string, serverID int64) string { + return fmt.Sprintf("hcloud:///%s/%d", url.PathEscape(zone), serverID) +} +``` + +- [ ] **Step 6: Fix remaining int64 flow-through in `machine_controller.go`** + +Build and fix each site the compiler flags. Known sites: the `serverID` local in `GetMachineStatus` (declared via `transcoder.DecodeServerIDFromProviderID`, now int64 — remove the redundant `int` declaration at the top `var serverID int` and let it be int64), the `PlacementGroupID` parse (`strconv.Atoi` → `strconv.ParseInt(providerSpec.PlacementGroupID, 10, 64)` returning int64 for `PlacementGroup.GetByID`), and `resultData.ServerID`/`FloatingIPID` assignments (now int64 from `serverResult.Server.ID` / `ipResult.FloatingIP.ID`, which are int64 in v2). + +- [ ] **Step 7: Build the whole module** + +Run: `go build ./... 2>&1 | head -40` +Expected: exit 0. If the mock package fails, fix `mock/test_env.go` / `mock/machine_controller.go` ID literals to int64 and any v2-renamed helper (the `datacenter` field was already removed upstream — see commit `3e2b178` — so the mock should not reference it). + +- [ ] **Step 8: Run the full test suite** + +Run: `go test ./... 2>&1 | tail -30` +Expected: PASS. Fix any int/int64 literal mismatches in `*_test.go` and mock handlers. + +- [ ] **Step 9: Commit** + +```bash +git add -A +git commit -m "feat: migrate to hcloud-go v2 (resource IDs int -> int64)" +``` + +--- + +## Task 4: Add `tags` and `extraConfig` to ProviderSpec and apply tags to server labels + +**Files:** +- Modify: `pkg/hcloud/apis/provider_spec.go` (add fields) +- Modify: `pkg/hcloud/machine_controller.go:127-140` (merge `Tags` into `ServerCreateOpts.Labels`) +- Test: `pkg/hcloud/apis/transcoder/machine_class_json_test.go` (decode tags/extraConfig) + +**Interfaces:** +- Consumes: `apis.ProviderSpec` from Task 1. +- Produces: `apis.ProviderSpec.Tags map[string]string` and `apis.ProviderSpec.ExtraConfig map[string]string`, consumed by `CreateMachine`. + +- [ ] **Step 1: Write the failing decode test** + +In `pkg/hcloud/apis/transcoder/machine_class_json_test.go`, add a case that decodes a providerSpec carrying `tags` and asserts they survive. Use the mock provider spec builder or inline JSON: + +```go +It("should decode tags and extraConfig", func() { + raw := []byte(`{"cluster":"c","zone":"hel1-dc2","serverType":"cx22","imageName":"ubuntu-24.04","sshFingerprint":"aa:bb","tags":{"mcm.gardener.cloud/cluster":"c","mcm.gardener.cloud/role":"node"},"extraConfig":{"foo":"bar"}}`) + mc := &v1alpha1.MachineClass{ProviderSpec: runtime.RawExtension{Raw: raw}} + spec, err := DecodeProviderSpecFromMachineClass(mc, providerSecret) + Expect(err).NotTo(HaveOccurred()) + Expect(spec.Tags).To(HaveKeyWithValue("mcm.gardener.cloud/role", "node")) + Expect(spec.ExtraConfig).To(HaveKeyWithValue("foo", "bar")) +}) +``` + +- [ ] **Step 2: Run it to confirm it fails** + +Run: `go test ./pkg/hcloud/apis/transcoder/... -run Decode 2>&1 | head` +Expected: compile error — `spec.Tags` / `spec.ExtraConfig` undefined. + +- [ ] **Step 3: Add the fields to `pkg/hcloud/apis/provider_spec.go`** + +```go +type ProviderSpec struct { + Cluster string `json:"cluster"` + Zone string `json:"zone"` + ServerType string `json:"serverType"` + ImageName string `json:"imageName"` + SSHFingerprint string `json:"sshFingerprint"` + + PlacementGroupID string `json:"placementGroupID,omitempty"` + FloatingPoolName string `json:"floatingPoolName,omitempty"` + NetworkName string `json:"networkName,omitempty"` + + // Tags are additional labels applied to the created Hetzner server. The extension + // supplies mcm.gardener.cloud/cluster and mcm.gardener.cloud/role here. + Tags map[string]string `json:"tags,omitempty"` + // ExtraConfig carries per-machine-type "additional VM options" from the CloudProfile's + // MachineTypeOptions. Reserved: currently accepted but not applied (no Hetzner semantic yet). + ExtraConfig map[string]string `json:"extraConfig,omitempty"` +} +``` + +- [ ] **Step 4: Run the decode test to confirm it passes** + +Run: `go test ./pkg/hcloud/apis/transcoder/... -run Decode -v 2>&1 | tail` +Expected: PASS. + +- [ ] **Step 5: Merge `Tags` into the server labels in `CreateMachine`** + +In `pkg/hcloud/machine_controller.go`, after the `opts` literal (which sets the topology + cluster/role labels), merge provider-supplied tags so they augment (and can override) the defaults, without dropping the keys `ListMachines` selects on: + +```go + for k, v := range providerSpec.Tags { + opts.Labels[k] = v + } +``` + +Insert this immediately after the `opts := hcloud.ServerCreateOpts{...}` block (after line ~140) and before the SSH-key lookup. + +- [ ] **Step 6: Build and test** + +Run: `go build ./... && go test ./... 2>&1 | tail -20` +Expected: PASS. + +- [ ] **Step 7: Commit** + +```bash +git add -A +git commit -m "feat: accept providerSpec.tags/extraConfig; apply tags to server labels (extension contract)" +``` + +--- + +## Task 5: P0 correctness fixes + +Five independent fixes. Each is test-first where a unit test is feasible; the placement-group and cleanup fixes are exercised via the mock. + +### Task 5a: Decouple placement group from FloatingPoolName + +**Files:** Modify `pkg/hcloud/machine_controller.go:174-194`. Test: `pkg/hcloud/machine_controller_test.go`. + +- [ ] **Step 1: Fix the gate and the parse** + +Change the placement-group block guard from `if providerSpec.FloatingPoolName != ""` to `if providerSpec.PlacementGroupID != ""`, and parse the ID as int64: + +```go + if providerSpec.PlacementGroupID != "" { + placementGroupID, err := strconv.ParseInt(providerSpec.PlacementGroupID, 10, 64) + if err != nil { + return nil, status.Error(codes.InvalidArgument, err.Error()) + } + + placementGroup, _, err := client.PlacementGroup.GetByID(ctx, placementGroupID) + if err != nil { + return nil, status.Error(codes.Unavailable, err.Error()) + } + + _, _, err = client.Server.AddToPlacementGroup(ctx, server, placementGroup) + if err != nil { + return nil, status.Error(codes.Unavailable, err.Error()) + } + + server, err = apis.WaitForActionsAndGetServer(ctx, client, server) + if err != nil { + return nil, status.Error(codes.Unknown, err.Error()) + } + } +``` + +- [ ] **Step 2: Add a CreateMachine test that sets PlacementGroupID (no floating pool)** + +Extend the create test / mock so the provider spec has `PlacementGroupID: "42"` and no floating pool, and assert `CreateMachine` succeeds and the mock's `/placement_groups/42` + `AddToPlacementGroup` handler was hit (the mock scaffolding already exists at `mock/machine_controller.go` ~L401-477). Follow the existing test style in `machine_controller_test.go`. + +- [ ] **Step 3: Run and confirm pass** + +Run: `go test ./pkg/hcloud/... -run CreateMachine 2>&1 | tail` +Expected: PASS. + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "fix: apply placement group based on PlacementGroupID, not FloatingPoolName" +``` + +### Task 5b: Never delete a foreign server in create-error cleanup + +**Files:** Modify `pkg/hcloud/machine_controller.go:331-351` (`createMachineOnErrorCleanup`). + +- [ ] **Step 1: Delete only servers this request created** + +Remove the name-based fallback so cleanup acts solely on the ID this request recorded: + +```go +func (p *MachineProvider) createMachineOnErrorCleanup(ctx context.Context, req *driver.CreateMachineRequest) { + client := apis.GetClientForToken(string(req.Secret.Data["token"])) + resultData := ctx.Value(CtxWrapDataKey("MethodData")).(*CreateMachineMethodData) + + if resultData.ServerID != 0 { + server, _, _ := client.Server.GetByID(ctx, resultData.ServerID) + if nil != server { + _, _ = client.Server.Delete(ctx, server) + } + } + + if resultData.FloatingIPID != 0 { + floatingIP, _, _ := client.FloatingIP.GetByID(ctx, resultData.FloatingIPID) + if nil != floatingIP { + _, _ = client.FloatingIP.Delete(ctx, floatingIP) + } + } +} +``` + +- [ ] **Step 2: Build and run existing tests** + +Run: `go test ./pkg/hcloud/... 2>&1 | tail` +Expected: PASS (existing cleanup tests still pass; foreign-server name collision no longer deletes). + +- [ ] **Step 3: Commit** + +```bash +git add -A +git commit -m "fix: cleanup only deletes servers this request created (never by name)" +``` + +### Task 5c: Check the GetByName error before create + +**Files:** Modify `pkg/hcloud/machine_controller.go:87`. + +- [ ] **Step 1: Handle the lookup error** + +```go + server, _, err := client.Server.GetByName(ctx, machine.Name) + if err != nil { + return nil, status.Error(codes.Unavailable, err.Error()) + } +``` + +(Change the `:=` on the existing `providerSpec, err :=` flow as needed so `err` is reused, not shadowed; the function already has `err` in scope from the `DecodeProviderSpecFromMachineClass` call above.) + +- [ ] **Step 2: Build and test** + +Run: `go build ./... && go test ./pkg/hcloud/... 2>&1 | tail` +Expected: PASS. + +- [ ] **Step 3: Commit** + +```bash +git add -A +git commit -m "fix: fail create on transient Server.GetByName error instead of ignoring it" +``` + +### Task 5d: Fix nil-panic on null providerSpec + re-enable secret validation + +**Files:** Modify `pkg/hcloud/apis/transcoder/machine_class_json.go:39-48`, `pkg/hcloud/apis/validation/validation.go`. Test: `pkg/hcloud/apis/validation/validation_test.go`, `pkg/hcloud/apis/transcoder/machine_class_json_test.go`. + +- [ ] **Step 1: Write failing tests** + +In `validation_test.go`, add a case: a spec that is valid but a secret missing `token` yields an error. In `machine_class_json_test.go`, add: `ProviderSpec.Raw = []byte("null")` returns an error (not a panic). + +```go +It("should reject a null providerSpec without panicking", func() { + mc := &v1alpha1.MachineClass{ProviderSpec: runtime.RawExtension{Raw: []byte("null")}} + _, err := DecodeProviderSpecFromMachineClass(mc, providerSecret) + Expect(err).To(HaveOccurred()) +}) +``` + +- [ ] **Step 2: Run to confirm failure (panic / missing validation)** + +Run: `go test ./pkg/hcloud/apis/... 2>&1 | head` +Expected: the null case panics or the secret case has no error. + +- [ ] **Step 3: Decode into a value + nil-guard in `machine_class_json.go`** + +```go + // Extract providerSpec + var providerSpec apis.ProviderSpec + + if machineClass == nil { + return nil, errors.New("MachineClass provided is nil") + } + + jsonErr := json.Unmarshal(machineClass.ProviderSpec.Raw, &providerSpec) + if jsonErr != nil { + return nil, fmt.Errorf("failed to parse JSON data provided as ProviderSpec: %v", jsonErr) + } + + validationErr := validation.ValidateHCloudProviderSpec(&providerSpec, secret) + if len(validationErr) > 0 { + return nil, fmt.Errorf("error while validating ProviderSpec %v", validationErr) + } + + return &providerSpec, nil +``` + +(Decoding `null` into a value struct leaves it zero-valued, and the required-field validation then rejects it cleanly. Also switch the guard from `!= nil` to `len(...) > 0`.) + +- [ ] **Step 4: Re-enable secret validation in `validation.go`** + +Replace the commented line with real checks and add a `PlacementGroupID` numeric check: + +```go + if spec.SSHFingerprint == "" { + allErrs = append(allErrs, fmt.Errorf("sshFingerprint is a required field")) + } + if spec.PlacementGroupID != "" { + if _, err := strconv.ParseInt(spec.PlacementGroupID, 10, 64); err != nil { + allErrs = append(allErrs, fmt.Errorf("placementGroupID must be numeric: %v", err)) + } + } + if secrets != nil { + if len(secrets.Data["token"]) == 0 { + allErrs = append(allErrs, fmt.Errorf("token is required in the secret")) + } + if len(secrets.Data["userData"]) == 0 { + allErrs = append(allErrs, fmt.Errorf("userData is required in the secret")) + } + } + + return allErrs +``` + +Add `"strconv"` to the imports. + +- [ ] **Step 5: Run tests** + +Run: `go test ./pkg/hcloud/apis/... 2>&1 | tail` +Expected: PASS. (Note: existing tests that call `DecodeProviderSpecFromMachineClass` with a valid spec must also supply a secret with `token`+`userData` — update fixtures in `mock/provider_spec.go` / test setup if they now fail.) + +- [ ] **Step 6: Commit** + +```bash +git add -A +git commit -m "fix: reject null providerSpec without panic; validate secret token/userData and numeric placementGroupID" +``` + +### Task 5e: Map transient API errors to retryable codes + +**Files:** Modify `pkg/hcloud/machine_controller.go` (lookup error sites: image/sshkey/network/server-by-id lookups at ~107,113,144,154,383,385,391). + +- [ ] **Step 1: Reclassify lookup/API failures** + +For each `client.*.Get*`/lookup call whose `err` currently returns `codes.InvalidArgument`, change transient API failures to `codes.Unavailable` (retryable), keeping `InvalidArgument` only for genuinely malformed input (e.g. an unparseable providerID, image-not-found which is a config error stays `InvalidArgument`). Concretely: image lookup API error → `Unavailable`; SSH key lookup API error → `Unavailable`; network lookup API error → `Unavailable`; `Server.GetByID` API errors in `DeleteMachine`/`GetMachineStatus` → `Unavailable`. Leave the "not found / nil result" cases as their current explicit codes. + +- [ ] **Step 2: Build and test** + +Run: `go build ./... && go test ./... 2>&1 | tail` +Expected: PASS (update any test asserting the old code to expect `Unavailable`). + +- [ ] **Step 3: Commit** + +```bash +git add -A +git commit -m "fix: return retryable codes.Unavailable for transient hcloud API lookup failures" +``` + +--- + +## Task 6: Version package + version stamping + +**Files:** +- Create: `pkg/version/version.go` +- Verify: `Makefile:64` `-X` path now resolves (it already targets `.../pkg/version.Version`) + +**Interfaces:** +- Produces: `version.Version` string var, set at build time via `-ldflags -X`. + +- [ ] **Step 1: Create the version package** + +`pkg/version/version.go`: + +```go +// Package version holds the build-time version of the provider binary. +package version + +// Version is set at build time via -ldflags "-X .../pkg/version.Version=". +var Version = "dev" +``` + +- [ ] **Step 2: Confirm the LD_FLAGS path matches** + +Run: `grep -n 'pkg/version.Version' Makefile` +Expected: shows `-X github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/version.Version=$(VERSION)` (opendefensecloud after Task 1). + +- [ ] **Step 3: Build with the stamp and verify it embeds** + +Run: `make install && go version -m $(go env GOPATH)/bin/machine-controller 2>/dev/null | grep -i version || echo "built"` +Expected: build succeeds; `VERSION` is embedded (no longer a silent no-op). + +- [ ] **Step 4: Commit** + +```bash +git add -A +git commit -m "fix: add pkg/version so -ldflags version stamping is no longer a no-op" +``` + +--- + +## Task 7: CI hardening and release-workflow modernization + +**Files:** +- Modify: `.github/workflows/on-pr-test.yml`, `.github/workflows/on-release-publish.yml` +- Create: `.github/dependabot.yml` + +**Interfaces:** none (CI only). + +- [ ] **Step 1: Harden the PR workflow** + +Rewrite `.github/workflows/on-pr-test.yml` to Go 1.26, current actions, and add build + vet + lint gates alongside tests: + +```yaml +name: go:test + +on: + pull_request: + paths-ignore: + - "docs/**" + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.26" + cache: true + - name: Build + run: go build ./... + - name: Vet + run: go vet ./... + - name: Test + run: go test ./... -race +``` + +> A full `make check` (golangci-lint via gardener hack) + `make sast` gate is desirable but depends on the gardener hack scripts resolving `./charts` (`check-charts.sh`) which this repo lacks — track that as follow-up (Task 8 note). The `go build/vet/test -race` gate above is the immediate win. + +- [ ] **Step 2: Modernize the release workflow** + +In `.github/workflows/on-release-publish.yml`: bump `actions/checkout@v2`→`@v4`, `docker/setup-buildx-action@v1`→`@v3`, `docker/login-action@v1`→`@v3`, `docker/build-push-action@v2`→`@v6`, `softprops/action-gh-release@v1`→`@v2`; add `platforms: linux/amd64,linux/arm64` to the build-push step; add `needs: create-release` to `on-publish` so a release never publishes without its image. + +- [ ] **Step 3: Add dependabot** + +`.github/dependabot.yml`: + +```yaml +version: 2 +updates: + - package-ecosystem: gomod + directory: "/" + schedule: { interval: weekly } + - package-ecosystem: github-actions + directory: "/" + schedule: { interval: weekly } + - package-ecosystem: docker + directory: "/" + schedule: { interval: weekly } +``` + +- [ ] **Step 4: Commit** + +```bash +git add .github +git commit -m "ci: build/vet/test -race gate on PRs; bump actions; multi-arch release; dependabot" +``` + +--- + +## Task 8: Full verification and release cut + +**Files:** Modify `VERSION`. + +- [ ] **Step 1: Run the full local verification the same way CI/Make will** + +Run: `go build ./... && go vet ./... && go test ./... -race 2>&1 | tail -30` +Expected: all PASS. + +- [ ] **Step 2: Build the container image locally (amd64) to confirm the Dockerfile** + +Run: `docker build -t mcm-provider-hcloud:local .` +Expected: image builds; `make install` runs inside on Go 1.26.2. + +- [ ] **Step 3: Bump VERSION** + +Set `VERSION` to `v0.4.0-odp.1`. + +- [ ] **Step 4: Commit and tag** + +```bash +git add VERSION +git commit -m "release: v0.4.0-odp.1 (extension toolchain catch-up + hcloud-go v2 + P0 fixes)" +``` + +(Tag `v0.4.0-odp.1` is pushed by the maintainer to trigger the release workflow — do not push tags without confirmation.) + +- [ ] **Step 5: Record the handoff item** + +The extension repo's `charts/images.yaml` entry `machine-controller-manager-provider-hcloud` (tag `v0.3.2-odp.1`) must be bumped to `v0.4.0-odp.1` after the image publishes. Renovate will also propose this. Note it in the PR description. + +--- + +## Deferred to follow-up plans (out of scope here) + +These are independent, lower-risk, and not required for "compiles + tests pass + matches the extension contract." Track separately: + +- **Sample manifests** (`kubernetes/*.yaml`): they deploy the AWS provider image + retired `cx11`/`ubuntu-20.04`; rewrite to the real image + `cx22`/`ubuntu-24.04` with placement-group/floating-pool/network examples. +- **Docs & governance:** `docs/README.md` lists a non-existent interface method; README "work-in-progress" banner; `CODEOWNERS` → a real opendefensecloud team; drop orphaned `.ci/hack` Concourse scripts and the dead `make test-integration` target. +- **hcloud client cache + race** (`apis/client.go`): never writes back to `singletons`; add `sync.RWMutex`/`sync.Map` keyed on a token hash. +- **Broader test coverage:** floating-IP/private-network create paths, both cleanup branches, `apis` package (`GetRegionFromZone`, token newline strip, `HCLOUD_ENDPOINT`), response-payload assertions. +- **Full `make check`/`make sast` CI gate** once the `./charts` dependency of the gardener hack scripts is resolved. + +## Self-Review + +- **Spec coverage:** match extension versions (Task 2) ✓; module rename (Task 1) ✓; verify + implement tags/extraConfig schema (Task 4) ✓; P0 fixes folded post-revendor (Task 5, after Tasks 2–3) ✓; hcloud-go v2 (Task 3) ✓; CI (Task 7) ✓; release/handoff (Task 8) ✓. +- **Placeholder scan:** dependency-bump/hcloud-v2 tasks are explicitly compile-driven with named likely-breakage points rather than vague "fix errors"; all deterministic edits show exact code. +- **Type consistency:** `DecodeServerIDFromProviderID` int64 (Task 3) matches its use in `machine_controller.go`; `ProviderSpec.Tags`/`ExtraConfig` map[string]string (Task 4) match the extension's `MachineTypeOptions.ExtraConfig` and machineclass `tags` types; `validation` returns `[]error` checked with `len(...) > 0` (Task 5d). diff --git a/go.mod b/go.mod index a80f16f7..ae11b478 100644 --- a/go.mod +++ b/go.mod @@ -1,96 +1,101 @@ -module github.com/23technologies/machine-controller-manager-provider-hcloud +module github.com/opendefensecloud/machine-controller-manager-provider-hcloud -go 1.24.0 +go 1.26.2 require ( github.com/ahmetb/gen-crd-api-reference-docs v0.3.0 - github.com/gardener/gardener v1.119.0 - github.com/gardener/machine-controller-manager v0.58.0 - github.com/hetznercloud/hcloud-go v1.59.2 - github.com/onsi/ginkgo/v2 v2.23.3 - github.com/onsi/gomega v1.37.0 - github.com/spf13/pflag v1.0.6 - golang.org/x/lint v0.0.0-20241112194109-818c5a804067 - k8s.io/api v0.32.4 - k8s.io/apimachinery v0.32.4 - k8s.io/code-generator v0.32.4 - k8s.io/component-base v0.32.4 - k8s.io/klog/v2 v2.130.1 - sigs.k8s.io/controller-runtime v0.20.4 + github.com/gardener/gardener v1.146.4 + github.com/gardener/machine-controller-manager v0.62.1 + github.com/onsi/ginkgo/v2 v2.32.0 + github.com/onsi/gomega v1.42.0 + github.com/spf13/pflag v1.0.10 + k8s.io/api v0.35.5 + k8s.io/apimachinery v0.35.5 + k8s.io/code-generator v0.35.5 + k8s.io/component-base v0.35.5 + k8s.io/klog/v2 v2.140.0 ) require ( - github.com/Masterminds/semver/v3 v3.3.1 // indirect + github.com/go-openapi/swag/cmdutils v0.25.4 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect + github.com/go-openapi/swag/jsonname v0.25.5 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect + github.com/go-openapi/swag/loading v0.25.5 // indirect + github.com/go-openapi/swag/mangling v0.25.5 // indirect + github.com/go-openapi/swag/netutils v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect + github.com/go-openapi/swag/yamlutils v0.25.5 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + go.yaml.in/yaml/v2 v2.4.4 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + sigs.k8s.io/controller-runtime v0.23.3 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 // indirect +) + +require ( + github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/fxamacker/cbor/v2 v2.8.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect - github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/gardener/gardener/hack/tools v1.146.4 + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-openapi/jsonpointer v0.22.5 // indirect + github.com/go-openapi/jsonreference v0.21.5 // indirect + github.com/go-openapi/swag v0.25.4 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/gobuffalo/flect v1.0.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/gnostic-models v0.7.1 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/gofuzz v1.2.0 // indirect - github.com/google/pprof v0.0.0-20250208200701-d0013a598941 // indirect + github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/hetznercloud/hcloud-go/v2 v2.44.0 github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/ironcore-dev/vgopath v0.1.7 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.9.0 // indirect - github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect + github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // 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.22.0 // indirect - github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.63.0 // indirect - github.com/prometheus/procfs v0.15.1 // indirect + github.com/prometheus/client_golang v1.23.3-0.20260624042014-28914d017fba // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.69.0 // indirect + github.com/prometheus/procfs v0.20.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/cobra v1.10.2 // indirect github.com/x448/float16 v0.8.4 // indirect - go.opentelemetry.io/otel v1.32.0 // indirect - go.opentelemetry.io/otel/trace v1.32.0 // indirect - go.uber.org/mock v0.5.0 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.40.0 // indirect - golang.org/x/oauth2 v0.28.0 // indirect - golang.org/x/sync v0.14.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/term v0.32.0 // indirect - golang.org/x/text v0.25.0 // indirect - golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.33.0 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + go.uber.org/mock v0.6.0 + golang.org/x/mod v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/term v0.44.0 // indirect + golang.org/x/text v0.38.0 // indirect + golang.org/x/time v0.15.0 // indirect + golang.org/x/tools v0.46.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // 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.32.4 // indirect - k8s.io/apiserver v0.32.4 // indirect - k8s.io/client-go v0.32.4 // indirect - k8s.io/cluster-bootstrap v0.32.4 // indirect + k8s.io/apiextensions-apiserver v0.35.5 // indirect + k8s.io/apiserver v0.35.5 // indirect + k8s.io/client-go v0.35.5 // indirect + k8s.io/cluster-bootstrap v0.35.5 // indirect k8s.io/gengo v0.0.0-20250207200755-1244d31929d7 // indirect - k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 // indirect + k8s.io/gengo/v2 v2.0.0-20251215205346-5ee0d033ba5b // indirect k8s.io/klog v1.0.0 // indirect - k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect - k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 // indirect - sigs.k8s.io/controller-tools v0.17.3 // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect + k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 // indirect + 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 e5400630..fa2694f1 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/ahmetb/gen-crd-api-reference-docs v0.3.0 h1:+XfOU14S4bGuwyvCijJwhhBIjYN+YXS18jrCY2EzJaY= github.com/ahmetb/gen-crd-api-reference-docs v0.3.0/go.mod h1:TdjdkYhlOifCQWPs1UdTma97kQQMozf5h26hTuG70u8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -13,72 +13,97 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= -github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +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 v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= +github.com/evanphx/json-patch v5.9.11+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 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/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= -github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/gardener/gardener v1.119.0 h1:WUpomSfFy0W5YaP/QH1rocqfURnzNChApJRxO8xdqDo= -github.com/gardener/gardener v1.119.0/go.mod h1:eD9G3dW8+bINMPXFlVTb9zWACZHCqcRjJsi7F7wNpR8= -github.com/gardener/machine-controller-manager v0.58.0 h1:JLMpuD+omliu/RwK0mA9Ce+MLObJq421Du1qmaAHmAU= -github.com/gardener/machine-controller-manager v0.58.0/go.mod h1:TCU/KoudCMt2eV0Jnrq2D1TwgsrBCuhIVgV3j1el6Og= +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/gardener/gardener v1.146.4 h1:pBmvMWnHSG/QkgO2YUHNOnbbXCblz5kA8CL2TqA3QqA= +github.com/gardener/gardener v1.146.4/go.mod h1:BL4tsbuXJkgPlotTOFcU0BKQHAf21l6z/OQo7A3grTU= +github.com/gardener/gardener/hack/tools v1.146.4 h1:Q3bG6wXNytZUKiTz82QzC3JrCOERkdS1TeV5wo547dg= +github.com/gardener/gardener/hack/tools v1.146.4/go.mod h1:AYY/L1q1uzpKUYUN+KWHj4Ke6OUxcc6k9RyRkUVFYFc= +github.com/gardener/machine-controller-manager v0.62.1 h1:TQ4Qs9aaYTqSkqFownIaEAvnrYoNpBMcp7ZMYVEgUWM= +github.com/gardener/machine-controller-manager v0.62.1/go.mod h1:ICsymXK4VHm7NVr8Mpl6USvYcTZjUNSk6+XaVu/dd74= +github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= +github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.15 h1:amyJrvM1D33cPHwVrjo9jQxX8g/7E2wYdZ+01KS3zGE= +github.com/gkampitakis/go-snaps v0.5.15/go.mod h1:HNpx/9GoKisdhw9AFOBT1N7DBs9DiHo/hGheFGBZ+mc= github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= -github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= +github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0= +github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= +github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= +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.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= +github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= +github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= +github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU= +github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g= +github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw= +github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY= +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.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= +github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ= +github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.0 h1:3hZD1fwydvCx/cc1R2uYNQirHqf2s6lqpKV3FcNTURA= +github.com/go-openapi/testify/enable/yaml/v2 v2.5.0/go.mod h1:TvDZKBH7ZbMaF3EqH2AwTvNQCmzyZq8K1agRjf1B+Nk= +github.com/go-openapi/testify/v2 v2.5.0 h1:UOCr63aAsMIDydZbZGqo5Ev01D4eydItRbekDuZMJLw= +github.com/go-openapi/testify/v2 v2.5.0/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= 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/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= -github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +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.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20250208200701-d0013a598941 h1:43XjGa6toxLpeksjcxs1jIoIyr+vUfOqY2c6HB4bpoc= -github.com/google/pprof v0.0.0-20250208200701-d0013a598941/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= +github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= 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/hetznercloud/hcloud-go v1.59.2 h1:NkCPwYiPv85FnOV3IW9/gxfW61TPIUSwyPHRSLwCkHA= -github.com/hetznercloud/hcloud-go v1.59.2/go.mod h1:oTebZCjd+osj75jlI76Z+zjN1sTxmMiQ1MWoO8aRl1c= +github.com/hetznercloud/hcloud-go/v2 v2.44.0 h1:1p9qwaZ/H55nLP9c7WfuwUA0LfsexS2YHOhD8+iBga8= +github.com/hetznercloud/hcloud-go/v2 v2.44.0/go.mod h1:d0s2WLe7jSoStamv3eHoWgBSOxc/K17tYSXsqUkbse0= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/ironcore-dev/vgopath v0.1.7 h1:4QGfotAmfYnCUGMKiR+WQjB5VrjrZB88M2bec3xpF0k= -github.com/ironcore-dev/vgopath v0.1.7/go.mod h1:tAY0zaSKrRsWHXd4rXZO/L5anW5Dp7fOcXAvp6bsGQk= -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/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE= +github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= +github.com/klauspost/compress v1.18.6 h1:2jupLlAwFm95+YDR+NwD2MEfFO9d4z4Prjl1XXDjuao= +github.com/klauspost/compress v1.18.6/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -88,187 +113,176 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= -github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= -github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= +github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= 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/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.23.3 h1:edHxnszytJ4lD9D5Jjc4tiDkPBZ3siDeJJkUZJJVkp0= -github.com/onsi/ginkgo/v2 v2.23.3/go.mod h1:zXTP6xIp3U8aVuXN8ENK9IXRaTjFnpVB9mGmaSRvxnM= -github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= -github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= +github.com/onsi/ginkgo/v2 v2.32.0 h1:Hw7s2pVrQo/8Yz5N77qdnpHaoc+c6cC9WIV1Jce+J6E= +github.com/onsi/ginkgo/v2 v2.32.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.42.0 h1:CJby8u36xb7v34W78F8WKvqTQP7PCMIPB78IVDB73l4= +github.com/onsi/gomega v1.42.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= -github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= -github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= -github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k= -github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18= -github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= -github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/prometheus/client_golang v1.23.3-0.20260624042014-28914d017fba h1:KHpSrmy2SF+MKJ38YZHpLotXS+Y5DDJXmyAQ0fJtKMc= +github.com/prometheus/client_golang v1.23.3-0.20260624042014-28914d017fba/go.mod h1:yil2FhBgMjSH1lTcj2doFhabCcBjs5d/MjPMGJl9vTA= +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.69.0 h1:OA85nJQS/T/MaYh/Q2CcgDKSGWqNIgrBDvDH85CuiNk= +github.com/prometheus/common v0.69.0/go.mod h1:ZzL3f6u94qUxh9p+tJTrF+FvBS1XXbbRAZCQkytAL0Y= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= +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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= -github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= github.com/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= -go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= -go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= -go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= -go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= 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/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= +go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= +go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= +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/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA= -golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 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.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= -golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= -golang.org/x/oauth2 v0.28.0 h1:CrgCKl8PPAVtLnU3c+EDw6x11699EWlsDeWNWKdIOkc= -golang.org/x/oauth2 v0.28.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= 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.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= -golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= 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-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= -golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.44.0 h1:0rLvDRCtNj0gZkyIXhCyOb2OAzEhLVqc4B+hrsBhrmc= +golang.org/x/term v0.44.0/go.mod h1:7ze4MdzUzLXpSAoFP1H0bOI9aXDqveSvatT5vKcFh2Y= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= -golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE= +golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -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.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= -golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/tools v0.46.0 h1:7jTurBkPZu4moS/Uy4OQT1M+QBlsj3wejyZwsT8Z7rk= +golang.org/x/tools v0.46.0/go.mod h1:FrD85F8l+NWL+9XWBSyVSHO6Ne4jutsfIFba7AWQ5Ys= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= 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.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0= gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/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-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.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.32.4 h1:kw8Y/G8E7EpNy7gjB8gJZl3KJkNz8HM2YHrZPtAZsF4= -k8s.io/api v0.32.4/go.mod h1:5MYFvLvweRhyKylM3Es/6uh/5hGp0dg82vP34KifX4g= -k8s.io/apiextensions-apiserver v0.32.4 h1:IA+CoR63UDOijR/vEpow6wQnX4V6iVpzazJBskHrpHE= -k8s.io/apiextensions-apiserver v0.32.4/go.mod h1:Y06XO/b92H8ymOdG1HlA1submf7gIhbEDc3RjriqZOs= -k8s.io/apimachinery v0.32.4 h1:8EEksaxA7nd7xWJkkwLDN4SvWS5ot9g6Z/VZb3ju25I= -k8s.io/apimachinery v0.32.4/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= -k8s.io/apiserver v0.32.4 h1:Yf7sd/y+GOQKH1Qf6wUeayZrYXe2SKZ17Bcq7VQM5HQ= -k8s.io/apiserver v0.32.4/go.mod h1:JFUMNtE2M5yqLZpIsgCb06SkVSW1YcxW1oyLSTfjXR8= -k8s.io/client-go v0.32.4 h1:zaGJS7xoYOYumoWIFXlcVrsiYioRPrXGO7dBfVC5R6M= -k8s.io/client-go v0.32.4/go.mod h1:k0jftcyYnEtwlFW92xC7MTtFv5BNcZBr+zn9jPlT9Ic= -k8s.io/cluster-bootstrap v0.32.4 h1:QO2rZ1KDLHaa3WKgpF3P26/5AKLFViMt0jUJptylCgs= -k8s.io/cluster-bootstrap v0.32.4/go.mod h1:+O5BK2t/VxGXcPPOn+SlpFrC0x78nnW6jnPI2MRhdz8= -k8s.io/code-generator v0.32.4 h1:d4dm/43RD6xhPBX22JgJw9JUpwTKzVR6tAxJD7pz83o= -k8s.io/code-generator v0.32.4/go.mod h1:R0bKdIg1smtvsKvj9q7SxTeKq5X9ko6PuICCGt4yqxg= -k8s.io/component-base v0.32.4 h1:HuF+2JVLbFS5GODLIfPCb1Td6b+G2HszJoArcWOSr5I= -k8s.io/component-base v0.32.4/go.mod h1:10KloJEYw1keU/Xmjfy9TKJqUq7J2mYdiD1VDXoco4o= +k8s.io/api v0.35.5 h1:BrFeUDGY/LBtlA1R5RoxhlYRHs76RnQBc6xbm/y7hsQ= +k8s.io/api v0.35.5/go.mod h1:xWkFhMnoPZdTAQh95Rlw3zZpUUNVlFHcuESUYd06BWM= +k8s.io/apiextensions-apiserver v0.35.5 h1:HttlJjgsx3ddLsASCqklkKvfBlwUoXma8VLpeMG5YL8= +k8s.io/apiextensions-apiserver v0.35.5/go.mod h1:4xbAgP/jbt8sVHE3H4DfE1gSPLUoSzXrNqhZz1lTHKc= +k8s.io/apimachinery v0.35.5 h1:lbjjjUfVeVqFbiOpyhqZHc8DhiYkWOxSNij7lHx2U8Y= +k8s.io/apimachinery v0.35.5/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= +k8s.io/apiserver v0.35.5 h1:ZtFpSEmxf/VmOdbL3bo7hLxyNRorRegqOLmYSW0mxEo= +k8s.io/apiserver v0.35.5/go.mod h1:6NNWFTq/UosCwUmqhQDC+3ApzSx5ekeYMIwzSG+49VU= +k8s.io/client-go v0.35.5 h1:wUrgqVSmFRw75bgSHY7X0G/hZM/QYpV0Hg7SYYOYpFk= +k8s.io/client-go v0.35.5/go.mod h1:Z0mDcAJsX1Y7RQfuQlJipiRtqf8Mhk2VDu1/JvRqdGo= +k8s.io/cluster-bootstrap v0.35.5 h1:Dk0aNTKuqH87hBpzn85qrx1VVv5/CLNZCnCPDKlw5s4= +k8s.io/cluster-bootstrap v0.35.5/go.mod h1:Q9bKIm0xCTW8wMkR/NuUAm7VuRJnepI/6MNqdX6m540= +k8s.io/code-generator v0.35.5 h1:g2ZIw7LCjmX2p5WDjtkVYwmvtx+pDF0Pq1dfgCoHkhQ= +k8s.io/code-generator v0.35.5/go.mod h1:W46pDvFxY2SlphV3MBI/6KDZ2JDMhHXGVgPQXMoYFiM= +k8s.io/component-base v0.35.5 h1:1y1xxfpFNkNi4RMi6bvPNN4aDr9VhOijtEfrqnhPijs= +k8s.io/component-base v0.35.5/go.mod h1:n/+aL98XYINubqIu/Okh6mS/kZT2nMeN4IQkQR4VXRg= k8s.io/gengo v0.0.0-20201203183100-97869a43a9d9/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= k8s.io/gengo v0.0.0-20250207200755-1244d31929d7 h1:iOdAjO5OsCszwPG5xNcJJL+rKaMUogk5fx9HsYZJxQM= k8s.io/gengo v0.0.0-20250207200755-1244d31929d7/go.mod h1:FiNAH4ZV3gBg2Kwh89tzAEV2be7d5xI0vBa/VySYy3E= -k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7 h1:2OX19X59HxDprNCVrWi6jb7LW1PoqTlYqEq5H2oetog= -k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU= +k8s.io/gengo/v2 v2.0.0-20251215205346-5ee0d033ba5b h1:0YkdvW3rX2vaBWsqCGZAekxPRwaI5NuYNprOsMNVLns= +k8s.io/gengo/v2 v2.0.0-20251215205346-5ee0d033ba5b/go.mod h1:yvyl3l9E+UxlqOMUULdKTAYB0rEhsmjr7+2Vb/1pCSo= k8s.io/klog v0.2.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -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-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= -k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= -k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979 h1:jgJW5IePPXLGB8e/1wvd0Ich9QE97RvvF3a8J3fP/Lg= -k8s.io/utils v0.0.0-20250502105355-0f33e8f1c979/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/controller-runtime v0.20.4 h1:X3c+Odnxz+iPTRobG4tp092+CvBU9UK0t/bRf+n0DGU= -sigs.k8s.io/controller-runtime v0.20.4/go.mod h1:xg2XB0K5ShQzAgsoujxuKN4LNXR2LfwwHsPj7Iaw+XY= -sigs.k8s.io/controller-tools v0.17.3 h1:lwFPLicpBKLgIepah+c8ikRBubFW5kOQyT88r3EwfNw= -sigs.k8s.io/controller-tools v0.17.3/go.mod h1:1ii+oXcYZkxcBXzwv3YZBlzjt1fvkrCGjVF73blosJI= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016 h1:kXv6kKdoEtedwuqMmkqhbkgvYKeycVbC8+iPCP9j5kQ= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= +k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2 h1:wU4tMEhLGgIbLvXQb1cfN+EcM0wf7zC6CPF+C79jroc= +k8s.io/utils v0.0.0-20260507154919-ff6756f316d2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= +sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +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.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +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/hack/tools.go b/hack/tools.go index 74f8ba8c..84e374c0 100644 --- a/hack/tools.go +++ b/hack/tools.go @@ -9,13 +9,12 @@ package tools import ( - _ "github.com/ahmetb/gen-crd-api-reference-docs" - _ "github.com/gardener/gardener/hack" + _ "github.com/gardener/gardener/.github" + _ "github.com/gardener/gardener/.github/ISSUE_TEMPLATE" _ "github.com/gardener/gardener/hack/.ci" - _ "github.com/gardener/gardener/hack/api-reference/template" - _ "github.com/onsi/ginkgo/v2" - _ "github.com/onsi/gomega" - _ "golang.org/x/lint/golint" + _ "github.com/gardener/gardener/hack/tools" + + _ "github.com/ahmetb/gen-crd-api-reference-docs" + _ "go.uber.org/mock/mockgen" _ "k8s.io/code-generator" - _ "sigs.k8s.io/controller-runtime" // Needed to work around strange behaviour in check-generate. Without this explicit dependenc this package will always fail the check, either needing to be removed or added (depending on whether it is already present or not). ) diff --git a/pkg/cmd/app.go b/pkg/cmd/app.go index 9198ecbb..f4a0b992 100644 --- a/pkg/cmd/app.go +++ b/pkg/cmd/app.go @@ -26,7 +26,7 @@ import ( "k8s.io/component-base/logs" "k8s.io/component-base/version/verflag" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud" ) // RunProviderHCloudManager runs the HCloud machine controller server. diff --git a/pkg/hcloud/apis/client.go b/pkg/hcloud/apis/client.go index 5d4ab68c..356e33f5 100644 --- a/pkg/hcloud/apis/client.go +++ b/pkg/hcloud/apis/client.go @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/hetznercloud/hcloud-go/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud" "k8s.io/klog/v2" ) diff --git a/pkg/hcloud/apis/mock/machine_controller.go b/pkg/hcloud/apis/mock/machine_controller.go index 8b8c8a90..166ac989 100644 --- a/pkg/hcloud/apis/mock/machine_controller.go +++ b/pkg/hcloud/apis/mock/machine_controller.go @@ -23,12 +23,54 @@ import ( "fmt" "net/http" "strings" + "sync/atomic" "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" ) +// placementGroupGetByIDHits and addToPlacementGroupHits count how many times the +// respective mock endpoints have been invoked. Tests use these counters to assert +// that the placement-group flow was actually exercised, not merely that CreateMachine +// returned without error. +var ( + placementGroupGetByIDHits int32 + addToPlacementGroupHits int32 +) + +// ResetPlacementGroupHitCounters resets the placement-group hit counters. Call this +// from BeforeEach so counts don't leak between test cases. +func ResetPlacementGroupHitCounters() { + atomic.StoreInt32(&placementGroupGetByIDHits, 0) + atomic.StoreInt32(&addToPlacementGroupHits, 0) +} + +// PlacementGroupGetByIDHitCount returns how many times GET /placement_groups/{id} was hit. +func PlacementGroupGetByIDHitCount() int32 { + return atomic.LoadInt32(&placementGroupGetByIDHits) +} + +// AddToPlacementGroupHitCount returns how many times POST .../actions/add_to_placement_group was hit. +func AddToPlacementGroupHitCount() int32 { + return atomic.LoadInt32(&addToPlacementGroupHits) +} + +// serverDeleteHits counts how many times DELETE /servers/{id} was hit via the endpoint +// registered by SetupTestServerEndpointOnMux. Tests use this to prove that create-error +// cleanup never deletes a server it did not itself create. +var serverDeleteHits int32 + +// ResetServerDeleteHitCounter resets the server-delete hit counter. +func ResetServerDeleteHitCounter() { + atomic.StoreInt32(&serverDeleteHits, 0) +} + +// ServerDeleteHitCount returns how many times DELETE /servers/{id} was hit. +func ServerDeleteHitCount() int32 { + return atomic.LoadInt32(&serverDeleteHits) +} + const ( jsonImageData = ` { @@ -405,6 +447,7 @@ func SetupTestPlacementGroupEndpointOnMux(mux *http.ServeMux) { if strings.ToLower(req.Method) != "get" { panic("Unsupported HTTP method call") } + atomic.AddInt32(&placementGroupGetByIDHits, 1) res.WriteHeader(http.StatusOK) res.Write([]byte(` @@ -433,6 +476,7 @@ func SetupTestServerEndpointOnMux(mux *http.ServeMux) { res.Header().Add("Content-Type", "application/json; charset=utf-8") if strings.ToLower(req.Method) == "delete" { + atomic.AddInt32(&serverDeleteHits, 1) res.WriteHeader(http.StatusOK) res.Write([]byte(fmt.Sprintf("{ \"server\": %s }", newJsonServerData(TestServerID, "shutdown_server")))) } else if strings.ToLower(req.Method) == "get" { @@ -472,6 +516,7 @@ func SetupTestServerEndpointOnMux(mux *http.ServeMux) { panic("Invalid HTTP method data") } + atomic.AddInt32(&addToPlacementGroupHits, 1) res.Write([]byte("{ \"action\": {} }")) }) diff --git a/pkg/hcloud/apis/mock/provider_spec.go b/pkg/hcloud/apis/mock/provider_spec.go index bc7a89a6..6f543a3a 100644 --- a/pkg/hcloud/apis/mock/provider_spec.go +++ b/pkg/hcloud/apis/mock/provider_spec.go @@ -18,7 +18,7 @@ limitations under the License. package mock import ( - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" ) const ( diff --git a/pkg/hcloud/apis/mock/test_env.go b/pkg/hcloud/apis/mock/test_env.go index 833eaead..222c08c2 100644 --- a/pkg/hcloud/apis/mock/test_env.go +++ b/pkg/hcloud/apis/mock/test_env.go @@ -21,7 +21,7 @@ import ( "net/http" "net/http/httptest" - "github.com/hetznercloud/hcloud-go/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud" ) const ( diff --git a/pkg/hcloud/apis/provider_spec.go b/pkg/hcloud/apis/provider_spec.go index 09dec3b3..13ab1a32 100644 --- a/pkg/hcloud/apis/provider_spec.go +++ b/pkg/hcloud/apis/provider_spec.go @@ -28,4 +28,11 @@ type ProviderSpec struct { PlacementGroupID string `json:"placementGroupID,omitempty"` FloatingPoolName string `json:"floatingPoolName,omitempty"` NetworkName string `json:"networkName,omitempty"` + + // Tags are additional labels applied to the created Hetzner server. The extension + // supplies mcm.gardener.cloud/cluster and mcm.gardener.cloud/role here. + Tags map[string]string `json:"tags,omitempty"` + // ExtraConfig carries per-machine-type "additional VM options" from the CloudProfile's + // MachineTypeOptions. Reserved: currently accepted but not applied (no Hetzner semantic yet). + ExtraConfig map[string]string `json:"extraConfig,omitempty"` } diff --git a/pkg/hcloud/apis/transcoder/machine_class_json.go b/pkg/hcloud/apis/transcoder/machine_class_json.go index bbc8826b..c0ff50fe 100644 --- a/pkg/hcloud/apis/transcoder/machine_class_json.go +++ b/pkg/hcloud/apis/transcoder/machine_class_json.go @@ -25,8 +25,8 @@ import ( "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" corev1 "k8s.io/api/core/v1" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/validation" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/validation" ) // DecodeProviderSpecFromMachineClass decodes the given MachineClass to receive the ProviderSpec. @@ -36,7 +36,7 @@ import ( // secret *corev1.Secret Kubernetes secret that contains any sensitive data/credentials func DecodeProviderSpecFromMachineClass(machineClass *v1alpha1.MachineClass, secret *corev1.Secret) (*apis.ProviderSpec, error) { // Extract providerSpec - var providerSpec *apis.ProviderSpec + var providerSpec apis.ProviderSpec if machineClass == nil { return nil, errors.New("MachineClass provided is nil") @@ -48,10 +48,10 @@ func DecodeProviderSpecFromMachineClass(machineClass *v1alpha1.MachineClass, sec } // Validate the Spec - validationErr := validation.ValidateHCloudProviderSpec(providerSpec, secret) - if validationErr != nil { + validationErr := validation.ValidateHCloudProviderSpec(&providerSpec, secret) + if len(validationErr) > 0 { return nil, fmt.Errorf("error while validating ProviderSpec %v", validationErr) } - return providerSpec, nil + return &providerSpec, nil } diff --git a/pkg/hcloud/apis/transcoder/machine_class_json_test.go b/pkg/hcloud/apis/transcoder/machine_class_json_test.go index f09655b3..4f2a5fdc 100644 --- a/pkg/hcloud/apis/transcoder/machine_class_json_test.go +++ b/pkg/hcloud/apis/transcoder/machine_class_json_test.go @@ -18,11 +18,13 @@ limitations under the License. package transcoder import ( + "github.com/gardener/machine-controller-manager/pkg/apis/machine/v1alpha1" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" ) var _ = Describe("Transcoder", func() { @@ -31,6 +33,7 @@ var _ = Describe("Transcoder", func() { providerSecret := &corev1.Secret{ Data: map[string][]byte{ + "token": []byte("dummy-token"), "userData": []byte("dummy-user-data"), }, } @@ -48,5 +51,31 @@ var _ = Describe("Transcoder", func() { Expect(err).To(HaveOccurred()) }) + + It("should decode tags and extraConfig", func() { + raw := []byte(`{"cluster":"c","zone":"hel1-dc2","serverType":"cx22","imageName":"ubuntu-24.04","sshFingerprint":"aa:bb","tags":{"mcm.gardener.cloud/cluster":"c","mcm.gardener.cloud/role":"node"},"extraConfig":{"foo":"bar"}}`) + mc := &v1alpha1.MachineClass{ProviderSpec: runtime.RawExtension{Raw: raw}} + spec, err := DecodeProviderSpecFromMachineClass(mc, providerSecret) + Expect(err).NotTo(HaveOccurred()) + Expect(spec.Tags).To(HaveKeyWithValue("mcm.gardener.cloud/role", "node")) + Expect(spec.ExtraConfig).To(HaveKeyWithValue("foo", "bar")) + }) + + It("should reject a null providerSpec without panicking", func() { + mc := &v1alpha1.MachineClass{ProviderSpec: runtime.RawExtension{Raw: []byte("null")}} + + Expect(func() { + _, err := DecodeProviderSpecFromMachineClass(mc, providerSecret) + Expect(err).To(HaveOccurred()) + }).NotTo(Panic()) + }) + + It("should reject a secret missing token/userData", func() { + raw := []byte(`{"cluster":"c","zone":"hel1-dc2","serverType":"cx22","imageName":"ubuntu-24.04","sshFingerprint":"aa:bb"}`) + mc := &v1alpha1.MachineClass{ProviderSpec: runtime.RawExtension{Raw: raw}} + + _, err := DecodeProviderSpecFromMachineClass(mc, &corev1.Secret{Data: map[string][]byte{}}) + Expect(err).To(HaveOccurred()) + }) }) }) diff --git a/pkg/hcloud/apis/transcoder/provider_id.go b/pkg/hcloud/apis/transcoder/provider_id.go index 071592ef..07c3d7cf 100644 --- a/pkg/hcloud/apis/transcoder/provider_id.go +++ b/pkg/hcloud/apis/transcoder/provider_id.go @@ -27,7 +27,7 @@ import ( // ServerData describes a server type ServerData struct { - ID int + ID int64 Zone string } @@ -48,7 +48,7 @@ func DecodeServerDataFromProviderID(providerID string) (*ServerData, error) { return nil, errors.New("ProviderID given contains an invalid URL") } - serverID, err := strconv.Atoi(providerIDData[1]) + serverID, err := strconv.ParseInt(providerIDData[1], 10, 64) if err != nil { return nil, fmt.Errorf("ServerID found is invalid: %v", err) } @@ -65,7 +65,7 @@ func DecodeServerDataFromProviderID(providerID string) (*ServerData, error) { // // PARAMETERS // providerID string Provider ID to parse -func DecodeServerIDFromProviderID(providerID string) (int, error) { +func DecodeServerIDFromProviderID(providerID string) (int64, error) { serverData, err := DecodeServerDataFromProviderID(providerID) if err != nil { return -1, err @@ -84,7 +84,7 @@ func DecodeServerIDAsStringFromProviderID(providerID string) (string, error) { return "", err } - return strconv.Itoa(serverData.ID), nil + return strconv.FormatInt(serverData.ID, 10), nil } // DecodeZoneFromProviderID decodes the given ProviderID to extract the datacenter zone. @@ -104,7 +104,7 @@ func DecodeZoneFromProviderID(providerID string) (string, error) { // // PARAMETERS // zone string Datacenter zone -// serverID string Server ID -func EncodeProviderID(zone string, serverID int) string { +// serverID int64 Server ID +func EncodeProviderID(zone string, serverID int64) string { return fmt.Sprintf("hcloud:///%s/%d", url.PathEscape(zone), serverID) } diff --git a/pkg/hcloud/apis/transcoder/provider_id_test.go b/pkg/hcloud/apis/transcoder/provider_id_test.go index 7eeacd3c..4b7581e4 100644 --- a/pkg/hcloud/apis/transcoder/provider_id_test.go +++ b/pkg/hcloud/apis/transcoder/provider_id_test.go @@ -24,7 +24,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" ) var _ = Describe("ProviderID", func() { @@ -34,7 +34,7 @@ var _ = Describe("ProviderID", func() { Expect(err).NotTo(HaveOccurred()) Expect(serverData.Zone).To(Equal(mock.TestZone)) - Expect(serverData.ID).To(Equal(mock.TestServerID)) + Expect(serverData.ID).To(Equal(int64(mock.TestServerID))) }) It("should fail if an unsupported provider ID scheme is provided", func() { @@ -94,7 +94,13 @@ var _ = Describe("ProviderID", func() { serverID, err := DecodeServerIDFromProviderID(EncodeProviderID(mock.TestZone, mock.TestServerID)) Expect(err).NotTo(HaveOccurred()) - Expect(serverID).To(Equal(mock.TestServerID)) + Expect(serverID).To(Equal(int64(mock.TestServerID))) + }) + + It("should decode the server ID as int64", func() { + id, err := DecodeServerIDFromProviderID("hcloud:///hel1-dc2/12345") + Expect(err).NotTo(HaveOccurred()) + Expect(id).To(Equal(int64(12345))) }) It("should fail if an unsupported provider ID scheme is provided", func() { diff --git a/pkg/hcloud/apis/utils.go b/pkg/hcloud/apis/utils.go index e2e3d68a..8f87ad4f 100644 --- a/pkg/hcloud/apis/utils.go +++ b/pkg/hcloud/apis/utils.go @@ -25,8 +25,8 @@ import ( "strings" "time" - "github.com/hetznercloud/hcloud-go/hcloud" - "github.com/hetznercloud/hcloud-go/hcloud/schema" + "github.com/hetznercloud/hcloud-go/v2/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud/schema" ) // Constant defaultMachineOperationInterval is the time to wait between retries diff --git a/pkg/hcloud/apis/validation/validation.go b/pkg/hcloud/apis/validation/validation.go index e31247f6..456f743d 100644 --- a/pkg/hcloud/apis/validation/validation.go +++ b/pkg/hcloud/apis/validation/validation.go @@ -19,10 +19,11 @@ package validation import ( "fmt" + "strconv" corev1 "k8s.io/api/core/v1" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" ) // ValidateHCloudProviderSpec validates provider specification and secret to check if all fields are present and valid @@ -48,7 +49,19 @@ func ValidateHCloudProviderSpec(spec *apis.ProviderSpec, secrets *corev1.Secret) if spec.SSHFingerprint == "" { allErrs = append(allErrs, fmt.Errorf("sshFingerprint is a required field")) } - //allErrs = append(allErrs, ValidateSecret(secret)...) + if spec.PlacementGroupID != "" { + if _, err := strconv.ParseInt(spec.PlacementGroupID, 10, 64); err != nil { + allErrs = append(allErrs, fmt.Errorf("placementGroupID must be numeric: %v", err)) + } + } + if secrets != nil { + if len(secrets.Data["token"]) == 0 { + allErrs = append(allErrs, fmt.Errorf("token is required in the secret")) + } + if len(secrets.Data["userData"]) == 0 { + allErrs = append(allErrs, fmt.Errorf("userData is required in the secret")) + } + } return allErrs } diff --git a/pkg/hcloud/apis/validation/validation_test.go b/pkg/hcloud/apis/validation/validation_test.go index 266689d8..09010006 100644 --- a/pkg/hcloud/apis/validation/validation_test.go +++ b/pkg/hcloud/apis/validation/validation_test.go @@ -19,18 +19,20 @@ package validation import ( "fmt" + "strconv" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" ) var _ = Describe("Validation", func() { providerSecret := &corev1.Secret{ Data: map[string][]byte{ + "token": []byte("dummy-token"), "userData": []byte("dummy-user-data"), }, } @@ -167,6 +169,60 @@ var _ = Describe("Validation", func() { }, }, }), + Entry("token missing from secret", &data{ + setup: setup{}, + action: action{ + spec: mock.NewProviderSpec(), + secret: &corev1.Secret{ + Data: map[string][]byte{ + "userData": []byte("dummy-user-data"), + }, + }, + }, + expect: expect{ + errToHaveOccurred: true, + errList: []error{ + fmt.Errorf("token is required in the secret"), + }, + }, + }), + Entry("userData missing from secret", &data{ + setup: setup{}, + action: action{ + spec: mock.NewProviderSpec(), + secret: &corev1.Secret{ + Data: map[string][]byte{ + "token": []byte("dummy-token"), + }, + }, + }, + expect: expect{ + errToHaveOccurred: true, + errList: []error{ + fmt.Errorf("userData is required in the secret"), + }, + }, + }), + Entry("placementGroupID non-numeric", &data{ + setup: setup{}, + action: action{ + spec: &apis.ProviderSpec{ + Cluster: mock.TestCluster, + Zone: mock.TestZone, + ImageName: mock.TestImageName, + ServerType: mock.TestServerType, + SSHFingerprint: mock.TestSSHFingerprint, + PlacementGroupID: "not-a-number", + }, + secret: providerSecret, + }, + expect: expect{ + errToHaveOccurred: true, + errList: []error{ + fmt.Errorf("placementGroupID must be numeric: %v", &strconv.NumError{Func: "ParseInt", Num: "not-a-number", Err: strconv.ErrSyntax}), + }, + }, + }), ) }) }) diff --git a/pkg/hcloud/machine_controller.go b/pkg/hcloud/machine_controller.go index a17bf893..f042ec64 100644 --- a/pkg/hcloud/machine_controller.go +++ b/pkg/hcloud/machine_controller.go @@ -27,11 +27,11 @@ import ( "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/status" - "github.com/hetznercloud/hcloud-go/hcloud" + "github.com/hetznercloud/hcloud-go/v2/hcloud" "k8s.io/klog/v2" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/transcoder" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/transcoder" ) // CreateMachine handles a machine creation request @@ -84,7 +84,10 @@ func (p *MachineProvider) createMachine(ctx context.Context, req *driver.CreateM client := apis.GetClientForToken(string(secret.Data["token"])) - server, _, _ := client.Server.GetByName(ctx, machine.Name) + server, _, err := client.Server.GetByName(ctx, machine.Name) + if err != nil { + return nil, status.Error(codes.Unavailable, err.Error()) + } if nil != server { var errorCode codes.Code @@ -104,13 +107,13 @@ func (p *MachineProvider) createMachine(ctx context.Context, req *driver.CreateM image, _, err := client.Image.GetByName(ctx, imageName) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } if image == nil { images, err := client.Image.AllWithOpts(ctx, hcloud.ImageListOpts{Name: imageName, IncludeDeprecated: true}) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } if len(images) == 0 { @@ -134,14 +137,18 @@ func (p *MachineProvider) createMachine(ctx context.Context, req *driver.CreateM "topology.kubernetes.io/region": region, "topology.kubernetes.io/zone": zone, }, - Datacenter: &hcloud.Datacenter{Name: zone}, + Location: &hcloud.Location{Name: region}, UserData: userDataBase64Enc, StartAfterCreate: &startAfterCreate, } + for k, v := range providerSpec.Tags { + opts.Labels[k] = v + } + sshKey, _, err := client.SSHKey.GetByFingerprint(ctx, providerSpec.SSHFingerprint) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } else if sshKey == nil { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("SSH key with fingerprint %s not found", providerSpec.SSHFingerprint)) } @@ -151,7 +158,7 @@ func (p *MachineProvider) createMachine(ctx context.Context, req *driver.CreateM if providerSpec.NetworkName != "" { network, _, err := client.Network.GetByName(ctx, providerSpec.NetworkName) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } else if network == nil { return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Network %s not found", providerSpec.NetworkName)) } @@ -171,15 +178,15 @@ func (p *MachineProvider) createMachine(ctx context.Context, req *driver.CreateM return nil, status.Error(codes.Unknown, err.Error()) } - if providerSpec.FloatingPoolName != "" { - placementGroupID, err := strconv.Atoi(providerSpec.PlacementGroupID) + if providerSpec.PlacementGroupID != "" { + placementGroupID, err := strconv.ParseInt(providerSpec.PlacementGroupID, 10, 64) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } placementGroup, _, err := client.PlacementGroup.GetByID(ctx, placementGroupID) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } _, _, err = client.Server.AddToPlacementGroup(ctx, server, placementGroup) @@ -187,7 +194,7 @@ func (p *MachineProvider) createMachine(ctx context.Context, req *driver.CreateM return nil, status.Error(codes.Unavailable, err.Error()) } - server, err = apis.WaitForActionsAndGetServer(ctx, client, serverResult.Server) + server, err = apis.WaitForActionsAndGetServer(ctx, client, server) if err != nil { return nil, status.Error(codes.Unknown, err.Error()) } @@ -332,14 +339,11 @@ func (p *MachineProvider) createMachineOnErrorCleanup(ctx context.Context, req * client := apis.GetClientForToken(string(req.Secret.Data["token"])) resultData := ctx.Value(CtxWrapDataKey("MethodData")).(*CreateMachineMethodData) - var server *hcloud.Server if resultData.ServerID != 0 { - server, _, _ = client.Server.GetByID(ctx, resultData.ServerID) - } else { - server, _, _ = client.Server.GetByName(ctx, req.Machine.Name) - } - if nil != server { - _, _ = client.Server.Delete(ctx, server) + server, _, _ := client.Server.GetByID(ctx, resultData.ServerID) + if nil != server { + _, _ = client.Server.Delete(ctx, server) + } } if resultData.FloatingIPID != 0 { @@ -382,13 +386,13 @@ func (p *MachineProvider) DeleteMachine(ctx context.Context, req *driver.DeleteM server, _, err = client.Server.GetByID(ctx, serverID) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } } else { server, _, err = client.Server.GetByName(ctx, machine.Name) } if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } else if server == nil { klog.V(3).Infof("VM %s does not exist", machine.Name) return &driver.DeleteMachineResponse{}, nil @@ -428,7 +432,7 @@ func (p *MachineProvider) GetMachineStatus(ctx context.Context, req *driver.GetM secret = req.Secret machineClass = req.MachineClass server *hcloud.Server - serverID int + serverID int64 ) // Log messages to track start and end of request @@ -452,14 +456,18 @@ func (p *MachineProvider) GetMachineStatus(ctx context.Context, req *driver.GetM serverID = server.ID } else { - serverID, err := transcoder.DecodeServerIDFromProviderID(machine.Spec.ProviderID) + // Assign to the outer serverID/err (declared in the var block above) rather + // than shadowing them with :=. Shadowing here would leave the outer serverID + // at its zero value, so the ProviderID encoded below would embed server ID 0 + // instead of the real one. + serverID, err = transcoder.DecodeServerIDFromProviderID(machine.Spec.ProviderID) if err != nil { return nil, status.Error(codes.InvalidArgument, err.Error()) } server, _, err = client.Server.GetByID(ctx, serverID) if err != nil { - return nil, status.Error(codes.InvalidArgument, err.Error()) + return nil, status.Error(codes.Unavailable, err.Error()) } } diff --git a/pkg/hcloud/machine_controller_test.go b/pkg/hcloud/machine_controller_test.go index 6ba167ac..6a5b5324 100644 --- a/pkg/hcloud/machine_controller_test.go +++ b/pkg/hcloud/machine_controller_test.go @@ -19,6 +19,9 @@ package hcloud import ( "context" + "fmt" + "net/http" + "strings" "github.com/gardener/machine-controller-manager/pkg/util/provider/driver" "github.com/gardener/machine-controller-manager/pkg/util/provider/machinecodes/codes" @@ -28,8 +31,8 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" - "github.com/23technologies/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis" + "github.com/opendefensecloud/machine-controller-manager-provider-hcloud/pkg/hcloud/apis/mock" ) var provider *MachineProvider @@ -57,6 +60,7 @@ var _ = Describe("MachineController", func() { mock.SetupSshKeysEndpointOnMux(mockTestEnv.Mux) mock.SetupTestPlacementGroupEndpointOnMux(mockTestEnv.Mux) mock.SetupTestServerEndpointOnMux(mockTestEnv.Mux) + mock.ResetPlacementGroupHitCounters() }) var _ = AfterEach(func() { @@ -156,6 +160,212 @@ var _ = Describe("MachineController", func() { }, }), ) + + It("applies the placement group when PlacementGroupID is set without a floating pool", func() { + ctx := context.Background() + + // TestProviderSpec carries placementGroupID "42" and no floatingPoolName, + // so the placement-group flow must be driven by PlacementGroupID alone. + _, err := provider.CreateMachine(ctx, &driver.CreateMachineRequest{ + Machine: mock.NewMachine(-1), + MachineClass: mock.NewMachineClass(), + Secret: providerSecret, + }) + + Expect(err).NotTo(HaveOccurred()) + Expect(mock.PlacementGroupGetByIDHitCount()).To(BeNumerically(">", 0), "expected GET /placement_groups/42 to be hit") + Expect(mock.AddToPlacementGroupHitCount()).To(BeNumerically(">", 0), "expected add_to_placement_group action to be hit") + }) + + It("does not delete a foreign server that merely shares a name (never deletes by name)", func() { + ctx := context.Background() + + env := mock.NewMockTestEnv() + defer env.Teardown() + + const token = "cleanup-foreign-token" + apis.SetClientForToken(token, env.Client) + defer apis.SetClientForToken(token, nil) + + mock.SetupImagesEndpointOnMux(env.Mux) + mock.SetupSshKeysEndpointOnMux(env.Mux) + mock.SetupFloatingIPsEndpointOnMux(env.Mux) + // emptyOnFirstRequest=false: GetByName finds a server named "machine-0" + // immediately. That fixture server carries no labels, so it does not match + // this request's providerSpec labels and createMachineValidateDeploymentAndCleanup + // correctly treats it as foreign (not created by this request). + mock.SetupServersEndpointOnMux(env.Mux, false) + mock.SetupTestServerEndpointOnMux(env.Mux) + mock.ResetServerDeleteHitCounter() + + secret := &corev1.Secret{ + Data: map[string][]byte{ + "token": []byte(token), + "userData": []byte("dummy-user-data"), + }, + } + + _, err := provider.CreateMachine(ctx, &driver.CreateMachineRequest{ + Machine: mock.NewMachine(-1), + MachineClass: mock.NewMachineClass(), + Secret: secret, + }) + + Expect(err).To(HaveOccurred()) + Expect(mock.ServerDeleteHitCount()).To(Equal(int32(0)), "create-error cleanup must never delete a server found only by name") + }) + + It("fails create when the initial Server.GetByName lookup errors transiently", func() { + ctx := context.Background() + + env := mock.NewMockTestEnv() + defer env.Teardown() + + const token = "getbyname-error-token" + apis.SetClientForToken(token, env.Client) + defer apis.SetClientForToken(token, nil) + + // No /images or /ssh_keys handlers registered: if the GetByName error is + // (incorrectly) swallowed, execution falls through to those lookups and + // fails there instead -- with the wrong error code. Only a fix that + // surfaces the GetByName error directly returns codes.Unavailable here. + env.Mux.HandleFunc("/servers", func(res http.ResponseWriter, req *http.Request) { + if strings.ToLower(req.Method) == "get" { + res.WriteHeader(http.StatusInternalServerError) + return + } + panic("unexpected method on /servers in this test") + }) + + secret := &corev1.Secret{ + Data: map[string][]byte{ + "token": []byte(token), + "userData": []byte("dummy-user-data"), + }, + } + + _, err := provider.CreateMachine(ctx, &driver.CreateMachineRequest{ + Machine: mock.NewMachine(-1), + MachineClass: mock.NewMachineClass(), + Secret: secret, + }) + + Expect(err).To(HaveOccurred()) + + errStatus, ok := err.(*status.Status) + Expect(ok).To(BeTrue()) + Expect(errStatus.Code()).To(Equal(codes.Unavailable)) + }) + + It("returns Unavailable when the image lookup fails transiently", func() { + ctx := context.Background() + + env := mock.NewMockTestEnv() + defer env.Teardown() + + const token = "image-lookup-error-token" + apis.SetClientForToken(token, env.Client) + defer apis.SetClientForToken(token, nil) + + mock.SetupServersEndpointOnMux(env.Mux, true) + env.Mux.HandleFunc("/images", func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(http.StatusInternalServerError) + }) + + secret := &corev1.Secret{ + Data: map[string][]byte{ + "token": []byte(token), + "userData": []byte("dummy-user-data"), + }, + } + + _, err := provider.CreateMachine(ctx, &driver.CreateMachineRequest{ + Machine: mock.NewMachine(-1), + MachineClass: mock.NewMachineClass(), + Secret: secret, + }) + + Expect(err).To(HaveOccurred()) + + errStatus, ok := err.(*status.Status) + Expect(ok).To(BeTrue()) + Expect(errStatus.Code()).To(Equal(codes.Unavailable)) + }) + + It("returns Unavailable when the SSH key lookup fails transiently", func() { + ctx := context.Background() + + env := mock.NewMockTestEnv() + defer env.Teardown() + + const token = "sshkey-lookup-error-token" + apis.SetClientForToken(token, env.Client) + defer apis.SetClientForToken(token, nil) + + mock.SetupServersEndpointOnMux(env.Mux, true) + mock.SetupImagesEndpointOnMux(env.Mux) + env.Mux.HandleFunc("/ssh_keys", func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(http.StatusInternalServerError) + }) + + secret := &corev1.Secret{ + Data: map[string][]byte{ + "token": []byte(token), + "userData": []byte("dummy-user-data"), + }, + } + + _, err := provider.CreateMachine(ctx, &driver.CreateMachineRequest{ + Machine: mock.NewMachine(-1), + MachineClass: mock.NewMachineClass(), + Secret: secret, + }) + + Expect(err).To(HaveOccurred()) + + errStatus, ok := err.(*status.Status) + Expect(ok).To(BeTrue()) + Expect(errStatus.Code()).To(Equal(codes.Unavailable)) + }) + + It("returns Unavailable when the network lookup fails transiently", func() { + ctx := context.Background() + + env := mock.NewMockTestEnv() + defer env.Teardown() + + const token = "network-lookup-error-token" + apis.SetClientForToken(token, env.Client) + defer apis.SetClientForToken(token, nil) + + mock.SetupServersEndpointOnMux(env.Mux, true) + mock.SetupImagesEndpointOnMux(env.Mux) + mock.SetupSshKeysEndpointOnMux(env.Mux) + env.Mux.HandleFunc("/networks", func(res http.ResponseWriter, req *http.Request) { + res.WriteHeader(http.StatusInternalServerError) + }) + + spec := []byte(`{"cluster":"xyz","zone":"hel1-dc2","imageName":"ubuntu-20.04","serverType":"cx11-ceph","sshFingerprint":"00:11:22:33:44:55:66:77:88:99:aa:bb:cc:dd:ee:ff","networkName":"mynet"}`) + + secret := &corev1.Secret{ + Data: map[string][]byte{ + "token": []byte(token), + "userData": []byte("dummy-user-data"), + }, + } + + _, err := provider.CreateMachine(ctx, &driver.CreateMachineRequest{ + Machine: mock.NewMachine(-1), + MachineClass: mock.NewMachineClassWithProviderSpec(spec), + Secret: secret, + }) + + Expect(err).To(HaveOccurred()) + + errStatus, ok := err.(*status.Status) + Expect(ok).To(BeTrue()) + Expect(errStatus.Code()).To(Equal(codes.Unavailable)) + }) }) Describe("#DeleteMachine", func() { @@ -234,6 +444,23 @@ var _ = Describe("MachineController", func() { errStatus: codes.InvalidArgument, }, }), + Entry("server lookup by ID fails transiently", &data{ + setup: setup{}, + action: action{ + &driver.DeleteMachineRequest{ + // Server ID 9999 has no mock handler registered, so the HTTP + // request hits an unmatched path -- a genuine transient-style + // API failure, distinct from a well-formed "not found" response. + Machine: mock.ManipulateMachine(mock.NewMachine(mock.TestServerID), map[string]interface{}{"Spec.ProviderID": fmt.Sprintf("hcloud:///%s/9999", mock.TestZone)}), + MachineClass: mock.NewMachineClass(), + Secret: providerSecret, + }, + }, + expect: expect{ + errToHaveOccurred: true, + errStatus: codes.Unavailable, + }, + }), ) }) @@ -314,7 +541,40 @@ var _ = Describe("MachineController", func() { errStatus: codes.InvalidArgument, }, }), + Entry("server lookup by ID fails transiently", &data{ + setup: setup{}, + action: action{ + &driver.GetMachineStatusRequest{ + // Server ID 9999 has no mock handler registered, so the HTTP + // request hits an unmatched path -- a genuine transient-style + // API failure, distinct from a well-formed "not found" response. + Machine: mock.ManipulateMachine(mock.NewMachine(mock.TestServerID), map[string]interface{}{"Spec.ProviderID": fmt.Sprintf("hcloud:///%s/9999", mock.TestZone)}), + MachineClass: mock.NewMachineClass(), + Secret: providerSecret, + }, + }, + expect: expect{ + errToHaveOccurred: true, + errStatus: codes.Unavailable, + }, + }), ) + + It("returns the real server ID in ProviderID when called with a non-empty providerID", func() { + ctx := context.Background() + + resp, err := provider.GetMachineStatus(ctx, &driver.GetMachineStatusRequest{ + Machine: mock.NewMachine(mock.TestServerID), + MachineClass: mock.NewMachineClass(), + Secret: providerSecret, + }) + + Expect(err).NotTo(HaveOccurred()) + + expectedProviderID := fmt.Sprintf("hcloud:///%s/%d", mock.TestZone, mock.TestServerID) + Expect(resp.ProviderID).To(Equal(expectedProviderID)) + Expect(resp.ProviderID).NotTo(HaveSuffix("/0"), "ProviderID must encode the real server ID, not the zero value left by a shadowed serverID") + }) }) Describe("#ListMachines", func() { diff --git a/pkg/hcloud/types.go b/pkg/hcloud/types.go index 944321bd..33880617 100644 --- a/pkg/hcloud/types.go +++ b/pkg/hcloud/types.go @@ -18,8 +18,8 @@ limitations under the License. package hcloud type CreateMachineMethodData struct { - ServerID int - FloatingIPID int + ServerID int64 + FloatingIPID int64 } type CtxWrapDataKey string diff --git a/pkg/version/version.go b/pkg/version/version.go new file mode 100644 index 00000000..9143950b --- /dev/null +++ b/pkg/version/version.go @@ -0,0 +1,5 @@ +// Package version holds the build-time version of the provider binary. +package version + +// Version is set at build time via -ldflags "-X .../pkg/version.Version=". +var Version = "dev"