From d954dcaa6a314c64fcac1d770874bddd233ec12b Mon Sep 17 00:00:00 2001 From: Serge Logvinov Date: Sat, 21 Dec 2024 08:45:18 +0200 Subject: [PATCH] feat: init project --- .conform.yaml | 56 ++++++ .dockerignore | 22 +++ .github/ISSUE_TEMPLATE/BUG_REPORT.md | 27 +++ .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md | 16 ++ .github/PULL_REQUEST_TEMPLATE.md | 23 +++ .github/dependabot.yml | 50 ++++++ .github/workflows/build-edge.yaml | 57 ++++++ .github/workflows/build-test.yaml | 48 ++++++ .github/workflows/charts.yaml | 28 +++ .github/workflows/conform.yaml | 26 +++ .github/workflows/release-charts.yaml | 43 +++++ .github/workflows/release-please.yml | 22 +++ .github/workflows/release-pre.yaml | 56 ++++++ .github/workflows/release.yaml | 59 +++++++ .github/workflows/stale.yaml | 21 +++ .gitignore | 29 ++++ .golangci.yml | 191 ++++++++++++++++++++ ADOPTERS.md | 10 ++ CODE_OF_CONDUCT.md | 76 ++++++++ CONTRIBUTING.md | 16 ++ LICENSE | 201 ++++++++++++++++++++++ OWNERS | 4 + README.md | 29 ++++ cmd/controller/main.go | 137 +++++++++++++++ docker-compose.yml | 88 ++++++++++ go.mod | 54 ++++++ go.sum | 164 ++++++++++++++++++ pkg/csi/controller.go | 166 ++++++++++++++++++ pkg/csi/driver.go | 27 +++ pkg/csi/helper.go | 50 ++++++ pkg/csi/identity.go | 81 +++++++++ pkg/tools/config.go | 48 ++++++ pkg/tools/nodes.go | 86 +++++++++ pkg/tools/pv.go | 128 ++++++++++++++ 34 files changed, 2139 insertions(+) create mode 100644 .conform.yaml create mode 100644 .dockerignore create mode 100644 .github/ISSUE_TEMPLATE/BUG_REPORT.md create mode 100644 .github/ISSUE_TEMPLATE/FEATURE_REQUEST.md create mode 100644 .github/PULL_REQUEST_TEMPLATE.md create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/build-edge.yaml create mode 100644 .github/workflows/build-test.yaml create mode 100644 .github/workflows/charts.yaml create mode 100644 .github/workflows/conform.yaml create mode 100644 .github/workflows/release-charts.yaml create mode 100644 .github/workflows/release-please.yml create mode 100644 .github/workflows/release-pre.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/stale.yaml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 ADOPTERS.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 OWNERS create mode 100644 README.md create mode 100644 cmd/controller/main.go create mode 100644 docker-compose.yml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/csi/controller.go create mode 100644 pkg/csi/driver.go create mode 100644 pkg/csi/helper.go create mode 100644 pkg/csi/identity.go create mode 100644 pkg/tools/config.go create mode 100644 pkg/tools/nodes.go create mode 100644 pkg/tools/pv.go diff --git a/.conform.yaml b/.conform.yaml new file mode 100644 index 0000000..79b0e71 --- /dev/null +++ b/.conform.yaml @@ -0,0 +1,56 @@ +policies: + - type: commit + spec: + header: + length: 89 + imperative: true + case: lower + invalidLastCharacters: . + body: + required: true + dco: true + gpg: false + spellcheck: + locale: US + maximumOfOneCommit: false + conventional: + types: + - build + - chore + - ci + - docs + - perf + - refactor + - revert + - style + - test + scopes: + - deps + - main + - chart + descriptionLength: 72 + - type: license + spec: + skipPaths: + - .git/ + includeSuffixes: + - .go + excludeSuffixes: + - .pb.go + allowPrecedingComments: false + header: | + /* + Copyright 2023 The Kubernetes Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..72bd916 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,22 @@ +.vscode/ +.github/ +.git/ +**/.gitignore +# +bin/ +charts/ +docs/ +dist/ +hack/ +docker-compose.yml +Dockerfile + +# other +*.md +*.yml +*.zip +*.sql + +# cosign +/cosign.key +/cosign.pub diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md new file mode 100644 index 0000000..9e39483 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md @@ -0,0 +1,27 @@ +--- +name: Bug Report +about: Report a bug. +title: "" +labels: "" +assignees: "" +--- + +## Bug Report + +### Description + +### Logs + +### Environment + +- Plugin version: +- Kubernetes version: [`kubectl version --short`] +- CSI capasity: [`kubectl get csistoragecapacities -ocustom-columns=CLASS:.storageClassName,AVAIL:.capacity,ZONE:.nodeTopology.matchLabels -A`] +- CSI resource on the node: [`kubectl get CSINode -oyaml`] +- Node describe: [`kubectl describe node `] +- OS version [`cat /etc/os-release`] + +### Community Note + +* Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request +* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request diff --git a/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md new file mode 100644 index 0000000..da62ba3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md @@ -0,0 +1,16 @@ +--- +name: Feature Requests +about: Create a feature request. +title: "" +labels: "" +assignees: "" +--- + +## Feature Request + +### Description + +### Community Note + +* Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request +* Please do not leave "+1" or other comments that do not add relevant new information or questions, they generate extra noise for issue followers and do not help prioritize the request diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..7a590ba --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ +# Pull Request + + + +## What? (description) + +## Why? (reasoning) + +## Acceptance + +Please use the following checklist: + +- [ ] you linked an issue (if applicable) +- [ ] you included tests (if applicable) +- [ ] you linted your code (`make lint`) +- [ ] you linted your code (`make unit`) + +> See `make help` for a description of the available targets. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5ee0e17 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,50 @@ +--- + +# See https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + commit-message: + prefix: "chore:" + open-pull-requests-limit: 8 + rebase-strategy: disabled + schedule: + interval: "monthly" + day: "monday" + time: "08:00" + timezone: "UTC" + + - package-ecosystem: "gomod" + directory: "/" + commit-message: + prefix: "chore:" + open-pull-requests-limit: 8 + rebase-strategy: disabled + schedule: + interval: "monthly" + day: "monday" + time: "07:00" + timezone: "UTC" + groups: + k8s.io: + patterns: + - "k8s.io/api" + - "k8s.io/apimachinery" + - "k8s.io/client-go" + - "k8s.io/cloud-provider" + - "k8s.io/component-base" + - "k8s.io/mount-utils" + + - package-ecosystem: "docker" + directory: "/" + commit-message: + prefix: "chore:" + open-pull-requests-limit: 8 + rebase-strategy: disabled + schedule: + interval: "monthly" + day: "monday" + time: "07:00" + timezone: "UTC" diff --git a/.github/workflows/build-edge.yaml b/.github/workflows/build-edge.yaml new file mode 100644 index 0000000..025228b --- /dev/null +++ b/.github/workflows/build-edge.yaml @@ -0,0 +1,57 @@ +name: Build edge + +on: + push: + branches: + - main + paths: + - 'go.mod' + - 'go.sum' + - 'cmd/**' + - 'pkg/**' + - 'Dockerfile' + +jobs: + build-publish: + name: "Build image and publish" + timeout-minutes: 15 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.7.0 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - name: Set up docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Github registry login + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + timeout-minutes: 10 + run: make images + env: + USERNAME: ${{ github.repository_owner }} + PUSH: "true" + TAG: "edge" + - name: Sign images + timeout-minutes: 4 + run: make images-cosign + env: + USERNAME: ${{ github.repository_owner }} + TAG: "edge" diff --git a/.github/workflows/build-test.yaml b/.github/workflows/build-test.yaml new file mode 100644 index 0000000..f810888 --- /dev/null +++ b/.github/workflows/build-test.yaml @@ -0,0 +1,48 @@ +name: Build check + +on: + pull_request: + branches: + - main + paths: + - 'go.mod' + - 'go.sum' + - 'cmd/**' + - 'pkg/**' + - 'Dockerfile' + +jobs: + build: + name: Build + timeout-minutes: 15 + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + permissions: + contents: read + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up go + timeout-minutes: 5 + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.62.2 + args: --timeout=5m --config=.golangci.yml + - name: Unit + run: make unit + - name: Build + timeout-minutes: 10 + run: make images + env: + PLATFORM: linux/amd64 + - name: Check node tools + timeout-minutes: 5 + run: make image-tools-check + env: + PLATFORM: linux/amd64 diff --git a/.github/workflows/charts.yaml b/.github/workflows/charts.yaml new file mode 100644 index 0000000..e760cea --- /dev/null +++ b/.github/workflows/charts.yaml @@ -0,0 +1,28 @@ +name: Helm chart check + +on: + pull_request: + branches: + - main + paths: + - 'charts/**' + +jobs: + helm-lint: + name: Helm chart check + timeout-minutes: 5 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Install chart-testing tools + id: lint + uses: helm/chart-testing-action@v2.6.1 + + - name: Run helm chart linter + run: ct --config hack/ct.yml lint + - name: Run helm template + run: make helm-unit diff --git a/.github/workflows/conform.yaml b/.github/workflows/conform.yaml new file mode 100644 index 0000000..f7c62f1 --- /dev/null +++ b/.github/workflows/conform.yaml @@ -0,0 +1,26 @@ +name: Conformance check + +on: + pull_request: + branches: + - main + +jobs: + conform: + name: Conformance + timeout-minutes: 5 + runs-on: ubuntu-latest + if: github.event.pull_request.draft == false + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.head.sha }} + - name: Checkout main branch + run: git fetch --no-tags origin main:main + + - name: Conform action + uses: talos-systems/conform@v0.1.0-alpha.30 + with: + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-charts.yaml b/.github/workflows/release-charts.yaml new file mode 100644 index 0000000..e0d0bab --- /dev/null +++ b/.github/workflows/release-charts.yaml @@ -0,0 +1,43 @@ +name: HelmChart Release + +on: + push: + branches: + - main + paths: + - 'charts/**' + +jobs: + build-publish: + name: "Publish helm chart" + timeout-minutes: 10 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Install Helm + uses: azure/setup-helm@v4 + with: + version: v3.12.2 + - name: Install Cosign + uses: sigstore/cosign-installer@v3.7.0 + + - name: Github registry login + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Helm release + timeout-minutes: 5 + run: make helm-login helm-release + env: + HELM_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..eefee0b --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,22 @@ +name: Release please + +on: + workflow_dispatch: {} + push: + branches: + - main + +jobs: + release-please: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Create release PR + id: release + uses: googleapis/release-please-action@v4 + with: + config-file: hack/release-please-config.json + manifest-file: hack/release-please-manifest.json diff --git a/.github/workflows/release-pre.yaml b/.github/workflows/release-pre.yaml new file mode 100644 index 0000000..05107e6 --- /dev/null +++ b/.github/workflows/release-pre.yaml @@ -0,0 +1,56 @@ +name: Release check + +on: + pull_request: + branches: + - main + +jobs: + build-publish: + name: "Check release docs" + timeout-minutes: 15 + runs-on: ubuntu-latest + if: startsWith(github.head_ref, 'release-') + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Release version + shell: bash + id: release + run: | + echo "TAG=v${GITHUB_HEAD_REF:8}" >> "$GITHUB_ENV" + + - name: Helm docs + uses: gabe565/setup-helm-docs-action@v1 + with: + version: v1.11.3 + + - name: Generate + run: make docs + - name: Check + run: git diff --exit-code + + build-publish-cli: + name: "Check cli tool" + timeout-minutes: 15 + runs-on: ubuntu-latest + if: startsWith(github.head_ref, 'release-') + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: '~> v2' + args: check + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..86e46d3 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,59 @@ +name: Release + +on: + workflow_dispatch: {} + push: + tags: + - 'v*' + +jobs: + build-publish: + name: "Build image and publish" + timeout-minutes: 15 + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + id-token: write + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Unshallow + run: git fetch --prune --unshallow + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.7.0 + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + - name: Set up docker buildx + uses: docker/setup-buildx-action@v3 + + - name: Github registry login + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + timeout-minutes: 10 + run: make images + env: + PUSH: "true" + TAG: "edge" + - name: Sign images + timeout-minutes: 4 + run: make images-cosign + env: + TAG: "edge" + + - name: Build and push + timeout-minutes: 10 + run: make images + env: + PUSH: "true" + - name: Sign images + timeout-minutes: 4 + run: make images-cosign diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml new file mode 100644 index 0000000..b1151b8 --- /dev/null +++ b/.github/workflows/stale.yaml @@ -0,0 +1,21 @@ +name: Close stale issues + +on: + schedule: + - cron: '30 8 * * *' + +jobs: + stale: + name: Check stale issues + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v9 + with: + stale-issue-message: This issue is stale because it has been open 180 days with no activity. Remove stale label or comment or this will be closed in 14 days. + close-issue-message: This issue was closed because it has been stalled for 14 days with no activity. + days-before-issue-stale: 180 + days-before-issue-close: 14 + days-before-pr-close: -1 # never close PRs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f2b1a76 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/ +dist/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# cosign +/cosign.key +/cosign.pub + +.idea \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..6e43014 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,191 @@ +# This file contains all available configuration options +# with their default values. + +# options for analysis running +run: + # default concurrency is a available CPU number + # concurrency: 4 + + # exit code when at least one issue was found, default is 1 + issues-exit-code: 1 + + # include test files or not, default is true + tests: true + + # which files to skip: they will be analyzed, but issues from them + # won't be reported. Default value is empty list, but there is + # no need to include all autogenerated files, we confidently recognize + # autogenerated files. If it's not please let us know. + exclude-files: + - charts/ + - docs/ + + # list of build tags, all linters use it. Default is empty list. + build-tags: + - integration + - integration_api + - integration_cli + - integration_k8s + - integration_provision + +# output configuration options +output: + # colored-line-number|line-number|json|tab|checkstyle, default is "colored-line-number" + formats: + - format: line-number + path: stdout + print-issued-lines: true + print-linter-name: true + uniq-by-line: true + sort-results: true + +# all available settings of specific linters +linters-settings: + errcheck: + # report about not checking of errors in type assetions: `a := b.(MyStruct)`; + # default is false: such cases aren't reported by default. + check-type-assertions: true + + # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; + # default is false: such cases aren't reported by default. + check-blank: true + govet: {} + gofmt: + # simplify code: gofmt with `-s` option, true by default + simplify: true + gocyclo: + # minimal code complexity to report, 30 by default (but we recommend 10-20) + min-complexity: 30 + dupl: + # tokens count to trigger issue, 150 by default + threshold: 100 + goconst: + # minimal length of string constant, 3 by default + min-len: 3 + # minimal occurrences count to trigger, 3 by default + min-occurrences: 3 + misspell: + # Correct spellings using locale preferences for US or UK. + # Default is to use a neutral variety of English. + # Setting locale to US will correct the British spelling of 'colour' to 'color'. + locale: US + lll: + # max line length, lines longer will be reported. Default is 120. + # '\t' is counted as 1 character by default, and can be changed with the tab-width option + line-length: 200 + # tab width in spaces. Default to 1. + tab-width: 1 + unparam: + # Inspect exported functions, default is false. Set to true if no external program/library imports your code. + # XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: + # if it's called for subdir of a project it can't find external interfaces. All text editor integrations + # with golangci-lint call it on a directory with the changed file. + check-exported: false + nakedret: + # make an issue if func has more lines of code than this setting and it has naked returns; default is 30 + max-func-lines: 30 + nolintlint: + allow-unused: false + allow-no-explanation: [] + require-explanation: false + require-specific: true + prealloc: + # XXX: we don't recommend using this linter before doing performance profiling. + # For most programs usage of prealloc will be a premature optimization. + + # Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. + # True by default. + simple: true + range-loops: true # Report preallocation suggestions on range loops, true by default + for-loops: false # Report preallocation suggestions on for loops, false by default + gci: + sections: + - standard # Captures all standard packages if they do not match another section. + - default # Contains all imports that could not be matched to another section type. + - prefix(github.com/sergelogvinov) # Groups all imports with the specified Prefix. + - prefix(k8s.io) # Groups all imports with the specified Prefix. + cyclop: + # the maximal code complexity to report + max-complexity: 30 + gomoddirectives: + replace-local: true + replace-allow-list: [] + retract-allow-no-explanation: false + exclude-forbidden: true + +linters: + enable-all: true + disable: + - depguard + - errorlint + - exhaustruct + - err113 + - forbidigo + - forcetypeassert + - funlen + - gochecknoglobals + - gochecknoinits + - gocognit + - godox + - godot + - gosec + - mnd + - ireturn # we return interfaces + - maintidx + - nestif + - nilnil # we return "nil, nil" + - nonamedreturns + - nolintlint + - paralleltest + - promlinter # https://github.com/golangci/golangci-lint/issues/2222 + - tagliatelle # we have many different conventions + - tagalign # too annoying + - testifylint + - testpackage + - thelper + - typecheck + - varnamelen # too annoying + - wrapcheck + - perfsprint + - exportloopref + + # temporarily disabled linters + - copyloopvar + - intrange + + # abandoned linters for which golangci shows the warning that the repo is archived by the owner + - perfsprint + + disable-all: false + fast: false + +issues: + # List of regexps of issue texts to exclude, empty list by default. + # But independently from this option we use default exclude patterns, + # it can be disabled by `exclude-use-default: false`. To list all + # excluded by default patterns execute `golangci-lint run --help` + exclude: + - package comment should be of the form "Package services ..." # revive + - ^ST1000 # ST1000: at least one file in a package should have a package comment (stylecheck) + + exclude-rules: + + # Independently from option `exclude` we use default exclude patterns, + # it can be disabled by this option. To list all + # excluded by default patterns execute `golangci-lint run --help`. + # Default value for this option is true. + exclude-use-default: false + + # Maximum issues count per one linter. Set to 0 to disable. Default is 50. + max-issues-per-linter: 0 + + # Maximum count of issues with the same text. Set to 0 to disable. Default is 3. + max-same-issues: 0 + + # Show only new issues: if there are unstaged changes or untracked files, + # only those changes are analyzed, else only changes in HEAD~ are analyzed. + # It's a super-useful option for integration of golangci-lint into existing + # large codebase. It's not practical to fix all existing issues at the moment + # of integration: much better don't allow issues in new code. + # Default is false. + new: false diff --git a/ADOPTERS.md b/ADOPTERS.md new file mode 100644 index 0000000..c708857 --- /dev/null +++ b/ADOPTERS.md @@ -0,0 +1,10 @@ +# Adopters + +This is a list of organizations or projects that have adopted the Proxmox CSI driver. + +## Adopters (listed alphabetically) + +### Template + +* **[Project/User name](Project URL)** + Description & Use cases diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..96df771 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +## Code of Conduct + +### Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +### Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +### Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +### Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +### Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at [INSERT EMAIL ADDRESS]. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +### Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..6aa1132 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,16 @@ +# Contributing + +## Developer Certificate of Origin + +All commits require a [DCO](https://developercertificate.org/) sign-off. +This is done by committing with the `--signoff` flag. + +## Development + +The build process for this project is designed to run entirely in containers. +To get started, run `make help` and follow the instructions. + +## Conformance + +To verify conformance status, run `make conformance`. +This runs a series of tests on the working tree and is required to pass before a contribution is accepted. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..a3ef0a1 --- /dev/null +++ b/OWNERS @@ -0,0 +1,4 @@ +approvers: +- sergelogvinov +reviewers: +- sergelogvinov diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ce6ef7 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Virtual CSI Plugin + +## FAQ + +See [FAQ](docs/faq.md) for answers to common questions. + +## Resources + +* https://arslan.io/2018/06/21/how-to-write-a-container-storage-interface-csi-plugin/ +* https://kubernetes-csi.github.io/docs/ + +## Contributing + +Contributions are welcomed and appreciated! +See [Contributing](CONTRIBUTING.md) for our guidelines. + +## License + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +[http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/cmd/controller/main.go b/cmd/controller/main.go new file mode 100644 index 0000000..7a84617 --- /dev/null +++ b/cmd/controller/main.go @@ -0,0 +1,137 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Virtual CSI Plugin Controller +package main + +import ( + "context" + "flag" + "net" + "os" + + proto "github.com/container-storage-interface/spec/lib/go/csi" + "google.golang.org/grpc" + + "github.com/sergelogvinov/virtual-csi-plugin/pkg/csi" + "github.com/sergelogvinov/virtual-csi-plugin/pkg/tools" + + clientkubernetes "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" +) + +var ( + version string + commit string + + showVersion = flag.Bool("version", false, "Print the version and exit.") + csiEndpoint = flag.String("csi-address", "unix:///csi/csi.sock", "CSI Endpoint") + + metricsAddress = flag.String("metrics-address", "", "The TCP network address where the HTTP server for metrics, will listen (example: `:8080`). By default the server is disabled.") + metricsPath = flag.String("metrics-path", "/metrics", "The HTTP path where prometheus metrics will be exposed.") + + kubeconfig = flag.String("kubeconfig", "", "Absolute path to the kubeconfig file. Either this or master needs to be set if the provisioner is being run out of cluster.") +) + +func main() { + klog.InitFlags(nil) + flag.Set("logtostderr", "true") //nolint: errcheck + flag.Parse() + + klog.V(2).InfoS("Version", "version", csi.DriverVersion, "csiVersion", csi.DriverSpecVersion, "gitVersion", version, "gitCommit", commit) + + if *showVersion { + klog.Infof("Driver version %v, GitVersion %s", csi.DriverVersion, version) + os.Exit(0) + } + + if *csiEndpoint == "" { + klog.Error("csi-address must be provided") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + + kconfig, _, err := tools.BuildConfig(*kubeconfig, "") + if err != nil { + klog.Error(err, "Failed to build a Kubernetes config") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + + clientset, err := clientkubernetes.NewForConfig(kconfig) + if err != nil { + klog.Error(err, "Failed to create a Clientset") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + + scheme, addr, err := csi.ParseEndpoint(*csiEndpoint) + if err != nil { + klog.Error(err, "Failed to parse endpoint") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + + listener, err := net.Listen(scheme, addr) + if err != nil { + klog.ErrorS(err, "Failed to listen", "address", *csiEndpoint) + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + + logErr := func(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + resp, rpcerr := handler(ctx, req) + if rpcerr != nil { + klog.ErrorS(rpcerr, "GRPC error") + } + + return resp, rpcerr + } + + opts := []grpc.ServerOption{ + grpc.UnaryInterceptor(logErr), + } + + // Prepare http endpoint for metrics + // mux := http.NewServeMux() + // if *metricsAddress != "" { + // mux.Handle("/metrics", legacyregistry.Handler()) + + // go func() { + // klog.V(2).InfoS("Metrics listening", "address", *metricsAddress, "metricsPath", *metricsPath) + + // err := http.ListenAndServe(*metricsAddress, mux) + // if err != nil { + // klog.ErrorS(err, "Failed to start HTTP server at specified address and metrics path", "address", addr, "metricsPath", *metricsPath) + // } + // }() + // } + + srv := grpc.NewServer(opts...) + + identityService := csi.NewIdentityService() + + controllerService, err := csi.NewControllerService(clientset) + if err != nil { + klog.ErrorS(err, "Failed to create controller service") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } + + proto.RegisterControllerServer(srv, controllerService) + proto.RegisterIdentityServer(srv, identityService) + + klog.InfoS("Listening for connection on address", "address", listener.Addr()) + + if err := srv.Serve(listener); err != nil { + klog.ErrorS(err, "Failed to run driver") + klog.FlushAndExit(klog.ExitFlushTimeout, 1) + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..19406c5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,88 @@ +services: + base: + image: registry.k8s.io/pause:3.10 + ports: + - "8080:8080" + plugin: + build: + context: . + target: develop + network_mode: "service:base" + environment: + # NODE_NAME: worker-11 + KUBECONFIG: /etc/kubernetes/kubeconfig + # UNSAFEMOUNT: "true" + command: + - "make" + - "run" + volumes: + - type: volume + source: socket-dir + target: /csi + - type: bind + source: ./hack + target: /etc/kubernetes + - type: bind + source: ./ + target: /src + csi-attacher: + image: registry.k8s.io/sig-storage/csi-attacher:v4.8.0 + network_mode: "service:base" + command: + - "--v=5" + - "--csi-address=unix:///csi/csi.sock" + - "--leader-election=false" + - "--default-fstype=ext4" + - "--kubeconfig=/etc/kubernetes/kubeconfig" + volumes: + - type: volume + source: socket-dir + target: /csi + - type: bind + source: ./hack + target: /etc/kubernetes + csi-provisioner: + image: registry.k8s.io/sig-storage/csi-provisioner:v5.1.0 + network_mode: "service:base" + command: + - "--v=5" + - "--csi-address=unix:///csi/csi.sock" + - "--leader-election=false" + - "--default-fstype=ext4" + - "--feature-gates=Topology=true" + # - "--enable-capacity" + # - "--capacity-ownerref-level=-1" + # - "--capacity-poll-interval=2m" + - "--extra-create-metadata=true" + # - "--node-deployment" + - "--kubeconfig=/etc/kubernetes/kubeconfig" + environment: + NAMESPACE: csi-proxmox + POD_NAME: csi-provisioner + # NODE_NAME: worker-11 + volumes: + - type: volume + source: socket-dir + target: /csi + - type: bind + source: ./hack + target: /etc/kubernetes + # csi-node-driver-registrar: + # image: registry.k8s.io/sig-storage/csi-node-driver-registrar:v2.11.1 + # network_mode: "service:base" + # command: + # - "--v=5" + # - "--csi-address=unix:///csi/csi.sock" + # - "--kubelet-registration-path=/var/lib/kubelet/plugins/csi.proxmox.sinextra.dev/csi.sock" + # environment: + # KUBE_NODE_NAME: worker-11 + # volumes: + # - type: volume + # source: socket-dir + # target: /csi + # - type: bind + # source: ./hack + # target: /etc/kubernetes + +volumes: + socket-dir: diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8816aa9 --- /dev/null +++ b/go.mod @@ -0,0 +1,54 @@ +module github.com/sergelogvinov/virtual-csi-plugin + +go 1.23.4 + +require ( + github.com/container-storage-interface/spec v1.11.0 + github.com/golang/protobuf v1.5.4 + github.com/kubernetes-csi/csi-lib-utils v0.20.0 + google.golang.org/grpc v1.69.0 + k8s.io/api v0.32.0 + k8s.io/apimachinery v0.32.0 + k8s.io/client-go v0.32.0 + k8s.io/klog/v2 v2.130.1 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.1 // indirect + github.com/fxamacker/cbor/v2 v2.7.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/gogo/protobuf v1.3.2 // indirect + github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/go-cmp v0.6.0 // indirect + github.com/google/gofuzz v1.2.0 // indirect + github.com/google/uuid v1.6.0 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/x448/float16 v0.8.4 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/oauth2 v0.24.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/time v0.8.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 // indirect + google.golang.org/protobuf v1.36.0 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect + k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect + sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3a01474 --- /dev/null +++ b/go.sum @@ -0,0 +1,164 @@ +github.com/container-storage-interface/spec v1.11.0 h1:H/YKTOeUZwHtyPOr9raR+HgFmGluGCklulxDYxSdVNM= +github.com/container-storage-interface/spec v1.11.0/go.mod h1:DtUvaQszPml1YJfIK7c00mlv6/g4wNMLanLgiUbKFRI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +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/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +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-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/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/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/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes-csi/csi-lib-utils v0.20.0 h1:JTvHRJugn+cByMnIU4nCnqPqOOUhuPzhlLqRvenwjDA= +github.com/kubernetes-csi/csi-lib-utils v0.20.0/go.mod h1:3b/HFVURW11oxV/gUAKyhhkvFpxXO/zRdvh1wdEfCZY= +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/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/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/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/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/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +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/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/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw= +go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I= +go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ= +go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s= +go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck= +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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= +golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= +golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +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/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.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= +golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= +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= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484 h1:Z7FRVJPSMaHQxD0uXU8WdgFh8PseLM8Q8NzhnpMrBhQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241216192217-9240e9c98484/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA= +google.golang.org/grpc v1.69.0 h1:quSiOM1GJPmPH5XtU+BCoVXcDVJJAzNcoyfC2cCjGkI= +google.golang.org/grpc v1.69.0/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v1.36.0 h1:mjIs9gYtt56AzC4ZaffQuh88TZurBGhIJMBZGSxNerQ= +google.golang.org/protobuf v1.36.0/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= +k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= +k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= +k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= +k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= +k8s.io/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-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= +k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +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/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= +sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= diff --git a/pkg/csi/controller.go b/pkg/csi/controller.go new file mode 100644 index 0000000..4dc1d30 --- /dev/null +++ b/pkg/csi/controller.go @@ -0,0 +1,166 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package csi + +import ( + "context" + "sync" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/kubernetes-csi/csi-lib-utils/protosanitizer" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + clientkubernetes "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" +) + +var controllerCaps = []csi.ControllerServiceCapability_RPC_Type{ + csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME, + csi.ControllerServiceCapability_RPC_GET_CAPACITY, + csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, + csi.ControllerServiceCapability_RPC_GET_VOLUME, + csi.ControllerServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER, +} + +// ControllerService is the controller service for the CSI driver +type ControllerService struct { + Kclient clientkubernetes.Interface + + volumeLocks sync.Mutex + + csi.UnimplementedControllerServer +} + +// NewControllerService returns a new controller service +func NewControllerService(kclient *clientkubernetes.Clientset) (*ControllerService, error) { + return &ControllerService{ + Kclient: kclient, + }, nil +} + +// CreateVolume creates a volume +func (d *ControllerService) CreateVolume(_ context.Context, request *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { + klog.V(4).InfoS("CreateVolume: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// DeleteVolume deletes a volume. +func (d *ControllerService) DeleteVolume(_ context.Context, request *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { + klog.V(4).InfoS("DeleteVolume: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ControllerGetCapabilities get controller capabilities. +func (d *ControllerService) ControllerGetCapabilities(_ context.Context, _ *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) { + klog.V(4).InfoS("ControllerGetCapabilities: called") + + caps := []*csi.ControllerServiceCapability{} + + for _, cap := range controllerCaps { + c := &csi.ControllerServiceCapability{ + Type: &csi.ControllerServiceCapability_Rpc{ + Rpc: &csi.ControllerServiceCapability_RPC{ + Type: cap, + }, + }, + } + caps = append(caps, c) + } + + return &csi.ControllerGetCapabilitiesResponse{Capabilities: caps}, nil +} + +// ControllerPublishVolume publish a volume +func (d *ControllerService) ControllerPublishVolume(ctx context.Context, request *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { + klog.V(4).InfoS("ControllerPublishVolume: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ControllerUnpublishVolume unpublish a volume +func (d *ControllerService) ControllerUnpublishVolume(ctx context.Context, request *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { + klog.V(4).InfoS("ControllerUnpublishVolume: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ValidateVolumeCapabilities validate volume capabilities +func (d *ControllerService) ValidateVolumeCapabilities(_ context.Context, request *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) { + klog.V(4).InfoS("ValidateVolumeCapabilities: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ListVolumes list volumes +func (d *ControllerService) ListVolumes(_ context.Context, request *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) { + klog.V(4).InfoS("ListVolumes: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// GetCapacity get capacity +func (d *ControllerService) GetCapacity(_ context.Context, request *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { + klog.V(5).InfoS("GetCapacity: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// CreateSnapshot create a snapshot +func (d *ControllerService) CreateSnapshot(_ context.Context, request *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) { + klog.V(4).InfoS("CreateSnapshot: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// DeleteSnapshot delete a snapshot +func (d *ControllerService) DeleteSnapshot(_ context.Context, request *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) { + klog.V(4).InfoS("DeleteSnapshot: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ListSnapshots list snapshots +func (d *ControllerService) ListSnapshots(_ context.Context, request *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) { + klog.V(4).InfoS("ListSnapshots: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ControllerExpandVolume expand a volume +func (d *ControllerService) ControllerExpandVolume(_ context.Context, request *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { + klog.V(4).InfoS("ControllerExpandVolume: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ControllerGetVolume get a volume +func (d *ControllerService) ControllerGetVolume(_ context.Context, request *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) { + klog.V(4).InfoS("ControllerGetVolume: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} + +// ControllerModifyVolume modify a volume +func (d *ControllerService) ControllerModifyVolume(_ context.Context, request *csi.ControllerModifyVolumeRequest) (*csi.ControllerModifyVolumeResponse, error) { + klog.V(4).InfoS("ControllerModifyVolume: called", "args", protosanitizer.StripSecrets(request)) + + return nil, status.Error(codes.Unimplemented, "") +} diff --git a/pkg/csi/driver.go b/pkg/csi/driver.go new file mode 100644 index 0000000..54c31d2 --- /dev/null +++ b/pkg/csi/driver.go @@ -0,0 +1,27 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package csi contains the CSI driver implementation +package csi + +const ( + // DriverName is the name of the CSI driver + DriverName = "csi.virtual.sinextra.dev" + // DriverVersion is the version of the CSI driver + DriverVersion = "0.1.0" + // DriverSpecVersion CSI spec version + DriverSpecVersion = "1.9.0" +) diff --git a/pkg/csi/helper.go b/pkg/csi/helper.go new file mode 100644 index 0000000..24ca6a2 --- /dev/null +++ b/pkg/csi/helper.go @@ -0,0 +1,50 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package csi + +import ( + "fmt" + "net/url" + "os" + "path" + "path/filepath" + "strings" +) + +// ParseEndpoint parses the endpoint string and returns the scheme and address +func ParseEndpoint(endpoint string) (string, string, error) { + u, err := url.Parse(endpoint) + if err != nil { + return "", "", fmt.Errorf("could not parse endpoint: %v", err) + } + + addr := path.Join(u.Host, filepath.FromSlash(u.Path)) + + scheme := strings.ToLower(u.Scheme) + switch scheme { + case "tcp": + case "unix": + addr = path.Join("/", addr) + if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { + return "", "", fmt.Errorf("could not remove unix domain socket %q: %v", addr, err) + } + default: + return "", "", fmt.Errorf("unsupported protocol: %s", scheme) + } + + return scheme, addr, nil +} diff --git a/pkg/csi/identity.go b/pkg/csi/identity.go new file mode 100644 index 0000000..496715c --- /dev/null +++ b/pkg/csi/identity.go @@ -0,0 +1,81 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package csi + +import ( + "context" + + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/golang/protobuf/ptypes/wrappers" + + "k8s.io/klog/v2" +) + +// IdentityService is the identity service for the CSI driver +type IdentityService struct { + csi.UnimplementedIdentityServer +} + +// NewIdentityService returns a new identity service +func NewIdentityService() *IdentityService { + return &IdentityService{} +} + +// GetPluginInfo returns the name and version of the plugin +func (d *IdentityService) GetPluginInfo(context.Context, *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) { + klog.V(5).InfoS("GetPluginInfo: called") + + return &csi.GetPluginInfoResponse{ + Name: DriverName, + VendorVersion: DriverVersion, + }, nil +} + +// GetPluginCapabilities returns the capabilities of the plugin +func (d *IdentityService) GetPluginCapabilities(context.Context, *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) { + klog.V(5).InfoS("GetPluginCapabilities: called") + + resp := &csi.GetPluginCapabilitiesResponse{ + Capabilities: []*csi.PluginCapability{ + { + Type: &csi.PluginCapability_Service_{ + Service: &csi.PluginCapability_Service{ + Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, + }, + }, + }, + { + Type: &csi.PluginCapability_Service_{ + Service: &csi.PluginCapability_Service{ + Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS, + }, + }, + }, + }, + } + + return resp, nil +} + +// Probe returns the health and readiness of the plugin +func (d *IdentityService) Probe(_ context.Context, _ *csi.ProbeRequest) (*csi.ProbeResponse, error) { + klog.V(5).InfoS("Probe: called") + + return &csi.ProbeResponse{ + Ready: &wrappers.BoolValue{Value: true}, + }, nil +} diff --git a/pkg/tools/config.go b/pkg/tools/config.go new file mode 100644 index 0000000..20b1c37 --- /dev/null +++ b/pkg/tools/config.go @@ -0,0 +1,48 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// Package tools implements tools to work with kubeernetes. +package tools + +import ( + "fmt" + + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" +) + +// BuildConfig returns a kubernetes client configuration and namespace. +func BuildConfig(kubeconfig, namespace string) (k *rest.Config, ns string, err error) { + clientConfigLoadingRules := clientcmd.NewDefaultClientConfigLoadingRules() + + if kubeconfig != "" { + clientConfigLoadingRules.ExplicitPath = kubeconfig + } + + config := clientcmd.NewNonInteractiveDeferredLoadingClientConfig( + clientConfigLoadingRules, &clientcmd.ConfigOverrides{}) + + if namespace == "" { + namespace, _, err = config.Namespace() + if err != nil { + return nil, "", fmt.Errorf("failed to get namespace from kubeconfig: %w", err) + } + } + + k, err = config.ClientConfig() + + return k, namespace, err +} diff --git a/pkg/tools/nodes.go b/pkg/tools/nodes.go new file mode 100644 index 0000000..04df285 --- /dev/null +++ b/pkg/tools/nodes.go @@ -0,0 +1,86 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tools + +import ( + "context" + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + clientkubernetes "k8s.io/client-go/kubernetes" +) + +// CSINodes returns a list of nodes that have the specified CSI driver name. +func CSINodes(ctx context.Context, kclient *clientkubernetes.Clientset, csiDriverName string) ([]string, error) { + nodes := []string{} + + csinodes, err := kclient.StorageV1().CSINodes().List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to list CSINodes: %v", err) + } + + for _, csinode := range csinodes.Items { + for _, driver := range csinode.Spec.Drivers { + if driver.Name == csiDriverName { + nodes = append(nodes, driver.NodeID) + + break + } + } + } + + return nodes, nil +} + +// CondonNodes condones the specified nodes. +func CondonNodes(ctx context.Context, kclient *clientkubernetes.Clientset, nodes []string) ([]string, error) { + cordonedNodes := []string{} + patch := []byte(`{"spec":{"unschedulable":true}}`) + + for _, node := range nodes { + nodeStatus, err := kclient.CoreV1().Nodes().Get(ctx, node, metav1.GetOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to get node status: %v", err) + } + + if !nodeStatus.Spec.Unschedulable { + _, err = kclient.CoreV1().Nodes().Patch(ctx, node, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to cordon node: %v", err) + } + + cordonedNodes = append(cordonedNodes, node) + } + } + + return cordonedNodes, nil +} + +// UncondonNodes uncondones the specified nodes. +func UncondonNodes(ctx context.Context, kclient *clientkubernetes.Clientset, nodes []string) error { + patch := []byte(`{"spec":{"unschedulable":false}}`) + + for _, node := range nodes { + _, err := kclient.CoreV1().Nodes().Patch(ctx, node, types.MergePatchType, patch, metav1.PatchOptions{}) + if err != nil { + return fmt.Errorf("failed to uncordon node: %v", err) + } + } + + return nil +} diff --git a/pkg/tools/pv.go b/pkg/tools/pv.go new file mode 100644 index 0000000..62091fa --- /dev/null +++ b/pkg/tools/pv.go @@ -0,0 +1,128 @@ +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package tools + +import ( + "context" + "encoding/json" + "fmt" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/watch" + clientkubernetes "k8s.io/client-go/kubernetes" +) + +// PVCResources returns the PersistentVolumeClaim and PersistentVolume resources. +func PVCResources(ctx context.Context, clientset *clientkubernetes.Clientset, namespace, pvcName string) (*corev1.PersistentVolumeClaim, *corev1.PersistentVolume, error) { + pvc, err := clientset.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, pvcName, metav1.GetOptions{}) + if err != nil { + return nil, nil, fmt.Errorf("failed to get PersistentVolumeClaims: %v", err) + } + + pv, err := clientset.CoreV1().PersistentVolumes().Get(ctx, pvc.Spec.VolumeName, metav1.GetOptions{}) + if err != nil { + return nil, nil, fmt.Errorf("failed to get PersistentVolumes: %v", err) + } + + return pvc, pv, nil +} + +// PVCPodUsage returns the list of pods and the node that are using the specified PersistentVolumeClaim. +func PVCPodUsage(ctx context.Context, clientset *clientkubernetes.Clientset, namespace, pvcName string) (pods []string, node string, err error) { + podList, err := clientset.CoreV1().Pods(namespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, "", fmt.Errorf("failed to list pods: %v", err) + } + + for _, pod := range podList.Items { + if pod.Status.Phase != corev1.PodPending { + for _, volume := range pod.Spec.Volumes { + if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvcName { + pods = append(pods, pod.Name) + node = pod.Spec.NodeName + + break + } + } + } + } + + return pods, node, nil +} + +// PVCCreateOrUpdate creates or updates the specified PersistentVolumeClaim resource. +func PVCCreateOrUpdate( + ctx context.Context, + clientset *clientkubernetes.Clientset, + pvc *corev1.PersistentVolumeClaim, +) (*corev1.PersistentVolumeClaim, error) { + res, err := clientset.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(ctx, pvc, metav1.CreateOptions{}) + if err != nil { + patch := corev1.PersistentVolumeClaim{ + Spec: corev1.PersistentVolumeClaimSpec{ + VolumeName: pvc.Spec.VolumeName, + }, + } + + patchBytes, err := json.Marshal(&patch) + if err != nil { + return nil, fmt.Errorf("failed to json.Marshal PVC: %w", err) + } + + return clientset.CoreV1().PersistentVolumeClaims(pvc.Namespace).Patch(ctx, pvc.Name, types.MergePatchType, patchBytes, metav1.PatchOptions{}) + } + + return res, err +} + +// PVWaitDelete waits for the specified PersistentVolume to be deleted. +func PVWaitDelete(ctx context.Context, clientset *clientkubernetes.Clientset, pvName string) error { + _, err := clientset.CoreV1().PersistentVolumes().Get(ctx, pvName, metav1.GetOptions{}) + if err != nil { + return nil //nolint: nilerr + } + + watcher, err := clientset.CoreV1().PersistentVolumes().Watch(ctx, metav1.ListOptions{ + FieldSelector: "metadata.name=" + pvName, + }) + if err != nil { + return err + } + + defer watcher.Stop() + + timeout := time.After(10 * time.Minute) + + for { + select { + case event, ok := <-watcher.ResultChan(): + if !ok { + return fmt.Errorf("watch channel closed unexpectedly") + } + + if event.Type == watch.Deleted { + return nil + } + + case <-timeout: + return fmt.Errorf("timeout waiting for PersistentVolume %s to be deleted", pvName) + } + } +}