├── .gitignore ├── test ├── rwx-unbound-pvc.yaml ├── unbound-pvc.yaml ├── failed-pvc.yaml ├── README.md ├── rwx-bound-pvc.yaml └── bound-pvc.yaml ├── .github └── workflows │ ├── main.yaml │ ├── release.yaml │ └── build.yaml ├── README.md ├── src ├── monitor_size.go ├── monitor_size_windows.go ├── go.mod ├── util.go ├── main.go └── go.sum ├── LICENSE └── .krew.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | kubectl-browse-pvc -------------------------------------------------------------------------------- /test/rwx-unbound-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: rwx-unbound-pvc 5 | spec: 6 | accessModes: 7 | - ReadWriteMany 8 | resources: 9 | requests: 10 | storage: 2Gi 11 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Build & Test 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | uses: ./.github/workflows/build.yaml 11 | with: 12 | version: ${GITHUB_SHA::7} 13 | 14 | 15 | -------------------------------------------------------------------------------- /test/unbound-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: unbound-pvc 5 | spec: 6 | storageClassName: mayastor 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: 2Gi 12 | -------------------------------------------------------------------------------- /test/failed-pvc.yaml: -------------------------------------------------------------------------------- 1 | # Need to configure kind rwx first 2 | apiVersion: v1 3 | kind: PersistentVolumeClaim 4 | metadata: 5 | name: rwx-test 6 | spec: 7 | accessModes: 8 | - ReadWriteMany 9 | resources: 10 | requests: 11 | storage: 2Gi 12 | storageClassName: noclass 13 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # Testing Resources 2 | 3 | Kubernetes manifests for testing access 4 | 5 | - ```bound-pvc``` is a pvc bound to a pod, browse-pvc should fail to connect and return an erro 6 | - ``failed-pvc`` a pvc that is not bound to a pod, but is in some kind of failure/pending state where it cannot be mounted. 7 | - ``rwx-bound-pvc`` a RWX pvc that is attached to another pod 8 | - ``rwx-unbound-pvc`` A RW pvc that is not attached to another pod 9 | - ``unbound-pvc`` a pvc that is not connected to any other pod -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kubectl-browse-pvc 2 | Kubectl plugin to browse a Kubernetes PVC from the command line 3 | 4 | I constantly found myself spinning up dummy pods to exec into them so I could browse a PVC, this takes a few steps out of creating dummy pods to check out the contents of a PVC. 5 | 6 | Install via krew 7 | ``` 8 | kubectl krew install browse-pvc 9 | ``` 10 | 11 | Usage 12 | 13 | ``` 14 | kubectl browse-pvc 15 | ``` 16 | On a PVC. The tool spins up a pod that mounts the PVC and then execs into it allowing you to modify the contents of the PVC. The Job finishes and cleans up the pod when you disconnect. 17 | 18 | 19 | Commands can be described to run a command instead of popping a shell 20 | ``` 21 | kubectl browse-pvc -- 22 | ``` 23 | 24 | 25 | A User ID can be described to set the user the container runs as 26 | ``` 27 | kubectl browse-pvc -u 1000 28 | ``` 29 | -------------------------------------------------------------------------------- /test/rwx-bound-pvc.yaml: -------------------------------------------------------------------------------- 1 | # Need to configure kind rwx first 2 | # apiVersion: v1 3 | # kind: PersistentVolumeClaim 4 | # metadata: 5 | # name: rwx-bound-pvc 6 | # spec: 7 | # accessModes: 8 | # - ReadWriteMany 9 | # resources: 10 | # requests: 11 | # storage: 2Gi 12 | # --- 13 | # apiVersion: apps/v1 14 | # kind: Deployment 15 | # metadata: 16 | # name: nginx-deployment 17 | # labels: 18 | # app: nginx 19 | # spec: 20 | # replicas: 1 21 | # selector: 22 | # matchLabels: 23 | # app: nginx 24 | # template: 25 | # metadata: 26 | # labels: 27 | # app: nginx 28 | # spec: 29 | # containers: 30 | # - name: nginx 31 | # image: nginx:latest 32 | # volumeMounts: 33 | # - name: rwx-volume 34 | # mountPath: /usr/share/nginx/html 35 | # volumes: 36 | # - name: rwx-volume 37 | # persistentVolumeClaim: 38 | # claimName: rwx-test 39 | -------------------------------------------------------------------------------- /src/monitor_size.go: -------------------------------------------------------------------------------- 1 | //go:build linux || darwin 2 | // +build linux darwin 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | "os/signal" 9 | "syscall" 10 | 11 | "golang.org/x/term" 12 | "k8s.io/client-go/tools/remotecommand" 13 | ) 14 | 15 | type sizeQueue struct { 16 | resizeChan chan remotecommand.TerminalSize 17 | stopResizing chan struct{} 18 | } 19 | 20 | func (s *sizeQueue) Next() *remotecommand.TerminalSize { 21 | size, ok := <-s.resizeChan 22 | if !ok { 23 | return nil 24 | } 25 | return &size 26 | } 27 | 28 | func (s *sizeQueue) Stop() { 29 | close(s.stopResizing) 30 | } 31 | 32 | func (s *sizeQueue) MonitorSize() { 33 | sigCh := make(chan os.Signal, 1) 34 | signal.Notify(sigCh, syscall.SIGWINCH) 35 | 36 | for { 37 | select { 38 | case <-sigCh: 39 | width, height, err := term.GetSize(int(os.Stdout.Fd())) 40 | if err == nil { 41 | select { 42 | case s.resizeChan <- remotecommand.TerminalSize{Width: uint16(width), Height: uint16(height)}: 43 | default: 44 | } 45 | } 46 | case <-s.stopResizing: 47 | close(s.resizeChan) 48 | return 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/monitor_size_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | // +build windows 3 | 4 | package main 5 | 6 | import ( 7 | "os" 8 | 9 | "golang.org/x/term" 10 | "k8s.io/client-go/tools/remotecommand" 11 | ) 12 | 13 | type sizeQueue struct { 14 | resizeChan chan remotecommand.TerminalSize 15 | stopResizing chan struct{} 16 | } 17 | 18 | func (s *sizeQueue) Next() *remotecommand.TerminalSize { 19 | size, ok := <-s.resizeChan 20 | if !ok { 21 | return nil 22 | } 23 | return &size 24 | } 25 | 26 | func (s *sizeQueue) Stop() { 27 | close(s.stopResizing) 28 | } 29 | 30 | func (s *sizeQueue) MonitorSize() { 31 | sigCh := make(chan os.Signal, 1) 32 | // Need to fix this to get it working on windows 33 | //signal.Notify(sigCh, syscall.SIGWINCH) 34 | 35 | for { 36 | select { 37 | case <-sigCh: 38 | width, height, err := term.GetSize(int(os.Stdout.Fd())) 39 | if err == nil { 40 | select { 41 | case s.resizeChan <- remotecommand.TerminalSize{Width: uint16(width), Height: uint16(height)}: 42 | default: 43 | } 44 | } 45 | case <-s.stopResizing: 46 | close(s.resizeChan) 47 | return 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Clay Buxton 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.krew.yaml: -------------------------------------------------------------------------------- 1 | # Krew Manifest 2 | apiVersion: krew.googlecontainertools.github.com/v1alpha2 3 | kind: Plugin 4 | metadata: 5 | name: browse-pvc 6 | spec: 7 | version: "{{ .TagName }}" 8 | homepage: https://github.com/clbx/kubectl-browse-pvc 9 | shortDescription: "Browse PVC contents from the command line." 10 | description: "Browse PVCs from the command line. Creates a job thats binds an unbound PVC and execs into it." 11 | platforms: 12 | # macOS ARM 13 | - selector: 14 | matchLabels: 15 | os: darwin 16 | arch: arm64 17 | {{addURIAndSha "https://github.com/clbx/kubectl-browse-pvc/releases/download/{{ .TagName }}/kubectl-browse-pvc-darwin-arm.zip" .TagName }} 18 | bin: kubectl-browse-pvc 19 | # macOS x86_64 20 | - selector: 21 | matchLabels: 22 | os: darwin 23 | arch: amd64 24 | {{addURIAndSha "https://github.com/clbx/kubectl-browse-pvc/releases/download/{{ .TagName }}/kubectl-browse-pvc-darwin-x86_64.zip" .TagName }} 25 | bin: kubectl-browse-pvc 26 | # linux x86_64 27 | - selector: 28 | matchLabels: 29 | os: linux 30 | arch: amd64 31 | {{addURIAndSha "https://github.com/clbx/kubectl-browse-pvc/releases/download/{{ .TagName }}/kubectl-browse-pvc-linux-x86_64.zip" .TagName }} 32 | bin: kubectl-browse-pvc 33 | # linux ARM 34 | - selector: 35 | matchLabels: 36 | os: linux 37 | arch: arm64 38 | {{addURIAndSha "https://github.com/clbx/kubectl-browse-pvc/releases/download/{{ .TagName }}/kubectl-browse-pvc-linux-arm.zip" .TagName }} 39 | bin: kubectl-browse-pvc 40 | -------------------------------------------------------------------------------- /test/bound-pvc.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: PersistentVolumeClaim 3 | metadata: 4 | name: bound-pvc 5 | spec: 6 | storageClassName: mayastor 7 | accessModes: 8 | - ReadWriteOnce 9 | resources: 10 | requests: 11 | storage: 2Gi 12 | --- 13 | apiVersion: apps/v1 14 | kind: Deployment 15 | metadata: 16 | name: pod-1 17 | labels: 18 | app: pod-1 19 | spec: 20 | replicas: 1 21 | selector: 22 | matchLabels: 23 | app: pod-1 24 | template: 25 | metadata: 26 | labels: 27 | app: pod-1 28 | spec: 29 | nodeName: titan 30 | containers: 31 | - name: busybox 32 | image: busybox:latest 33 | # sleep for 3600 seconds 34 | command: [ "sleep", "3600" ] 35 | volumeMounts: 36 | - name: volume 37 | mountPath: /mnt/ 38 | volumes: 39 | - name: volume 40 | persistentVolumeClaim: 41 | claimName: bound-pvc 42 | --- 43 | apiVersion: apps/v1 44 | kind: Deployment 45 | metadata: 46 | name: pod-2 47 | labels: 48 | app: pod-2 49 | spec: 50 | replicas: 1 51 | selector: 52 | matchLabels: 53 | app: pod-2 54 | template: 55 | metadata: 56 | labels: 57 | app: pod-2 58 | spec: 59 | nodeName: titan 60 | containers: 61 | - name: busybox 62 | image: busybox:latest 63 | # sleep for 3600 seconds 64 | command: [ "sleep", "3600" ] 65 | volumeMounts: 66 | - name: volume 67 | mountPath: /mnt/ 68 | volumes: 69 | - name: volume 70 | persistentVolumeClaim: 71 | claimName: bound-pvc 72 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | permissions: 3 | contents: write 4 | on: 5 | push: 6 | tags: 7 | - "v*.*.*" 8 | jobs: 9 | build: 10 | uses: ./.github/workflows/build.yaml 11 | with: 12 | version: ${{ github.ref_name }} 13 | create-release: 14 | runs-on: ubuntu-latest 15 | needs: build 16 | steps: 17 | - name: Download Artifacts 18 | uses: actions/download-artifact@v4 19 | 20 | - name: ls 21 | run: ls -R 22 | 23 | - name: Fix permissions linux x86_64 24 | run: chmod +x kubectl-browse-pvc-linux-x86_64/kubectl-browse-pvc 25 | 26 | - name: Fix permissions linux arm64 27 | run: chmod +x kubectl-browse-pvc-linux-arm/kubectl-browse-pvc 28 | 29 | - name: Fix permissions darwin arm64 30 | run: chmod +x kubectl-browse-pvc-darwin-arm/kubectl-browse-pvc 31 | 32 | - name: Fix permissions darwin x86_64 33 | run: chmod +x kubectl-browse-pvc-darwin-x86_64/kubectl-browse-pvc 34 | 35 | - name: Zip linux x86_64 36 | uses: montudor/action-zip@v1 37 | with: 38 | args: zip -j kubectl-browse-pvc-linux-x86_64.zip kubectl-browse-pvc-linux-x86_64/kubectl-browse-pvc kubectl-browse-pvc-linux-x86_64/LICENSE 39 | 40 | - name: Zip linux arm64 41 | uses: montudor/action-zip@v1 42 | with: 43 | args: zip -j kubectl-browse-pvc-linux-arm.zip kubectl-browse-pvc-linux-arm/kubectl-browse-pvc kubectl-browse-pvc-linux-arm/LICENSE 44 | 45 | - name: Zip darwin arm64 46 | uses: montudor/action-zip@v1 47 | with: 48 | args: zip -j kubectl-browse-pvc-darwin-arm.zip kubectl-browse-pvc-darwin-arm/kubectl-browse-pvc kubectl-browse-pvc-darwin-arm/LICENSE 49 | 50 | - name: Zip darwin x86_64 51 | uses: montudor/action-zip@v1 52 | with: 53 | args: zip -j kubectl-browse-pvc-darwin-x86_64.zip kubectl-browse-pvc-darwin-x86_64/kubectl-browse-pvc kubectl-browse-pvc-darwin-x86_64/LICENSE 54 | 55 | - name: Create Release 56 | id: create_release 57 | uses: ncipollo/release-action@v1 58 | with: 59 | artifacts: "kubectl-browse-pvc-darwin-arm.zip,kubectl-browse-pvc-darwin-x86_64.zip,kubectl-browse-pvc-linux-x86_64.zip,kubectl-browse-pvc-linux-arm.zip" 60 | artifactErrorsFailBuild: true 61 | 62 | - name: Check out Code 63 | uses: actions/checkout@v4.0.0 64 | 65 | - name: Update Krew 66 | uses: rajatjindal/krew-release-bot@v0.0.46 67 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build Job 2 | 3 | on: 4 | workflow_call: 5 | inputs: 6 | version: 7 | required: true 8 | type: string 9 | 10 | jobs: 11 | build-darwin-arm: 12 | runs-on: macos-latest 13 | steps: 14 | - name: Set up Go 15 | uses: actions/setup-go@v4.1.0 16 | with: 17 | go-version: "1.22.0" 18 | 19 | - name: Check out Code 20 | uses: actions/checkout@v4.0.0 21 | 22 | - name: Build 23 | run: | 24 | cd src 25 | go build -v -ldflags "-X main.Version=${{ inputs.version }}" -o kubectl-browse-pvc . 26 | mv kubectl-browse-pvc .. 27 | 28 | - name: Fix permissions 29 | run: chmod +x ./kubectl-browse-pvc 30 | 31 | - name: Upload Artifact 32 | uses: actions/upload-artifact@v4 33 | with: 34 | name: kubectl-browse-pvc-darwin-arm 35 | path: | 36 | ./kubectl-browse-pvc 37 | LICENSE 38 | 39 | build-darwin-x86_64: 40 | runs-on: macos-13 41 | steps: 42 | - name: Set up Go 43 | uses: actions/setup-go@v4.1.0 44 | with: 45 | go-version: "1.22.0" 46 | 47 | - name: Check out Code 48 | uses: actions/checkout@v4.0.0 49 | 50 | - name: Build 51 | run: | 52 | cd src 53 | go build -v -ldflags "-X main.Version=${{ inputs.version }}" -o kubectl-browse-pvc . 54 | mv kubectl-browse-pvc .. 55 | 56 | - name: Fix permissions 57 | run: chmod +x ./kubectl-browse-pvc 58 | 59 | - name: Upload Artifact 60 | uses: actions/upload-artifact@v4 61 | with: 62 | name: kubectl-browse-pvc-darwin-x86_64 63 | path: | 64 | ./kubectl-browse-pvc 65 | LICENSE 66 | 67 | build-linux-x86_64: 68 | runs-on: ubuntu-latest 69 | steps: 70 | - name: Set up Go 71 | uses: actions/setup-go@v4.1.0 72 | with: 73 | go-version: "1.22.0" 74 | 75 | - name: Check out Code 76 | uses: actions/checkout@v4.0.0 77 | 78 | - name: Build 79 | run: | 80 | cd src 81 | go build -v -ldflags "-X main.Version=${{ inputs.version }}" -o kubectl-browse-pvc . 82 | mv kubectl-browse-pvc .. 83 | 84 | - name: Fix permissions 85 | run: chmod +x ./kubectl-browse-pvc 86 | 87 | - name: Upload Artifact 88 | uses: actions/upload-artifact@v4 89 | with: 90 | name: kubectl-browse-pvc-linux-x86_64 91 | path: | 92 | ./kubectl-browse-pvc 93 | LICENSE 94 | 95 | build-linux-arm: 96 | runs-on: ubuntu-latest 97 | steps: 98 | - name: Set up Go 99 | uses: actions/setup-go@v4.1.0 100 | with: 101 | go-version: "1.22.0" 102 | 103 | - name: Check out Code 104 | uses: actions/checkout@v4.0.0 105 | 106 | - name: Build 107 | run: | 108 | cd src 109 | GOARCH=arm64 go build -v -ldflags "-X main.Version=${{ inputs.version }}" -o kubectl-browse-pvc . 110 | mv kubectl-browse-pvc .. 111 | 112 | - name: Fix permissions 113 | run: chmod +x ./kubectl-browse-pvc 114 | 115 | - name: Upload Artifact 116 | uses: actions/upload-artifact@v4 117 | with: 118 | name: kubectl-browse-pvc-linux-arm 119 | path: | 120 | ./kubectl-browse-pvc 121 | LICENSE 122 | -------------------------------------------------------------------------------- /src/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/clbx/kubectl-browse-pvc 2 | 3 | go 1.23.0 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/briandowns/spinner v1.23.0 9 | github.com/spf13/cobra v1.7.0 10 | golang.org/x/term v0.29.0 11 | k8s.io/api v0.30.1 12 | k8s.io/apimachinery v0.30.1 13 | k8s.io/cli-runtime v0.30.1 14 | k8s.io/client-go v0.30.1 15 | ) 16 | 17 | require ( 18 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect 19 | github.com/davecgh/go-spew v1.1.1 // indirect 20 | github.com/emicklei/go-restful/v3 v3.11.0 // indirect 21 | github.com/evanphx/json-patch v4.12.0+incompatible // indirect 22 | github.com/fatih/color v1.7.0 // indirect 23 | github.com/go-errors/errors v1.4.2 // indirect 24 | github.com/go-logr/logr v1.4.1 // indirect 25 | github.com/go-openapi/jsonpointer v0.19.6 // indirect 26 | github.com/go-openapi/jsonreference v0.20.2 // indirect 27 | github.com/go-openapi/swag v0.22.3 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/protobuf v1.5.4 // indirect 30 | github.com/google/btree v1.0.1 // indirect 31 | github.com/google/gnostic-models v0.6.8 // indirect 32 | github.com/google/gofuzz v1.2.0 // indirect 33 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 34 | github.com/google/uuid v1.3.0 // indirect 35 | github.com/gorilla/websocket v1.5.0 // indirect 36 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect 37 | github.com/imdario/mergo v0.3.6 // indirect 38 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 39 | github.com/josharian/intern v1.0.0 // indirect 40 | github.com/json-iterator/go v1.1.12 // indirect 41 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 42 | github.com/mailru/easyjson v0.7.7 // indirect 43 | github.com/mattn/go-colorable v0.1.13 // indirect 44 | github.com/mattn/go-isatty v0.0.16 // indirect 45 | github.com/moby/spdystream v0.2.0 // indirect 46 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587 // indirect 47 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 48 | github.com/modern-go/reflect2 v1.0.2 // indirect 49 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 50 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 51 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 52 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 53 | github.com/pkg/errors v0.9.1 // indirect 54 | github.com/spf13/pflag v1.0.5 // indirect 55 | github.com/xlab/treeprint v1.2.0 // indirect 56 | go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect 57 | golang.org/x/net v0.36.0 // indirect 58 | golang.org/x/oauth2 v0.10.0 // indirect 59 | golang.org/x/sync v0.11.0 // indirect 60 | golang.org/x/sys v0.30.0 // indirect 61 | golang.org/x/text v0.22.0 // indirect 62 | golang.org/x/time v0.3.0 // indirect 63 | google.golang.org/appengine v1.6.7 // indirect 64 | google.golang.org/protobuf v1.33.0 // indirect 65 | gopkg.in/inf.v0 v0.9.1 // indirect 66 | gopkg.in/yaml.v2 v2.4.0 // indirect 67 | gopkg.in/yaml.v3 v3.0.1 // indirect 68 | k8s.io/klog/v2 v2.120.1 // indirect 69 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect 70 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect 71 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect 72 | sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect 73 | sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect 74 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect 75 | sigs.k8s.io/yaml v1.3.0 // indirect 76 | ) 77 | -------------------------------------------------------------------------------- /src/util.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | batchv1 "k8s.io/api/batch/v1" 5 | corev1 "k8s.io/api/core/v1" 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | ) 8 | 9 | type PodOptions struct { 10 | image string 11 | namespace string 12 | pvc corev1.PersistentVolumeClaim 13 | cmd []string 14 | args []string 15 | node string 16 | user int64 17 | } 18 | 19 | var script = ` 20 | chmod +x /etc/profile.d/ps1.sh 21 | 22 | base_processes=$(ls /proc | grep -E '^[0-9]+$' | while read -r pid; do cat /proc/"$pid"/comm 2>/dev/null; done | grep -E "ash|bash|sh" | wc -l) 23 | echo "Processes: $base_processes" 24 | sleep 2 25 | 26 | while :; do 27 | shell_processes=$(ls /proc | grep -E '^[0-9]+$' | while read -r pid; do cat /proc/"$pid"/comm 2>/dev/null; done | grep -E "ash|bash|sh" | wc -l) 28 | if [ "$shell_processes" -gt "$base_processes" ]; then 29 | echo "Found an additional process" 30 | while [ "$shell_processes" -gt "$base_processes" ]; do 31 | sleep 2 32 | shell_processes=$(ls /proc | grep -E '^[0-9]+$' | while read -r pid; do cat /proc/"$pid"/comm 2>/dev/null; done | grep -E "ash|bash|sh" | wc -l) 33 | done 34 | exit 0 35 | fi 36 | done 37 | ` 38 | 39 | // Finds if a pod that attached to a PVC 40 | func findPodByPVC(podList corev1.PodList, pvc corev1.PersistentVolumeClaim) *corev1.Pod { 41 | for _, pod := range podList.Items { 42 | for _, volume := range pod.Spec.Volumes { 43 | if volume.PersistentVolumeClaim != nil && volume.PersistentVolumeClaim.ClaimName == pvc.Name { 44 | return &pod 45 | } 46 | } 47 | } 48 | return nil 49 | } 50 | 51 | // Returns a job for the get command. 52 | // func buildPvcbGetJob(namespace string, image string, pvc corev1.PersistentVolumeClaim) *batchv1.Job { 53 | func buildPvcbGetJob(options PodOptions) *batchv1.Job { 54 | 55 | //Check if provided arguments is empty. If so use the browsing script 56 | if len(options.args) == 0 { 57 | options.args = []string{script} 58 | } 59 | 60 | // Setup SecurityContext 61 | var allowPrivilegeEscalation bool 62 | var runAsNonRoot bool 63 | if options.user == 0 { 64 | runAsNonRoot = false 65 | allowPrivilegeEscalation = true 66 | } else { 67 | runAsNonRoot = true 68 | allowPrivilegeEscalation = false 69 | } 70 | 71 | securityContext := corev1.SecurityContext{ 72 | RunAsUser: &options.user, 73 | RunAsNonRoot: &runAsNonRoot, 74 | AllowPrivilegeEscalation: &allowPrivilegeEscalation, 75 | Capabilities: &corev1.Capabilities{ 76 | Drop: []corev1.Capability{"ALL"}, 77 | }, 78 | SeccompProfile: &corev1.SeccompProfile{ 79 | Type: "RuntimeDefault", 80 | }, 81 | } 82 | 83 | TTLSecondsAfterFinished := new(int32) 84 | *TTLSecondsAfterFinished = 10 85 | 86 | job := &batchv1.Job{ 87 | ObjectMeta: metav1.ObjectMeta{ 88 | Name: "browse-" + options.pvc.Name, 89 | Namespace: options.namespace, 90 | }, 91 | Spec: batchv1.JobSpec{ 92 | TTLSecondsAfterFinished: TTLSecondsAfterFinished, 93 | Template: corev1.PodTemplateSpec{ 94 | ObjectMeta: metav1.ObjectMeta{ 95 | Name: "browse-pvc", 96 | Labels: map[string]string{ 97 | "job-name": "browse-" + options.pvc.Name, 98 | }, 99 | }, 100 | Spec: corev1.PodSpec{ 101 | RestartPolicy: "Never", 102 | NodeName: options.node, 103 | Containers: []corev1.Container{ 104 | { 105 | Name: "browser", 106 | Image: image, 107 | Command: options.cmd, 108 | Args: options.args, 109 | SecurityContext: &securityContext, 110 | Env: []corev1.EnvVar{ 111 | { 112 | Name: "PS1", 113 | Value: "\\h:\\w\\$ ", 114 | }, 115 | }, 116 | VolumeMounts: []corev1.VolumeMount{ 117 | { 118 | Name: "target-pvc", 119 | MountPath: "/mnt", 120 | }, 121 | }, 122 | }, 123 | }, 124 | Volumes: []corev1.Volume{ 125 | { 126 | Name: "target-pvc", 127 | VolumeSource: corev1.VolumeSource{ 128 | PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{ 129 | ClaimName: options.pvc.Name, 130 | }, 131 | }, 132 | }, 133 | }, 134 | }, 135 | }, 136 | }, 137 | } 138 | return job 139 | } 140 | -------------------------------------------------------------------------------- /src/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "io" 8 | "log" 9 | "os" 10 | "time" 11 | 12 | "github.com/briandowns/spinner" 13 | "github.com/spf13/cobra" 14 | "golang.org/x/term" 15 | 16 | corev1 "k8s.io/api/core/v1" 17 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 18 | 19 | "k8s.io/cli-runtime/pkg/genericclioptions" 20 | "k8s.io/client-go/kubernetes/scheme" 21 | _ "k8s.io/client-go/plugin/pkg/client/auth" 22 | "k8s.io/client-go/tools/clientcmd" 23 | "k8s.io/client-go/tools/remotecommand" 24 | 25 | "k8s.io/client-go/kubernetes" 26 | ) 27 | 28 | var image string 29 | var Version string 30 | var containerUser int 31 | 32 | func main() { 33 | 34 | var kubeConfigFlags = genericclioptions.NewConfigFlags(true) 35 | 36 | if Version == "" { 37 | Version = "devel" 38 | } 39 | 40 | var rootCmd = &cobra.Command{ 41 | Use: "kubectl-browse-pvc [-- COMMAND [ARGS...]]", 42 | Short: "Kubernetes PVC Browser", 43 | Long: `Kubernetes PVC Browser`, 44 | Version: Version, 45 | Args: cobra.MinimumNArgs(1), 46 | Run: func(cmd *cobra.Command, args []string) { 47 | pvcName := args[0] 48 | commandArgs := args[1:] 49 | browseCommand(kubeConfigFlags, pvcName, commandArgs) 50 | }, 51 | } 52 | 53 | rootCmd.Flags().StringVarP(&image, "image", "i", "alpine", "Image to mount job to") 54 | rootCmd.Flags().IntVarP(&containerUser, "container-user", "u", 0, "User ID to run the container as") 55 | kubeConfigFlags.AddFlags(rootCmd.Flags()) 56 | 57 | if err := rootCmd.Execute(); err != nil { 58 | log.Fatalf("Error executing command: %s\n", err) 59 | } 60 | 61 | } 62 | 63 | func browseCommand(kubeConfigFlags *genericclioptions.ConfigFlags, pvcName string, commandArgs []string) error { 64 | 65 | config, err := kubeConfigFlags.ToRESTConfig() 66 | if err != nil { 67 | log.Fatalf("Failed to create kubernetes config: %v", err) 68 | } 69 | 70 | clientset, err := kubernetes.NewForConfig(config) 71 | if err != nil { 72 | log.Fatalf("Failed to create kubernetes client: %v", err) 73 | } 74 | 75 | // Get namespace if not set 76 | namespace := *kubeConfigFlags.Namespace 77 | if namespace == "" { 78 | config, err := clientcmd.NewDefaultClientConfigLoadingRules().Load() 79 | if err != nil { 80 | log.Fatalf("Failed to load kubeconfig: %v", err) 81 | } 82 | namespace = config.Contexts[config.CurrentContext].Namespace 83 | if err != nil { 84 | log.Fatalf("Failed to get namespace from current context: %v", err) 85 | } 86 | } 87 | 88 | fmt.Printf("Browsing PVC %s in namespace %s\n", pvcName, namespace) 89 | 90 | targetPvc, err := clientset.CoreV1().PersistentVolumeClaims(namespace).Get(context.TODO(), pvcName, metav1.GetOptions{}) 91 | if err != nil { 92 | log.Fatalf("Failed to get PVC: %v", err) 93 | } 94 | 95 | nsPods, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{}) 96 | if err != nil { 97 | log.Fatalf("Failed to get pods: %v", err) 98 | } 99 | 100 | attachedPod := findPodByPVC(*nsPods, *targetPvc) 101 | 102 | manyAccessMode := false 103 | for _, mode := range targetPvc.Spec.AccessModes { 104 | if mode == corev1.ReadWriteMany || mode == corev1.ReadOnlyMany { 105 | manyAccessMode = true 106 | break 107 | } 108 | } 109 | 110 | var node string = "" 111 | 112 | // If the pvc is a rwo and already attached to a node, it needs to scheduled to the same node as the workload 113 | if attachedPod != nil && !manyAccessMode { 114 | node = attachedPod.Spec.NodeName 115 | } 116 | 117 | options := &PodOptions{ 118 | image: image, 119 | namespace: namespace, 120 | pvc: *targetPvc, 121 | cmd: []string{"/bin/sh", "-c", "--"}, 122 | args: commandArgs, 123 | node: node, 124 | user: int64(containerUser), 125 | } 126 | 127 | // Build the Job 128 | pvcbGetJob := buildPvcbGetJob(*options) 129 | // Create Job 130 | pvcbGetJob, err = clientset.BatchV1().Jobs(namespace).Create(context.TODO(), pvcbGetJob, metav1.CreateOptions{}) 131 | 132 | //Handle if there were command args 133 | if len(commandArgs) > 0 { 134 | waitForJobCompletion(*clientset, namespace, pvcbGetJob.Name) 135 | logs, err := getPodLogs(clientset, namespace, targetPvc.Name) 136 | if err != nil { 137 | log.Fatalf("Failed to get logs: %v", err) 138 | } 139 | fmt.Printf("%s", logs) 140 | return nil 141 | } 142 | 143 | if err != nil { 144 | log.Fatalf("Failed to create job: %v", err) 145 | } 146 | 147 | timeout := 30 148 | 149 | for timeout > 0 { 150 | pvcbGetJob, err = clientset.BatchV1().Jobs(namespace).Get(context.TODO(), pvcbGetJob.GetObjectMeta().GetName(), metav1.GetOptions{}) 151 | 152 | if err != nil { 153 | log.Fatalf("Failed to get job: %v", err) 154 | } 155 | 156 | if pvcbGetJob.Status.Active > 0 { 157 | break 158 | } 159 | 160 | time.Sleep(time.Second) 161 | 162 | timeout-- 163 | } 164 | 165 | // Find the created pod. 166 | podList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{ 167 | LabelSelector: "job-name=" + pvcbGetJob.Name, 168 | }) 169 | 170 | if err != nil { 171 | log.Fatalf("Failed to get pods: %v", err) 172 | } 173 | 174 | if len(podList.Items) != 1 { 175 | fmt.Printf("%d\n", len(podList.Items)) 176 | log.Fatalf("Found an unexpected number of controllers, this shouldn't happen.") 177 | } 178 | 179 | pod := &podList.Items[0] 180 | 181 | podSpinner := spinner.New(spinner.CharSets[11], 100*time.Millisecond) 182 | podSpinner.Suffix = " Waiting for Pod to Start\n" 183 | podSpinner.FinalMSG = "✓ Attached to " + pod.Name + "\n" 184 | podSpinner.Start() 185 | 186 | for pod.Status.Phase != corev1.PodRunning && timeout > 0 { 187 | 188 | pod, err = clientset.CoreV1().Pods(namespace).Get(context.TODO(), pod.Name, metav1.GetOptions{}) 189 | if err != nil { 190 | log.Fatalf("Failed to get pod: %v", err) 191 | } 192 | 193 | time.Sleep(time.Second) 194 | timeout-- 195 | } 196 | 197 | podSpinner.Stop() 198 | if timeout == 0 { 199 | log.Fatalf("Pod failed to start") 200 | } 201 | 202 | request := clientset.CoreV1().RESTClient(). 203 | Post(). 204 | Resource("pods"). 205 | Name(pod.Name). 206 | Namespace(options.namespace). 207 | SubResource("exec"). 208 | VersionedParams(&corev1.PodExecOptions{ 209 | Command: []string{"sh", "-c", "cd /mnt && (ash || bash || sh)"}, 210 | Stdin: true, 211 | Stdout: true, 212 | Stderr: true, 213 | TTY: true, 214 | }, scheme.ParameterCodec) 215 | 216 | exec, err := remotecommand.NewSPDYExecutor(config, "POST", request.URL()) 217 | if err != nil { 218 | return err 219 | } 220 | 221 | oldState, err := term.MakeRaw(0) 222 | if err != nil { 223 | panic(err) 224 | } 225 | defer term.Restore(0, oldState) 226 | 227 | terminalSizeQueue := &sizeQueue{ 228 | resizeChan: make(chan remotecommand.TerminalSize, 1), 229 | stopResizing: make(chan struct{}), 230 | } 231 | 232 | // prime with initial term size 233 | width, height, err := term.GetSize(int(os.Stdout.Fd())) 234 | if err == nil { 235 | terminalSizeQueue.resizeChan <- remotecommand.TerminalSize{Width: uint16(width), Height: uint16(height)} 236 | } 237 | 238 | go terminalSizeQueue.MonitorSize() 239 | defer terminalSizeQueue.Stop() 240 | 241 | err = exec.StreamWithContext(context.TODO(), remotecommand.StreamOptions{ 242 | Stdin: os.Stdin, 243 | Stdout: os.Stdout, 244 | Stderr: os.Stderr, 245 | Tty: true, 246 | TerminalSizeQueue: terminalSizeQueue, 247 | }) 248 | if err != nil { 249 | return err 250 | } 251 | 252 | return nil 253 | } 254 | 255 | func waitForJobCompletion(clientset kubernetes.Clientset, namespace string, jobName string) error { 256 | timeout := 300 257 | for timeout > 0 { 258 | job, err := clientset.BatchV1().Jobs(namespace).Get(context.TODO(), jobName, metav1.GetOptions{}) 259 | if err != nil { 260 | return err 261 | } 262 | if job.Status.Succeeded > 0 || job.Status.Failed > 0 { 263 | return nil 264 | } 265 | time.Sleep(time.Second) 266 | timeout-- 267 | } 268 | return fmt.Errorf("job failed to complete within 300s") 269 | } 270 | 271 | func getPodLogs(clientset *kubernetes.Clientset, namespace string, pvcName string) (string, error) { 272 | 273 | //find the pod based on the label applied to the pod in the job 274 | labelSelector := fmt.Sprintf("job-name=browse-%s", pvcName) 275 | podList, err := clientset.CoreV1().Pods(namespace).List(context.TODO(), metav1.ListOptions{ 276 | LabelSelector: labelSelector, 277 | }) 278 | 279 | if err != nil { 280 | return "", fmt.Errorf("failed to get pods: %v", err) 281 | } 282 | 283 | if len(podList.Items) != 1 { 284 | return "", fmt.Errorf("found an unexpected number of pods with the browse label, this shouldn't happen") 285 | } 286 | 287 | podName := podList.Items[0].Name 288 | 289 | podLogOpts := corev1.PodLogOptions{} 290 | req := clientset.CoreV1().Pods(namespace).GetLogs(podName, &podLogOpts) 291 | podLogs, err := req.Stream(context.TODO()) 292 | 293 | if err != nil { 294 | return "", fmt.Errorf("failed to get logs: %v", err) 295 | } 296 | 297 | defer podLogs.Close() 298 | 299 | buf := new(bytes.Buffer) 300 | _, err = io.Copy(buf, podLogs) 301 | if err != nil { 302 | return "", fmt.Errorf("failed to copy logs: %v", err) 303 | } 304 | return buf.String(), nil 305 | } 306 | -------------------------------------------------------------------------------- /src/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= 3 | github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= 4 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 5 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 6 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 7 | github.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A= 8 | github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE= 9 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 10 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 11 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 12 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 13 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 15 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 16 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 17 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 18 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 19 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 20 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 21 | github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= 22 | github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 23 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 24 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 25 | github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84= 26 | github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= 27 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 28 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 29 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= 30 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 31 | github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= 32 | github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 33 | github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= 34 | github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= 35 | github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= 36 | github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= 37 | github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= 38 | github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= 39 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= 40 | github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= 41 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 42 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 43 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 44 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 45 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 46 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 47 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 48 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 49 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 50 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 51 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 52 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 53 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 54 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 55 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 56 | github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= 57 | github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= 58 | github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= 59 | github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= 60 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 61 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 62 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 63 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 64 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 65 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 66 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 67 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 68 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 69 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 70 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 71 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 72 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= 73 | github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= 74 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 75 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 76 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 77 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 78 | github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 79 | github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= 80 | github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 81 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= 82 | github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 83 | github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= 84 | github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= 85 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 86 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 87 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 88 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 89 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 90 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 91 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 92 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 93 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 94 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 95 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 96 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 97 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 98 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 99 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 100 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 101 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 102 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 103 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 104 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 105 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 106 | github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= 107 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 108 | github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= 109 | github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= 110 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= 111 | github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 112 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 113 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 114 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 115 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 116 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 117 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 118 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 119 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 120 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 121 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 122 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 123 | github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= 124 | github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= 125 | github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= 126 | github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= 127 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 128 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 129 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 130 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 131 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 132 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 133 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 134 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= 135 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= 136 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 137 | github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= 138 | github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 139 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 140 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 141 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 142 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 143 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 144 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 145 | github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= 146 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 147 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 148 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 149 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 150 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 151 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 152 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 153 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 154 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 155 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 156 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 157 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 158 | go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY= 159 | go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds= 160 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 161 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 162 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 163 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 164 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 165 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 166 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 167 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 168 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 169 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 170 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 171 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 172 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 173 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 174 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 175 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 176 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 177 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 178 | golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= 179 | golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= 180 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 181 | golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8= 182 | golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI= 183 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 184 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 185 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 186 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 187 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 188 | golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= 189 | golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 190 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 191 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 192 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 193 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 194 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 195 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 196 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 197 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 198 | golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= 199 | golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 200 | golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 201 | golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= 202 | golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= 203 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 204 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 205 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 206 | golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= 207 | golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= 208 | golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= 209 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 210 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 211 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 212 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 213 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 214 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 215 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 216 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 217 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 218 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= 219 | golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= 220 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 221 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 222 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 223 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 224 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 225 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 226 | google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= 227 | google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 228 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 229 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 230 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 231 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 232 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 233 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 234 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 235 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 236 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 237 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 238 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 239 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 240 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 241 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 242 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= 243 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 244 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 245 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 246 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 247 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 248 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 249 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 250 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 251 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= 252 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 253 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 254 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 255 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 256 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 257 | k8s.io/api v0.30.1 h1:kCm/6mADMdbAxmIh0LBjS54nQBE+U4KmbCfIkF5CpJY= 258 | k8s.io/api v0.30.1/go.mod h1:ddbN2C0+0DIiPntan/bye3SW3PdwLa11/0yqwvuRrJM= 259 | k8s.io/apimachinery v0.30.1 h1:ZQStsEfo4n65yAdlGTfP/uSHMQSoYzU/oeEbkmF7P2U= 260 | k8s.io/apimachinery v0.30.1/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= 261 | k8s.io/cli-runtime v0.30.1 h1:kSBBpfrJGS6lllc24KeniI9JN7ckOOJKnmFYH1RpTOw= 262 | k8s.io/cli-runtime v0.30.1/go.mod h1:zhHgbqI4J00pxb6gM3gJPVf2ysDjhQmQtnTxnMScab8= 263 | k8s.io/client-go v0.30.1 h1:uC/Ir6A3R46wdkgCV3vbLyNOYyCJ8oZnjtJGKfytl/Q= 264 | k8s.io/client-go v0.30.1/go.mod h1:wrAqLNs2trwiCH/wxxmT/x3hKVH9PuV0GGW0oDoHVqc= 265 | k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= 266 | k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 267 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= 268 | k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= 269 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI= 270 | k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 271 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= 272 | sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= 273 | sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 h1:XX3Ajgzov2RKUdc5jW3t5jwY7Bo7dcRm+tFxT+NfgY0= 274 | sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3/go.mod h1:9n16EZKMhXBNSiUC5kSdFQJkdH3zbxS/JoO619G1VAY= 275 | sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 h1:W6cLQc5pnqM7vh3b7HvGNfXrJ/xL6BDMS0v1V/HHg5U= 276 | sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3/go.mod h1:JWP1Fj0VWGHyw3YUPjXSQnRnrwezrZSrApfX5S0nIag= 277 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= 278 | sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= 279 | sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo= 280 | sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= 281 | --------------------------------------------------------------------------------