├── .editorconfig ├── .github ├── dependabot.yml ├── kind-test │ ├── pod.yaml │ └── pvc.yaml └── workflows │ ├── release.yaml │ └── tests.yaml ├── .gitignore ├── .golangci.yml ├── .goreleaser.yml ├── .krew.yaml ├── CODEOWNERS ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── cmd └── rename-pvc │ └── main.go ├── go.mod ├── go.sum └── pkg └── renamepvc ├── renamepvc.go └── renamepvc_test.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | 9 | [{*.go,Makefile,.gitmodules,go.mod,go.sum}] 10 | indent_style = tab 11 | 12 | [*.md] 13 | indent_style = tab 14 | trim_trailing_whitespace = false 15 | 16 | [*.{yml,yaml,json}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [*.{js,jsx,ts,tsx,css,less,sass,scss,vue,py}] 21 | indent_style = space 22 | indent_size = 4 23 | 24 | [{*.sh,pre-push}] 25 | # we choose the google style guide here: https://google.github.io/styleguide/shellguide.html 26 | indent_style = space 27 | indent_size = 2 28 | 29 | shell_variant = posix # like -ln=posix 30 | binary_next_line = true # like -bn 31 | switch_case_indent = true # like -ci 32 | space_redirects = true # like -sr 33 | keep_padding = true # like -kp 34 | function_next_line = true # like -fn 35 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | reviewers: 13 | - "dergeberl" 14 | - "rgroemmer" 15 | - package-ecosystem: "github-actions" 16 | directory: "/" 17 | schedule: 18 | interval: "daily" 19 | reviewers: 20 | - "dergeberl" 21 | - "rgroemmer" 22 | -------------------------------------------------------------------------------- /.github/kind-test/pod.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Pod 4 | metadata: 5 | name: test 6 | spec: 7 | containers: 8 | - image: nginx 9 | name: nginx 10 | volumeMounts: 11 | - name: pvc 12 | mountPath: /test 13 | volumes: 14 | - name: pvc 15 | persistentVolumeClaim: 16 | claimName: pvc 17 | -------------------------------------------------------------------------------- /.github/kind-test/pvc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: pvc 6 | namespace: default 7 | spec: 8 | accessModes: 9 | - ReadWriteOnce 10 | resources: 11 | requests: 12 | storage: 3Gi 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*.*" 7 | 8 | jobs: 9 | release: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | - name: Set up Go 17 | uses: actions/setup-go@v5 18 | with: 19 | go-version: 1.24.x 20 | - name: Run GoReleaser 21 | uses: goreleaser/goreleaser-action@v6 22 | with: 23 | version: latest 24 | args: release --clean 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | - name: Update new version in krew-index 28 | uses: rajatjindal/krew-release-bot@v0.0.47 29 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # based on https://github.com/mvdan/github-actions-golang 3 | name: Test 4 | 5 | on: 6 | pull_request: 7 | branches: ["main"] 8 | paths-ignore: ["docs/**"] 9 | 10 | # Allows you to run this workflow manually from the Actions tab 11 | workflow_dispatch: 12 | 13 | jobs: 14 | test: 15 | strategy: 16 | matrix: 17 | go-version: [1.24.x] 18 | os: [ubuntu-latest, macos-latest, windows-latest] 19 | runs-on: ${{ matrix.os }} 20 | 21 | steps: 22 | - name: Install Go 23 | uses: actions/setup-go@v5 24 | with: 25 | go-version: ${{ matrix.go-version }} 26 | 27 | - name: Checkout code 28 | uses: actions/checkout@v4 29 | 30 | # cache go modules 31 | - uses: actions/cache@v4 32 | with: 33 | # In order: 34 | # * Module download cache 35 | # * Build cache (Linux) 36 | # * Build cache (Mac) 37 | # * Build cache (Windows) 38 | path: | 39 | ~/go/pkg/mod 40 | ~/.cache/go-build 41 | ~/Library/Caches/go-build 42 | %LocalAppData%\go-build 43 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 44 | restore-keys: | 45 | ${{ runner.os }}-go- 46 | - name: Run linting & unittest 47 | if: matrix.os == 'ubuntu-latest' 48 | run: make ci 49 | 50 | - name: Build package 51 | run: make build 52 | 53 | - name: Execute build 54 | if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' 55 | run: ./out/bin/rename-pvc --help 56 | 57 | - name: Execute build 58 | if: matrix.os == 'windows-latest' 59 | run: ./out/bin/rename-pvc.exe --help 60 | test-kind: 61 | strategy: 62 | matrix: 63 | go-version: [1.24.x] 64 | runs-on: ubuntu-latest 65 | 66 | steps: 67 | - name: Install Go 68 | uses: actions/setup-go@v5 69 | with: 70 | go-version: ${{ matrix.go-version }} 71 | 72 | - name: Create k8s Kind Cluster 73 | uses: helm/kind-action@v1.12.0 74 | with: 75 | cluster_name: kind 76 | 77 | - name: Checkout code 78 | uses: actions/checkout@v4 79 | 80 | # cache go modules 81 | - uses: actions/cache@v4 82 | with: 83 | # In order: 84 | # * Module download cache 85 | # * Build cache (Linux) 86 | # * Build cache (Mac) 87 | # * Build cache (Windows) 88 | path: | 89 | ~/go/pkg/mod 90 | ~/.cache/go-build 91 | ~/Library/Caches/go-build 92 | %LocalAppData%\go-build 93 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 94 | restore-keys: | 95 | ${{ runner.os }}-go- 96 | 97 | - name: Build package 98 | run: make build 99 | 100 | - name: Create PVC and deployment 101 | run: kubectl apply -f .github/kind-test/ 102 | 103 | - name: Wait until pod is ready 104 | run: kubectl wait --for=condition=ready pod/test 105 | 106 | - name: Delete pod 107 | run: kubectl delete --wait -f .github/kind-test/pod.yaml 108 | 109 | - name: Execute rename 110 | run: ./out/bin/rename-pvc -y pvc pvc-new 111 | 112 | - name: Check rename 113 | run: kubectl get pvc pvc-new 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ########### 2 | # Project # 3 | ########### 4 | 5 | # make output 6 | bin/ 7 | out/ 8 | 9 | ########## 10 | # Golang # 11 | ########## 12 | 13 | # Binaries for programs and plugins 14 | *.exe 15 | *.exe~ 16 | *.dll 17 | *.so 18 | *.dylib 19 | 20 | # Test binary, built with `go test -c` 21 | *.test 22 | 23 | # Output of the go coverage tool, specifically when used with LiteIDE 24 | *.out 25 | 26 | # Dependency directories (remove the comment below to include it) 27 | # vendor/ 28 | 29 | ########## 30 | # Linux # 31 | ########## 32 | 33 | *~ 34 | 35 | # temporary files which can be created if a process still has a handle open of a deleted file 36 | .fuse_hidden* 37 | 38 | # KDE directory preferences 39 | .directory 40 | 41 | # Linux trash folder which might appear on any partition or disk 42 | .Trash-* 43 | 44 | # .nfs files are created when an open file is removed but is still being accessed 45 | .nfs* 46 | 47 | ########### 48 | # Windows # 49 | ########### 50 | 51 | # Windows thumbnail cache files 52 | Thumbs.db 53 | Thumbs.db:encryptable 54 | ehthumbs.db 55 | ehthumbs_vista.db 56 | 57 | # Dump file 58 | *.stackdump 59 | 60 | # Folder config file 61 | [Dd]esktop.ini 62 | 63 | # Recycle Bin used on file shares 64 | $RECYCLE.BIN/ 65 | 66 | # Windows Installer files 67 | *.cab 68 | *.msi 69 | *.msix 70 | *.msm 71 | *.msp 72 | 73 | # Windows shortcuts 74 | *.lnk 75 | 76 | ######### 77 | # macOS # 78 | ######### 79 | 80 | # General 81 | .DS_Store 82 | .AppleDouble 83 | .LSOverride 84 | 85 | # Icon must end with two \r 86 | Icon 87 | 88 | # Thumbnails 89 | ._* 90 | 91 | # Files that might appear in the root of a volume 92 | .DocumentRevisions-V100 93 | .fseventsd 94 | .Spotlight-V100 95 | .TemporaryItems 96 | .Trashes 97 | .VolumeIcon.icns 98 | .com.apple.timemachine.donotpresent 99 | 100 | # Directories potentially created on remote AFP share 101 | .AppleDB 102 | .AppleDesktop 103 | Network Trash Folder 104 | Temporary Items 105 | .apdisk 106 | 107 | ########## 108 | # VSCODE # 109 | ########## 110 | 111 | .vscode/* 112 | !.vscode/tasks.json 113 | !.vscode/launch.json 114 | !.vscode/extensions.json 115 | *.code-workspace 116 | 117 | # Local History for Visual Studio Code 118 | .history/ 119 | 120 | ############# 121 | # JetBrains # 122 | ############# 123 | 124 | .idea 125 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 126 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 127 | 128 | # User-specific stuff 129 | .idea/**/workspace.xml 130 | .idea/**/tasks.xml 131 | .idea/**/usage.statistics.xml 132 | .idea/**/dictionaries 133 | .idea/**/shelf 134 | 135 | # AWS User-specific 136 | .idea/**/aws.xml 137 | 138 | # Generated files 139 | .idea/**/contentModel.xml 140 | 141 | # Sensitive or high-churn files 142 | .idea/**/dataSources/ 143 | .idea/**/dataSources.ids 144 | .idea/**/dataSources.local.xml 145 | .idea/**/sqlDataSources.xml 146 | .idea/**/dynamic.xml 147 | .idea/**/uiDesigner.xml 148 | .idea/**/dbnavigator.xml 149 | 150 | # Mongo Explorer plugin 151 | .idea/**/mongoSettings.xml 152 | 153 | # File-based project format 154 | *.iws 155 | 156 | # mpeltonen/sbt-idea plugin 157 | .idea_modules/ 158 | 159 | # JIRA plugin 160 | atlassian-ide-plugin.xml 161 | 162 | # Cursive Clojure plugin 163 | .idea/replstate.xml 164 | 165 | # Crashlytics plugin (for Android Studio and IntelliJ) 166 | com_crashlytics_export_strings.xml 167 | crashlytics.properties 168 | crashlytics-build.properties 169 | fabric.properties 170 | 171 | # Editor-based Rest Client 172 | .idea/httpRequests 173 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | run: 3 | issues-exit-code: 1 4 | tests: true 5 | linters: 6 | default: none 7 | enable: 8 | - bodyclose 9 | - copyloopvar 10 | - dogsled 11 | - dupl 12 | - err113 13 | - exhaustive 14 | - funlen 15 | - gochecknoglobals 16 | - gochecknoinits 17 | - goconst 18 | - gocritic 19 | - gocyclo 20 | - goprintffuncname 21 | - gosec 22 | - govet 23 | - ineffassign 24 | - lll 25 | - misspell 26 | - mnd 27 | - nakedret 28 | - noctx 29 | - nolintlint 30 | - prealloc 31 | - revive 32 | - staticcheck 33 | - unconvert 34 | - unparam 35 | - unused 36 | - whitespace 37 | settings: 38 | dupl: 39 | threshold: 100 40 | funlen: 41 | lines: 100 42 | statements: 50 43 | goconst: 44 | min-len: 2 45 | min-occurrences: 2 46 | gocritic: 47 | disabled-checks: 48 | - dupImport 49 | - ifElseChain 50 | - octalLiteral 51 | - whyNoLint 52 | - wrapperFunc 53 | enabled-tags: 54 | - diagnostic 55 | - experimental 56 | - opinionated 57 | - performance 58 | - style 59 | gocyclo: 60 | min-complexity: 15 61 | lll: 62 | line-length: 140 63 | misspell: 64 | locale: US 65 | nolintlint: 66 | require-explanation: true 67 | require-specific: true 68 | allow-unused: false 69 | exclusions: 70 | generated: lax 71 | presets: 72 | - comments 73 | - common-false-positives 74 | - legacy 75 | - std-error-handling 76 | rules: 77 | - linters: 78 | - dogsled 79 | - gochecknoglobals 80 | - gosec 81 | - mnd 82 | - noctx 83 | path: _test\.go 84 | - linters: 85 | - gocritic 86 | text: 'unnecessaryDefer:' 87 | paths: 88 | - third_party$ 89 | - builtin$ 90 | - examples$ 91 | formatters: 92 | enable: 93 | - gofmt 94 | - goimports 95 | exclusions: 96 | generated: lax 97 | paths: 98 | - third_party$ 99 | - builtin$ 100 | - examples$ 101 | -------------------------------------------------------------------------------- /.goreleaser.yml: -------------------------------------------------------------------------------- 1 | project_name: rename-pvc 2 | 3 | snapshot: 4 | name_template: '{{ .Tag }}-SNAPSHOT' 5 | 6 | before: 7 | hooks: 8 | - go mod tidy 9 | 10 | dist: bin 11 | 12 | builds: 13 | - id: rename-pvc 14 | env: 15 | - CGO_ENABLED=0 16 | main: ./cmd/rename-pvc 17 | ldflags: 18 | - -s 19 | - -w 20 | goos: 21 | - linux 22 | - windows 23 | - darwin 24 | ignore: 25 | - goos: windows 26 | goarch: arm64 27 | goarch: 28 | - amd64 29 | - arm64 30 | 31 | archives: 32 | - format: tar.gz 33 | name_template: '{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}' 34 | format_overrides: 35 | - goos: windows 36 | format: zip 37 | -------------------------------------------------------------------------------- /.krew.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 2 | kind: Plugin 3 | metadata: 4 | name: rename-pvc 5 | spec: 6 | version: "{{ .TagName }}" 7 | homepage: https://github.com/stackitcloud/rename-pvc 8 | shortDescription: "Rename a PersistentVolumeClaim (PVC)" 9 | description: | 10 | rename-pvc renames an existing PersistentVolumeClaim (PVC) by creating a new PVC 11 | with the same spec and rebinding the existing PersistentClaim (PV) to the newly created PVC. 12 | Afterwards the old PVC is automatically deleted. 13 | caveats: "Be sure to create a backup of your data in the PVC before using rename-pvc!" 14 | platforms: 15 | - bin: rename-pvc 16 | {{addURIAndSha "https://github.com/stackitcloud/rename-pvc/releases/download/{{ .TagName }}/rename-pvc-linux-amd64.tar.gz" .TagName }} 17 | selector: 18 | matchLabels: 19 | os: linux 20 | arch: amd64 21 | - bin: rename-pvc 22 | {{addURIAndSha "https://github.com/stackitcloud/rename-pvc/releases/download/{{ .TagName }}/rename-pvc-linux-arm64.tar.gz" .TagName }} 23 | selector: 24 | matchLabels: 25 | os: linux 26 | arch: arm64 27 | - bin: rename-pvc 28 | {{addURIAndSha "https://github.com/stackitcloud/rename-pvc/releases/download/{{ .TagName }}/rename-pvc-darwin-amd64.tar.gz" .TagName }} 29 | selector: 30 | matchLabels: 31 | os: darwin 32 | arch: amd64 33 | - bin: rename-pvc 34 | {{addURIAndSha "https://github.com/stackitcloud/rename-pvc/releases/download/{{ .TagName }}/rename-pvc-darwin-arm64.tar.gz" .TagName }} 35 | selector: 36 | matchLabels: 37 | os: darwin 38 | arch: arm64 39 | - bin: rename-pvc.exe 40 | {{addURIAndSha "https://github.com/stackitcloud/rename-pvc/releases/download/{{ .TagName }}/rename-pvc-windows-amd64.zip" .TagName }} 41 | selector: 42 | matchLabels: 43 | os: windows 44 | arch: amd64 45 | -------------------------------------------------------------------------------- /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @dergeberl @einfachnuralex 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to `rename-pvc` 2 | 3 | Welcome and thank you for making it this far and considering contributing to `rename-pvc`. 4 | We always appreciate any contributions by raising issues, improving the documentation, fixing bugs in the CLI or adding new features. 5 | 6 | Before opening a PR please read through this document. 7 | 8 | ## Process of making an addition 9 | 10 | > Please keep in mind to open an issue whenever you plan to make an addition to features to discuss it before implementing it. 11 | 12 | To contribute any code to this repository just do the following: 13 | 14 | 1. Make sure you have Go's latest version installed 15 | 2. Fork this repository 16 | 3. Run `make all` to make sure everything's setup correctly 17 | 4. Make your changes 18 | > Please follow the [seven rules of great Git commit messages](https://chris.beams.io/posts/git-commit/#seven-rules) 19 | > and make sure to keep your commits clean and atomic. 20 | > Your PR won't be squashed before merging so the commits should tell a story. 21 | > 22 | > Add documentation and tests for your addition if needed. 23 | 5. Run `make lint test` to ensure your code is ready to be merged 24 | > If any linting issues occur please fix them. 25 | > Using a nolint directive should only be used as a last resort. 26 | 6. Open a PR and make sure the CI pipelines succeeds. 27 | 7. Wait for one of the maintainers to review your code and react to the comments. 28 | 8. After approval merge the PR 29 | 9. Thank you for your contribution! :) 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash -e -o pipefail 2 | PWD = $(shell pwd) 3 | 4 | # constants 5 | GOLANGCI_VERSION = 2.1.5 6 | all: git-hooks tidy ## Initializes all tools 7 | 8 | out: 9 | @mkdir -p out 10 | 11 | git-hooks: 12 | @git config --local core.hooksPath .githooks/ 13 | 14 | download: ## Downloads the dependencies 15 | @go mod download 16 | 17 | tidy: ## Cleans up go.mod and go.sum 18 | @go mod tidy 19 | 20 | fmt: ## Formats all code with go fmt 21 | @go fmt ./... 22 | 23 | run: fmt ## Run the app 24 | @go run ./cmd/rename-pvc/main.go 25 | 26 | test-build: ## Tests whether the code compiles 27 | @go build -o /dev/null ./... 28 | 29 | build: out/bin ## Builds all binaries 30 | 31 | GO_BUILD = mkdir -pv "$(@)" && go build -ldflags="-w -s" -o "$(@)" ./... 32 | .PHONY: out/bin 33 | out/bin: 34 | $(GO_BUILD) 35 | 36 | GOLANGCI_LINT = bin/golangci-lint-$(GOLANGCI_VERSION) 37 | $(GOLANGCI_LINT): 38 | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | bash -s -- -b bin v$(GOLANGCI_VERSION) 39 | @mv bin/golangci-lint "$(@)" 40 | 41 | lint: fmt $(GOLANGCI_LINT) download ## Lints all code with golangci-lint 42 | @$(GOLANGCI_LINT) run 43 | 44 | lint-reports: out/lint.xml 45 | 46 | .PHONY: out/lint.xml 47 | out/lint.xml: $(GOLANGCI_LINT) out download 48 | @$(GOLANGCI_LINT) run ./... --output.checkstyle.path stdout | tee "$(@)" 49 | 50 | test: ## Runs all tests 51 | @go test $(ARGS) ./... 52 | 53 | coverage: out/report.json ## Displays coverage per func on cli 54 | go tool cover -func=out/cover.out 55 | 56 | html-coverage: out/report.json ## Displays the coverage results in the browser 57 | go tool cover -html=out/cover.out 58 | 59 | test-reports: out/report.json 60 | 61 | .PHONY: out/report.json 62 | out/report.json: out 63 | @go test -count 1 ./... -coverprofile=out/cover.out --json | tee "$(@)" 64 | 65 | clean: ## Cleans up everything 66 | @rm -rf bin out 67 | 68 | ci: lint-reports test-reports ## Executes lint and test and generates reports 69 | 70 | help: ## Shows the help 71 | @echo 'Usage: make ... ' 72 | @echo '' 73 | @echo 'Available targets are:' 74 | @echo '' 75 | @grep -E '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ 76 | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' 77 | @echo '' 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rename-pvc 2 | 3 | `rename-pvc` can rename PersistentVolumeClaims (PVCs) inside Kubernetes. 4 | 5 | :warning: Be sure to create a backup of your data in the PVC before using `rename-pvc`! 6 | 7 | ## Installation 8 | 9 | ### From krew plugin manager 10 | 11 | See [krew install guide.](https://krew.sigs.k8s.io/docs/user-guide/setup/install/) 12 | 13 | Update krew packages and install `rename-pvc`: 14 | 15 | ```shell 16 | kubectl krew update 17 | kubectl krew install rename-pvc 18 | ``` 19 | 20 | Now you can use `rename-pvc` with `kubectl rename-pvc`. 21 | 22 | ### From source 23 | 24 | If you have Go 1.16+, you can directly install by running: 25 | 26 | ```bash 27 | go install github.com/stackitcloud/rename-pvc/cmd/rename-pvc@latest 28 | ``` 29 | > Based on your go configuration the `rename-pvc` binary can be found in `$GOPATH/bin` or `$HOME/go/bin` in case `$GOPATH` is not set. 30 | > Make sure to add the respective directory to your `$PATH`. 31 | > [For more information see go docs for further information](https://golang.org/ref/mod#go-install). Run `go env` to view your current configuration. 32 | 33 | ### From the released binaries 34 | 35 | Download the desired version for your operating system and processor architecture from the [`rename-pvc` releases page](https://github.com/stackitcloud/rename-pvc/releases). 36 | Make the file executable and place it in a directory available in your `$PATH`. 37 | 38 | ## Usage 39 | 40 | To rename a PVC from `pvc-name` to `new-pvc-name` run the command: 41 | 42 | ```shell 43 | rename-pvc pvc-name new-pvc-name 44 | ``` 45 | 46 | Example Output: 47 | 48 | ```shell 49 | Rename PVC from 'pvc-name' in namespace 'default' to 'new-pvc-name' in namespace 'default'? (yes or no) y 50 | New PVC with name 'new-pvc-name' created 51 | ClaimRef of PV 'pvc-2dc982d6-72a0-4e80-b1a6-126b108d2adf' is updated to new PVC 'new-pvc-name' 52 | New PVC 'new-pvc-name' is bound to PV 'pvc-2dc982d6-72a0-4e80-b1a6-126b108d2adf' 53 | Old PVC 'pvc-name' is deleted 54 | ``` 55 | 56 | With the flag `--target-namespace` it is possible to change the namespace of the newly created PVC. `rename-pvc -n test1 --target-namespace test2 pvc-name pvc-name` will create the new PVC in Namespace `test2`. 57 | 58 | To select the Namespace and Kubernetes cluster you can use the default `kubectl` flags and environment variables (like `--namespace`, `--kubeconfig` or the `KUBECONFIG` environment variable). 59 | For all options run `--help`. 60 | 61 | ```shell 62 | Flags: 63 | --as string Username to impersonate for the operation. User could be a regular user or a service account in a namespace. 64 | --as-group stringArray Group to impersonate for the operation, this flag can be repeated to specify multiple groups. 65 | --as-uid string UID to impersonate for the operation. 66 | --cache-dir string Default cache directory (default "/home/m/.kube/cache") 67 | --certificate-authority string Path to a cert file for the certificate authority 68 | --client-certificate string Path to a client certificate file for TLS 69 | --client-key string Path to a client key file for TLS 70 | --cluster string The name of the kubeconfig cluster to use 71 | --context string The name of the kubeconfig context to use 72 | -h, --help help for /tmp/go-build4237287669/b001/exe/main 73 | --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure 74 | --kubeconfig string Path to the kubeconfig file to use for CLI requests. 75 | -n, --namespace string If present, the namespace scope for this CLI request 76 | --request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (default "0") 77 | -s, --server string The address and port of the Kubernetes API server 78 | -N, --target-namespace string Defines in which namespace the new PVC should be created. By default the source PVC's namespace is used. 79 | --tls-server-name string Server name to use for server certificate validation. If it is not provided, the hostname used to contact the server is used 80 | --token string Bearer token for authentication to the API server 81 | --user string The name of the kubeconfig user to use 82 | -y, --yes Skips confirmation if flag is set 83 | ``` 84 | 85 | ## How does it work? 86 | 87 | `rename-pvc` runs the following steps to rename an PVC in your Kubernetes cluster: 88 | 89 | 1. Creates the new PVC with the `.spec.volumeName` set to the existing PV 90 | - This new PVC is now in status `Lost` 91 | 2. Updates the `spec.claimRef` in the `PersistentVolume` to the new PVC 92 | 3. Waits until the new PVC's status is updated to `Bound` 93 | 4. Deletes the old PVC 94 | 95 | ## Maintainers 96 | 97 | | Name | Email | 98 | |:-----------------------------------------------------|:--------------------------------| 99 | | [@dergeberl](https://github.com/dergeberl) | maximilian.geberl@stackit.de | 100 | | [@einfachnuralex](https://github.com/einfachnuralex) | alexander.predeschly@stackit.de | 101 | 102 | ## Contribution 103 | 104 | If you want to contribute to `rename-pvc` please have a look at our [contribution guidelines](CONTRIBUTING.md). 105 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Reporting Security Issues 2 | 3 | **Please do not report security vulnerabilities through public GitHub issues.** 4 | 5 | We at STACKIT take security seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions. 6 | 7 | To report a security issue, please send an email to [stackit-security@stackit.de](mailto:stackit-security@stackit.de). 8 | 9 | Our team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. 10 | -------------------------------------------------------------------------------- /cmd/rename-pvc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "os" 6 | "os/signal" 7 | "syscall" 8 | 9 | rename "github.com/stackitcloud/rename-pvc/pkg/renamepvc" 10 | "k8s.io/cli-runtime/pkg/genericclioptions" 11 | ) 12 | 13 | func main() { 14 | if err := run(); err != nil { 15 | os.Exit(1) 16 | } 17 | } 18 | 19 | func run() error { 20 | ctx, ctxCancel := signal.NotifyContext(context.Background(), syscall.SIGTERM, syscall.SIGINT) 21 | defer ctxCancel() 22 | 23 | root := rename.NewCmdRenamePVC(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}) 24 | return root.ExecuteContext(ctx) 25 | } 26 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/stackitcloud/rename-pvc 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/spf13/cobra v1.9.1 7 | k8s.io/api v0.33.1 8 | k8s.io/apimachinery v0.33.1 9 | k8s.io/cli-runtime v0.33.1 10 | k8s.io/client-go v0.33.1 11 | ) 12 | 13 | require ( 14 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 15 | github.com/blang/semver/v4 v4.0.0 // indirect 16 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 17 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 18 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 19 | github.com/go-errors/errors v1.4.2 // indirect 20 | github.com/go-logr/logr v1.4.2 // indirect 21 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 22 | github.com/go-openapi/jsonreference v0.20.2 // indirect 23 | github.com/go-openapi/swag v0.23.0 // indirect 24 | github.com/gogo/protobuf v1.3.2 // indirect 25 | github.com/google/btree v1.1.3 // indirect 26 | github.com/google/gnostic-models v0.6.9 // indirect 27 | github.com/google/go-cmp v0.7.0 // indirect 28 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 29 | github.com/google/uuid v1.6.0 // indirect 30 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 31 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 32 | github.com/josharian/intern v1.0.0 // indirect 33 | github.com/json-iterator/go v1.1.12 // indirect 34 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 35 | github.com/mailru/easyjson v0.7.7 // indirect 36 | github.com/moby/term v0.5.0 // indirect 37 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 38 | github.com/modern-go/reflect2 v1.0.2 // indirect 39 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 40 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 41 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 42 | github.com/pkg/errors v0.9.1 // indirect 43 | github.com/spf13/pflag v1.0.6 // indirect 44 | github.com/x448/float16 v0.8.4 // indirect 45 | github.com/xlab/treeprint v1.2.0 // indirect 46 | golang.org/x/net v0.38.0 // indirect 47 | golang.org/x/oauth2 v0.27.0 // indirect 48 | golang.org/x/sync v0.12.0 // indirect 49 | golang.org/x/sys v0.31.0 // indirect 50 | golang.org/x/term v0.30.0 // indirect 51 | golang.org/x/text v0.23.0 // indirect 52 | golang.org/x/time v0.9.0 // indirect 53 | google.golang.org/protobuf v1.36.5 // indirect 54 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 55 | gopkg.in/inf.v0 v0.9.1 // indirect 56 | gopkg.in/yaml.v3 v3.0.1 // indirect 57 | k8s.io/klog/v2 v2.130.1 // indirect 58 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 59 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect 60 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect 61 | sigs.k8s.io/kustomize/api v0.19.0 // indirect 62 | sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect 63 | sigs.k8s.io/randfill v1.0.0 // indirect 64 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect 65 | sigs.k8s.io/yaml v1.4.0 // indirect 66 | ) 67 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= 2 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 3 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 4 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 6 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 7 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 8 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 14 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 15 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 16 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 17 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 18 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 19 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 20 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 21 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 22 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 23 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 24 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 25 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 26 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 27 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 28 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 29 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 30 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 31 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 32 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 33 | github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 34 | github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 35 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 36 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 37 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 38 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 39 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 40 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 41 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 42 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 43 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 44 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 45 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 46 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 47 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 48 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 49 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 50 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 51 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 52 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 53 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 54 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 55 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 56 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 57 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 58 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 59 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 60 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 61 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 62 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 63 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 64 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 65 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 66 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 67 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 68 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 69 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 70 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 72 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 73 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 74 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 75 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 76 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 77 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 78 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 79 | github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= 80 | github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 81 | github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 82 | github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 83 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 84 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 85 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 86 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 87 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 88 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 89 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 90 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 91 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 92 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 93 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 94 | github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= 95 | github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= 96 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 97 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 98 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 99 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 100 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 101 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 102 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 103 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 104 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 105 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 106 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 107 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 108 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 109 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 110 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 111 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 112 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 113 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 114 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 115 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 116 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 117 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 118 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 119 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 120 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 121 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 122 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 123 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 124 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 125 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 126 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 127 | golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= 128 | golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= 129 | golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= 130 | golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= 131 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 132 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 133 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 134 | golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= 135 | golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 136 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 137 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 138 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 139 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 140 | golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= 141 | golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 142 | golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= 143 | golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= 144 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 145 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 146 | golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= 147 | golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= 148 | golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= 149 | golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 150 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 151 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 152 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 153 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 154 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 155 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 156 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 157 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 158 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 159 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 160 | google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= 161 | google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 162 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 163 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 164 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 165 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 166 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 167 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 168 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 169 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 170 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 171 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 172 | k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= 173 | k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= 174 | k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= 175 | k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= 176 | k8s.io/cli-runtime v0.33.1 h1:TvpjEtF71ViFmPeYMj1baZMJR4iWUEplklsUQ7D3quA= 177 | k8s.io/cli-runtime v0.33.1/go.mod h1:9dz5Q4Uh8io4OWCLiEf/217DXwqNgiTS/IOuza99VZE= 178 | k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= 179 | k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= 180 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 181 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 182 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= 183 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= 184 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= 185 | k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 186 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= 187 | sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= 188 | sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= 189 | sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= 190 | sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= 191 | sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= 192 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 193 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 194 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 195 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc= 196 | sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 197 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 198 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 199 | -------------------------------------------------------------------------------- /pkg/renamepvc/renamepvc.go: -------------------------------------------------------------------------------- 1 | package renamepvc 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "strings" 11 | "time" 12 | 13 | "github.com/spf13/cobra" 14 | corev1 "k8s.io/api/core/v1" 15 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 16 | "k8s.io/cli-runtime/pkg/genericclioptions" 17 | "k8s.io/client-go/kubernetes" 18 | 19 | // for auth in kubernetes cluster 20 | _ "k8s.io/client-go/plugin/pkg/client/auth" 21 | ) 22 | 23 | var ( 24 | ErrNotBound = errors.New("new PVC did not get bound") 25 | ErrConfirmationNotSuccessful = errors.New("confirmation was not successful please type in yes to continue") 26 | ErrConfirmationUnknown = errors.New("please type yes or no") 27 | ErrVolumeMounted = errors.New("volume currently mounted") 28 | ) 29 | 30 | type renamePVCOptions struct { 31 | streams genericclioptions.IOStreams 32 | configFlags *genericclioptions.ConfigFlags 33 | k8sClient kubernetes.Interface 34 | 35 | confirm bool 36 | oldName string 37 | newName string 38 | sourceNamespace string 39 | targetNamespace string 40 | } 41 | 42 | // NewCmdRenamePVC returns the cobra command for the pvc rename 43 | func NewCmdRenamePVC(streams genericclioptions.IOStreams) *cobra.Command { 44 | o := renamePVCOptions{ 45 | streams: streams, 46 | configFlags: genericclioptions.NewConfigFlags(false), 47 | } 48 | 49 | command := os.Args[0] 50 | if strings.HasPrefix(filepath.Base(os.Args[0]), "kubectl-") { 51 | command = "kubectl rename-pvc" 52 | } 53 | 54 | cmd := &cobra.Command{ 55 | Use: fmt.Sprintf("%v [pvc name] [new pvc name]", command), 56 | Short: "Rename a PersistentVolumeClaim (PVC)", 57 | Long: `rename-pvc renames an existing PersistentVolumeClaim (PVC) by creating a new PVC 58 | with the same spec and rebinding the existing PersistentClaim (PV) to the newly created PVC. 59 | Afterwards the old PVC is automatically deleted.`, 60 | Example: fmt.Sprintf("%v oldPvcName newPvcName", command), 61 | Args: cobra.ExactArgs(2), //nolint: mnd // needs always 2 inputs 62 | SilenceUsage: true, 63 | RunE: func(c *cobra.Command, args []string) error { 64 | var err error 65 | o.sourceNamespace, _, err = o.configFlags.ToRawKubeConfigLoader().Namespace() 66 | if err != nil { 67 | return err 68 | } 69 | 70 | if o.targetNamespace == "" { 71 | o.targetNamespace = o.sourceNamespace 72 | } 73 | 74 | o.oldName = args[0] 75 | o.newName = args[1] 76 | 77 | o.k8sClient, err = getK8sClient(o.configFlags) 78 | if err != nil { 79 | return err 80 | } 81 | return o.run(c.Context()) 82 | }, 83 | } 84 | cmd.Flags().BoolVarP(&o.confirm, "yes", "y", false, "Skips confirmation if flag is set") 85 | cmd.Flags().StringVarP(&o.targetNamespace, "target-namespace", "N", "", 86 | "Defines in which namespace the new PVC should be created. By default the source PVC's namespace is used.") 87 | o.configFlags.AddFlags(cmd.Flags()) 88 | return cmd 89 | } 90 | 91 | // run manages the workflow for renaming a pvc from oldName to newName 92 | func (o *renamePVCOptions) run(ctx context.Context) error { 93 | if err := o.confirmCheck(); err != nil { 94 | return err 95 | } 96 | 97 | oldPvc, err := o.k8sClient.CoreV1().PersistentVolumeClaims(o.sourceNamespace).Get(ctx, o.oldName, metav1.GetOptions{}) 98 | if err != nil { 99 | return err 100 | } 101 | 102 | err = o.checkIfMounted(ctx, oldPvc) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | return o.rename(ctx, oldPvc) 108 | } 109 | 110 | func (o *renamePVCOptions) confirmCheck() error { 111 | if o.confirm { 112 | return nil 113 | } 114 | _, err := fmt.Fprintf(o.streams.Out, 115 | "Rename PVC from '%s' in namespace '%s' to '%s' in namespace '%v'? (yes or no) ", 116 | o.oldName, o.sourceNamespace, o.newName, o.targetNamespace) 117 | if err != nil { 118 | return err 119 | } 120 | input, err := bufio.NewReader(o.streams.In).ReadString('\n') 121 | if err != nil { 122 | return err 123 | } 124 | switch strings.TrimSuffix(input, "\n") { 125 | case "y", "yes": 126 | return nil 127 | case "n", "no": 128 | return ErrConfirmationNotSuccessful 129 | default: 130 | return ErrConfirmationUnknown 131 | } 132 | } 133 | 134 | func getK8sClient(configFlags *genericclioptions.ConfigFlags) (*kubernetes.Clientset, error) { 135 | config, err := configFlags.ToRESTConfig() 136 | if err != nil { 137 | return nil, err 138 | } 139 | return kubernetes.NewForConfig(config) 140 | } 141 | 142 | // checkIfMounted returns an error if the volume is mounted in a pod 143 | func (o *renamePVCOptions) checkIfMounted(ctx context.Context, pvc *corev1.PersistentVolumeClaim) error { 144 | podList, err := o.k8sClient.CoreV1().Pods(pvc.Namespace).List(ctx, metav1.ListOptions{}) 145 | if err != nil { 146 | return err 147 | } 148 | for pod := range podList.Items { 149 | for vol := range podList.Items[pod].Spec.Volumes { 150 | volume := &podList.Items[pod].Spec.Volumes[vol] 151 | if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc.Name { 152 | return fmt.Errorf("%w in pod \"%s\"", ErrVolumeMounted, podList.Items[pod].Name) 153 | } 154 | } 155 | } 156 | return nil 157 | } 158 | 159 | // waitUntilPvcIsBound waits util the pvc is in state Bound, with a timeout of 60 sec 160 | func (o *renamePVCOptions) waitUntilPvcIsBound(ctx context.Context, pvc *corev1.PersistentVolumeClaim) error { 161 | for i := 0; i <= 60; i++ { 162 | checkPVC, err := o.k8sClient.CoreV1().PersistentVolumeClaims(pvc.Namespace).Get(ctx, pvc.GetName(), metav1.GetOptions{}) 163 | if err != nil { 164 | return err 165 | } 166 | 167 | if checkPVC.Status.Phase == corev1.ClaimBound { 168 | return nil 169 | } 170 | 171 | select { 172 | case <-ctx.Done(): 173 | return ctx.Err() 174 | case <-time.After(time.Second): 175 | } 176 | } 177 | 178 | return ErrNotBound 179 | } 180 | 181 | // rename the oldPvc to newName 182 | func (o *renamePVCOptions) rename( 183 | ctx context.Context, 184 | oldPvc *corev1.PersistentVolumeClaim, 185 | ) error { 186 | // get new pvc with old PVC inputs 187 | newPvc := oldPvc.DeepCopy() 188 | newPvc.Status = corev1.PersistentVolumeClaimStatus{} 189 | newPvc.Name = o.newName 190 | newPvc.UID = "" 191 | newPvc.CreationTimestamp = metav1.Now() 192 | newPvc.SelfLink = "" //nolint: staticcheck // to keep compatibility with older versions 193 | newPvc.ResourceVersion = "" 194 | newPvc.Namespace = o.targetNamespace 195 | 196 | pv, err := o.k8sClient.CoreV1().PersistentVolumes().Get(ctx, oldPvc.Spec.VolumeName, metav1.GetOptions{}) 197 | if err != nil { 198 | return err 199 | } 200 | 201 | newPvc, err = o.k8sClient.CoreV1().PersistentVolumeClaims(o.targetNamespace).Create(ctx, newPvc, metav1.CreateOptions{}) 202 | if err != nil { 203 | return err 204 | } 205 | _, _ = fmt.Fprintf(o.streams.Out, "New PVC with name '%s' created\n", newPvc.Name) 206 | 207 | pv.Spec.ClaimRef = &corev1.ObjectReference{ 208 | Kind: newPvc.Kind, 209 | Namespace: newPvc.Namespace, 210 | Name: newPvc.Name, 211 | UID: newPvc.UID, 212 | APIVersion: newPvc.APIVersion, 213 | ResourceVersion: newPvc.ResourceVersion, 214 | } 215 | pv, err = o.k8sClient.CoreV1().PersistentVolumes().Update(ctx, pv, metav1.UpdateOptions{}) 216 | if err != nil { 217 | return err 218 | } 219 | _, _ = fmt.Fprintf(o.streams.Out, "ClaimRef of PV '%s' is updated to new PVC '%s'\n", pv.Name, newPvc.Name) 220 | 221 | err = o.waitUntilPvcIsBound(ctx, newPvc) 222 | if err != nil { 223 | return err 224 | } 225 | _, _ = fmt.Fprintf(o.streams.Out, "New PVC '%s' is bound to PV '%s'\n", newPvc.Name, pv.Name) 226 | 227 | err = o.k8sClient.CoreV1().PersistentVolumeClaims(o.sourceNamespace).Delete(ctx, oldPvc.Name, metav1.DeleteOptions{}) 228 | if err != nil { 229 | return err 230 | } 231 | _, _ = fmt.Fprintf(o.streams.Out, "Old PVC '%s' is deleted\n", oldPvc.Name) 232 | 233 | return nil 234 | } 235 | -------------------------------------------------------------------------------- /pkg/renamepvc/renamepvc_test.go: -------------------------------------------------------------------------------- 1 | package renamepvc 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "testing" 7 | 8 | corev1 "k8s.io/api/core/v1" 9 | k8sErrors "k8s.io/apimachinery/pkg/api/errors" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/apimachinery/pkg/runtime" 12 | "k8s.io/cli-runtime/pkg/genericclioptions" 13 | "k8s.io/client-go/kubernetes/fake" 14 | k8sTesting "k8s.io/client-go/testing" 15 | ) 16 | 17 | func TestPVCRename(t *testing.T) { 18 | ctx := context.Background() 19 | 20 | t.Run("Test rename-pvc in same namespace - successfully", func(t *testing.T) { 21 | o, err := initTestSetup(ctx, 22 | "test1-old", "test1", 23 | "test1-new", "test1") 24 | if err != nil { 25 | t.Errorf("testsetup failed - got error %q", err) 26 | } 27 | 28 | err = o.run(context.Background()) 29 | if err != nil { 30 | t.Errorf("rename failed - got error %q", err) 31 | } 32 | 33 | err = checkRename(ctx, &o) 34 | if err != nil { 35 | t.Errorf("rename check failed - got error %q", err) 36 | } 37 | }) 38 | 39 | t.Run("Test rename-pvc in different namespace - successfully", func(t *testing.T) { 40 | o, err := initTestSetup(ctx, 41 | "test2-old", "test2-old", 42 | "test2-new", "test2") 43 | if err != nil { 44 | t.Errorf("testsetup failed - got error %q", err) 45 | } 46 | 47 | err = o.run(context.Background()) 48 | if err != nil { 49 | t.Errorf("rename failed - got error %q", err) 50 | } 51 | 52 | err = checkRename(ctx, &o) 53 | if err != nil { 54 | t.Errorf("rename check failed -got error %q", err) 55 | } 56 | }) 57 | 58 | t.Run("Test rename-pvc with running pod - fail", func(t *testing.T) { 59 | pod := corev1.Pod{ 60 | ObjectMeta: metav1.ObjectMeta{Name: "pod", Namespace: "test3"}, 61 | Spec: corev1.PodSpec{ 62 | Volumes: []corev1.Volume{{ 63 | Name: "test", 64 | VolumeSource: corev1.VolumeSource{ 65 | PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 66 | ClaimName: "test3-old", ReadOnly: false, 67 | }, 68 | }, 69 | }}, 70 | }, 71 | } 72 | 73 | o, err := initTestSetup(ctx, 74 | "test3-old", "test3", 75 | "test3-new", "test3", 76 | &pod, 77 | ) 78 | if err != nil { 79 | t.Errorf("testsetup failed - got error %q", err) 80 | } 81 | 82 | err = o.run(ctx) 83 | if !errors.Is(err, ErrVolumeMounted) { 84 | t.Errorf("expect %q error - but got %q", ErrVolumeMounted, err) 85 | } 86 | }) 87 | 88 | t.Run("Test rename-pvc with already existing newPvc - fail", func(t *testing.T) { 89 | pvc := corev1.PersistentVolumeClaim{ 90 | ObjectMeta: metav1.ObjectMeta{Name: "test4-new", Namespace: "test4"}, 91 | Spec: corev1.PersistentVolumeClaimSpec{ 92 | VolumeName: "test", 93 | }, 94 | } 95 | 96 | o, err := initTestSetup(ctx, 97 | "test4-old", "test4", 98 | "test4-new", "test4", 99 | &pvc, 100 | ) 101 | if err != nil { 102 | t.Errorf("testsetup failed - got error %q", err) 103 | } 104 | 105 | ll, _ := o.k8sClient.CoreV1().PersistentVolumeClaims("default").List(ctx, metav1.ListOptions{}) 106 | _ = ll 107 | err = o.run(ctx) 108 | if !k8sErrors.IsAlreadyExists(err) { 109 | t.Errorf("expect already exists error - but got %q", err) 110 | } 111 | }) 112 | } 113 | 114 | func TestConfirmCheck(t *testing.T) { 115 | t.Run("Test confirmCheck with y", func(t *testing.T) { 116 | streams, in, _, _ := genericclioptions.NewTestIOStreams() 117 | o := renamePVCOptions{streams: streams} 118 | in.WriteString("y\n") 119 | if err := o.confirmCheck(); err != nil { 120 | t.Errorf("confirmCheck - got error %q", err) 121 | } 122 | }) 123 | t.Run("Test confirmCheck with skip flag", func(t *testing.T) { 124 | streams, _, _, _ := genericclioptions.NewTestIOStreams() 125 | o := renamePVCOptions{streams: streams, confirm: true} 126 | if err := o.confirmCheck(); err != nil { 127 | t.Errorf("confirmCheck - got error %q", err) 128 | } 129 | }) 130 | t.Run("Test confirmCheck with n", func(t *testing.T) { 131 | streams, in, _, _ := genericclioptions.NewTestIOStreams() 132 | o := renamePVCOptions{streams: streams} 133 | in.WriteString("n\n") 134 | if err := o.confirmCheck(); !errors.Is(err, ErrConfirmationNotSuccessful) { 135 | t.Errorf("expect %q - got error %q", ErrConfirmationNotSuccessful, err) 136 | } 137 | }) 138 | t.Run("Test confirmCheck with unknown", func(t *testing.T) { 139 | streams, in, _, _ := genericclioptions.NewTestIOStreams() 140 | o := renamePVCOptions{streams: streams} 141 | in.WriteString("unknown\n") 142 | if err := o.confirmCheck(); !errors.Is(err, ErrConfirmationUnknown) { 143 | t.Errorf("expect %q - got error %q", ErrConfirmationUnknown, err) 144 | } 145 | }) 146 | } 147 | 148 | // initTestSetup returns renamePVCOptions initialized with a kubernetes fake client and created oldPvc for test 149 | func initTestSetup( 150 | ctx context.Context, 151 | oldName, sourceNamespace string, 152 | newName, targetNamespace string, 153 | extraObjects ...runtime.Object, 154 | ) (renamePVCOptions, error) { 155 | // init client 156 | streams, _, _, _ := genericclioptions.NewTestIOStreams() 157 | client := fake.NewSimpleClientset(extraObjects...) 158 | // the fake client did not set volumes to bound 159 | // add reactor to set every volume to Phase bound 160 | client.PrependReactor( 161 | "create", 162 | "persistentvolumeclaims", 163 | func(action k8sTesting.Action) (bool, runtime.Object, error) { 164 | obj := action.(k8sTesting.CreateAction).GetObject() 165 | pvc, ok := obj.(*corev1.PersistentVolumeClaim) 166 | if !ok { 167 | return false, obj, nil 168 | } 169 | pvc.Status.Phase = corev1.ClaimBound 170 | return false, obj, nil 171 | }) 172 | 173 | o := renamePVCOptions{ 174 | streams: streams, 175 | k8sClient: client, 176 | confirm: true, 177 | oldName: oldName, 178 | newName: newName, 179 | sourceNamespace: sourceNamespace, 180 | targetNamespace: targetNamespace, 181 | } 182 | 183 | // create pvc 184 | pvName := sourceNamespace + "-" + oldName + "-pv" 185 | pvc, err := o.k8sClient.CoreV1().PersistentVolumeClaims(sourceNamespace).Create(ctx, &corev1.PersistentVolumeClaim{ 186 | ObjectMeta: metav1.ObjectMeta{ 187 | Name: oldName, 188 | Namespace: sourceNamespace, 189 | }, 190 | Spec: corev1.PersistentVolumeClaimSpec{ 191 | VolumeName: pvName, 192 | }, 193 | }, metav1.CreateOptions{}) 194 | if err != nil { 195 | return renamePVCOptions{}, err 196 | } 197 | 198 | // create pv 199 | _, err = o.k8sClient.CoreV1().PersistentVolumes().Create(ctx, &corev1.PersistentVolume{ 200 | ObjectMeta: metav1.ObjectMeta{ 201 | Name: pvName, 202 | }, 203 | Spec: corev1.PersistentVolumeSpec{ 204 | ClaimRef: &corev1.ObjectReference{ 205 | Kind: pvc.Kind, 206 | Namespace: pvc.Namespace, 207 | Name: pvc.Name, 208 | UID: pvc.UID, 209 | APIVersion: pvc.APIVersion, 210 | ResourceVersion: pvc.ResourceVersion, 211 | }, 212 | }, 213 | }, metav1.CreateOptions{}) 214 | if err != nil { 215 | return renamePVCOptions{}, err 216 | } 217 | return o, nil 218 | } 219 | 220 | func checkRename(ctx context.Context, o *renamePVCOptions) error { 221 | newPVC, err := o.k8sClient.CoreV1().PersistentVolumeClaims(o.targetNamespace).Get(ctx, o.newName, metav1.GetOptions{}) 222 | if err != nil { 223 | return err 224 | } 225 | 226 | pv, err := o.k8sClient.CoreV1().PersistentVolumes().Get(ctx, newPVC.Spec.VolumeName, metav1.GetOptions{}) 227 | if err != nil { 228 | return err 229 | } 230 | 231 | if pv.Spec.ClaimRef.Name != newPVC.Name && 232 | pv.Spec.ClaimRef.Namespace != newPVC.Namespace { 233 | return errors.New("pv claimRef wrong") //nolint: err113 // in test okay 234 | } 235 | 236 | _, err = o.k8sClient.CoreV1().PersistentVolumeClaims(o.targetNamespace).Get(ctx, o.newName, metav1.GetOptions{}) 237 | if !k8sErrors.IsNotFound(err) { 238 | return err 239 | } 240 | 241 | return nil 242 | } 243 | --------------------------------------------------------------------------------