├── .gitignore ├── screenshots └── kubectl-open-svc-plugin.gif ├── .golangci.yaml ├── .github └── workflows │ ├── ci.yaml │ └── release.yaml ├── cmd └── kubectl-open_svc.go ├── pkg ├── utils │ ├── http.go │ └── http_test.go └── cmd │ ├── open-svc_test.go │ └── open-svc.go ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── Makefile ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /hack/tools/bin 3 | -------------------------------------------------------------------------------- /screenshots/kubectl-open-svc-plugin.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/superbrothers/kubectl-open-svc-plugin/HEAD/screenshots/kubectl-open-svc-plugin.gif -------------------------------------------------------------------------------- /.golangci.yaml: -------------------------------------------------------------------------------- 1 | run: 2 | timeout: 5m 3 | linters: 4 | disable-all: true 5 | enable: 6 | - errcheck 7 | - gofmt 8 | - gosimple 9 | - govet 10 | - ineffassign 11 | - staticcheck 12 | - typecheck 13 | - unused 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths-ignore: ['**.md'] 7 | pull_request: 8 | types: [opened, synchronize] 9 | paths-ignore: ['**.md'] 10 | 11 | jobs: 12 | run: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | - uses: actions/setup-go@v5 17 | with: 18 | go-version-file: go.mod 19 | - name: Ensure go.mod is already tidied 20 | run: go mod tidy && git diff -s --exit-code go.sum 21 | - run: make lint test 22 | - run: make dist 23 | - run: make validate-krew-manifest 24 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ["v*"] 6 | 7 | jobs: 8 | run: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | - uses: actions/setup-go@v5 13 | with: 14 | go-version-file: go.mod 15 | - run: make release 16 | - uses: softprops/action-gh-release@v1 17 | with: 18 | files: dist/!(config.yaml) 19 | env: 20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 21 | - uses: rajatjindal/krew-release-bot@v0.0.47 22 | with: 23 | krew_template_file: dist/krew/open-svc.yaml 24 | -------------------------------------------------------------------------------- /cmd/kubectl-open_svc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os" 5 | 6 | "github.com/spf13/pflag" 7 | "github.com/superbrothers/kubectl-open-svc-plugin/pkg/cmd" 8 | 9 | "k8s.io/cli-runtime/pkg/genericclioptions" 10 | "k8s.io/klog/v2" 11 | ) 12 | 13 | func main() { 14 | klog.InitFlags(nil) 15 | defer klog.Flush() 16 | 17 | flags := pflag.NewFlagSet("kubectl-open-svc", pflag.ExitOnError) 18 | pflag.CommandLine = flags 19 | 20 | root := cmd.NewCmdOpenService(genericclioptions.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}) 21 | if err := root.Execute(); err != nil { 22 | os.Exit(1) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /pkg/utils/http.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | "strconv" 8 | "strings" 9 | ) 10 | 11 | func StripModifierFunc(s string) func(r *http.Response) error { 12 | return func(r *http.Response) error { 13 | for key, vals := range r.Header { 14 | for i, val := range vals { 15 | vals[i] = strings.ReplaceAll(val, s, "") 16 | } 17 | r.Header[key] = vals 18 | } 19 | 20 | b, err := io.ReadAll(r.Body) 21 | if err != nil { 22 | return err 23 | } 24 | 25 | b = bytes.ReplaceAll(b, []byte(s), []byte("")) 26 | r.Body = io.NopCloser(bytes.NewReader(b)) 27 | r.Header.Set("Content-Length", strconv.Itoa(len(b))) 28 | 29 | return nil 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.goreleaser.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | project_name: kubectl-open_svc 3 | builds: 4 | - main: ./cmd/kubectl-open_svc.go 5 | binary: kubectl-open_svc 6 | env: 7 | - CGO_ENABLED=0 8 | goos: 9 | - linux 10 | - windows 11 | - darwin 12 | goarch: 13 | - amd64 14 | - arm 15 | - arm64 16 | archives: 17 | - name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}" 18 | format: zip 19 | files: 20 | - LICENSE 21 | - README.md 22 | wrap_in_directory: false 23 | checksum: 24 | name_template: 'checksums.txt' 25 | changelog: 26 | sort: asc 27 | krews: 28 | - name: open-svc 29 | homepage: https://github.com/superbrothers/kubectl-open-svc-plugin 30 | description: | 31 | Open the Kubernetes URL(s) for the specified service in your browser. 32 | Unlike the `kubectl port-forward` command, this plugin makes services 33 | accessible via their ClusterIP. 34 | short_description: Open the Kubernetes URL(s) for the specified service in your browser. 35 | skip_upload: false 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kazuki Suda 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 | -------------------------------------------------------------------------------- /pkg/utils/http_test.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "net/http" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestStripModifierFunc(t *testing.T) { 13 | tests := []struct { 14 | name string 15 | s string 16 | res *http.Response 17 | header http.Header 18 | body []byte 19 | }{ 20 | { 21 | name: "modified", 22 | s: "/api/v1/namespaces/default/services/nginx/proxy", 23 | res: &http.Response{ 24 | Header: map[string][]string{ 25 | "Location": {"/api/v1/namespaces/default/services/nginx/proxy/login"}, 26 | "Content-Length": {"86"}, 27 | }, 28 | Body: io.NopCloser(bytes.NewReader([]byte(``))), 29 | }, 30 | header: http.Header(map[string][]string{ 31 | "Location": {"/login"}, 32 | "Content-Length": {"39"}, 33 | }), 34 | body: []byte(``), 35 | }, 36 | } 37 | 38 | for _, tt := range tests { 39 | t.Run(tt.name, func(t *testing.T) { 40 | modifier := StripModifierFunc(tt.s) 41 | _ = modifier(tt.res) 42 | 43 | assert.Equal(t, tt.header, tt.res.Header) 44 | 45 | b, _ := io.ReadAll(tt.res.Body) 46 | assert.Equal(t, tt.body, b) 47 | }) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kubectl open-svc SERVICE_NAME 2 | 3 | This is a kubectl plugin that open the Kubernetes URL(s) for the specified service in your browser. Unlike the `kubectl port-forward` command, this plugin makes services accessible via their ClusterIP. 4 | 5 | ![Screenshot](./screenshots/kubectl-open-svc-plugin.gif) 6 | 7 | ``` 8 | $ kubectl open-svc -h 9 | Open the Kubernetes URL(s) for the specified service in your browser through a local proxy server. 10 | 11 | Usage: 12 | kubectl open-svc SERVICE [--port=8001] [--address=127.0.0.1] [--keepalive=0] [flags] 13 | 14 | Examples: 15 | # Open service/kubernetes-dashboard in namespace/kube-system 16 | kubectl open-svc kubernetes-dashboard -n kube-system 17 | 18 | # Open port "http-monitoring" of service/istiod in namespace/istio-system 19 | kubectl open-svc istiod -n istio-system --svc-port http-monitoring 20 | 21 | # Open port 15014 of service/istiod in namespace/istio-system 22 | kubectl open-svc istiod -n istio-system --svc-port 15014 23 | 24 | # Use "https" scheme with --scheme option for connections between the apiserver 25 | # and service/rook-ceph-mgr-dashboard in namespace/rook-ceph 26 | kubectl open-svc rook-ceph-mgr-dashboard -n rook-ceph --scheme https 27 | ``` 28 | 29 | ## Install the plugin 30 | 31 | 1. Install [krew](https://github.com/GoogleContainerTools/krew) that is a plugin manager for kubectl 32 | 2. Run: 33 | 34 | kubectl krew install open-svc 35 | 36 | 3. Try it out 37 | 38 | kubectl open-svc -h 39 | 40 | ## License 41 | 42 | This software is released under the MIT License. 43 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= go 2 | DIST_DIR := dist 3 | 4 | TOOLS_BIN_DIR := $(CURDIR)/hack/tools/bin 5 | $(shell mkdir -p $(TOOLS_BIN_DIR)) 6 | 7 | GORELEASER := $(TOOLS_BIN_DIR)/goreleaser 8 | GORELEASER_VERSION ?= v2.4.8 9 | GOLANGCI_LINT := $(TOOLS_BIN_DIR)/golangci-lint 10 | GOLANGCI_LINT_VERSION ?= v1.62.2 11 | VALIDATE_KREW_MAIFEST := $(TOOLS_BIN_DIR)/validate-krew-manifest 12 | VALIDATE_KREW_MAIFEST_VERSION ?= v0.4.4 13 | GORELEASER_FILTER_VERSION ?= v0.3.0 14 | GORELEASER_FILTER := $(TOOLS_BIN_DIR)/goreleaser-filter 15 | 16 | $(GORELEASER): 17 | GOBIN=$(TOOLS_BIN_DIR) $(GO) install github.com/goreleaser/goreleaser/v2@$(GORELEASER_VERSION) 18 | 19 | $(GOLANGCI_LINT): 20 | GOBIN=$(TOOLS_BIN_DIR) $(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) 21 | 22 | $(VALIDATE_KREW_MAIFEST): 23 | GOBIN=$(TOOLS_BIN_DIR) $(GO) install sigs.k8s.io/krew/cmd/validate-krew-manifest@$(VALIDATE_KREW_MAIFEST_VERSION) 24 | 25 | $(GORELEASER_FILTER): 26 | GOBIN=$(TOOLS_BIN_DIR) go install github.com/t0yv0/goreleaser-filter@$(GORELEASER_FILTER_VERSION) 27 | 28 | .PHONY: build 29 | build: $(GORELEASER) 30 | $(GORELEASER) build --snapshot --clean --single-target --output $(DIST_DIR)/kubectl-open_svc 31 | 32 | .PHONY: build-cross 33 | build-cross: $(GORELEASER) 34 | $(GORELEASER) build --snapshot --clean 35 | 36 | .PHONY: lint 37 | lint: $(GOLANGCI_LINT) 38 | $(GOLANGCI_LINT) run 39 | 40 | .PHONY: lint-fix 41 | lint-fix: $(GOLANGCI_LINT) 42 | $(GOLANGCI_LINT) run --fix 43 | 44 | .PHONY: test 45 | test: 46 | $(GO) test -v ./... 47 | 48 | .PHONY: validate-krew-manifest 49 | validate-krew-manifest: $(VALIDATE_KREW_MAIFEST) 50 | $(VALIDATE_KREW_MAIFEST) -manifest dist/krew/open-svc.yaml -skip-install 51 | 52 | .PHONY: dist 53 | dist: $(GORELEASER_FILTER) $(GORELEASER) 54 | cat .goreleaser.yaml | $(GORELEASER_FILTER) -goos $(shell go env GOOS) -goarch $(shell go env GOARCH) | $(GORELEASER) release -f- --clean --skip=publish --snapshot 55 | 56 | .PHONY: dist-all 57 | dist-all: $(GORELEASER) 58 | $(GORELEASER) release --clean --skip=publish --snapshot 59 | 60 | .PHONY: release 61 | release: $(GORELEASER) 62 | $(GORELEASER) release --clean --skip=publish 63 | 64 | .PHONY: clean 65 | clean: 66 | rm -rf $(DIST_DIR) $(TOOLS_BIN_DIR) 67 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/superbrothers/kubectl-open-svc-plugin 2 | 3 | go 1.23.4 4 | 5 | require ( 6 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c 7 | github.com/spf13/cobra v1.8.1 8 | github.com/spf13/pflag v1.0.5 9 | github.com/stretchr/testify v1.9.0 10 | k8s.io/api v0.32.0 11 | k8s.io/apimachinery v0.32.0 12 | k8s.io/cli-runtime v0.32.0 13 | k8s.io/client-go v0.32.0 14 | k8s.io/klog/v2 v2.130.1 15 | k8s.io/kubectl v0.32.0 16 | ) 17 | 18 | require ( 19 | github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect 20 | github.com/MakeNowJust/heredoc v1.0.0 // indirect 21 | github.com/blang/semver/v4 v4.0.0 // indirect 22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 | github.com/emicklei/go-restful/v3 v3.12.1 // indirect 24 | github.com/fxamacker/cbor/v2 v2.7.0 // indirect 25 | github.com/go-errors/errors v1.5.1 // indirect 26 | github.com/go-logr/logr v1.4.2 // indirect 27 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 28 | github.com/go-openapi/jsonreference v0.21.0 // indirect 29 | github.com/go-openapi/swag v0.23.0 // indirect 30 | github.com/gogo/protobuf v1.3.2 // indirect 31 | github.com/golang/protobuf v1.5.4 // indirect 32 | github.com/google/btree v1.1.3 // indirect 33 | github.com/google/gnostic-models v0.6.9 // indirect 34 | github.com/google/go-cmp v0.6.0 // indirect 35 | github.com/google/gofuzz v1.2.0 // indirect 36 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 37 | github.com/google/uuid v1.6.0 // indirect 38 | github.com/gorilla/websocket v1.5.3 // indirect 39 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect 40 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 41 | github.com/josharian/intern v1.0.0 // indirect 42 | github.com/json-iterator/go v1.1.12 // indirect 43 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect 44 | github.com/mailru/easyjson v0.7.7 // indirect 45 | github.com/mitchellh/go-wordwrap v1.0.1 // indirect 46 | github.com/moby/spdystream v0.5.0 // indirect 47 | github.com/moby/term v0.5.0 // indirect 48 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 49 | github.com/modern-go/reflect2 v1.0.2 // indirect 50 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 51 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 52 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 53 | github.com/peterbourgon/diskv v2.0.1+incompatible // indirect 54 | github.com/pkg/errors v0.9.1 // indirect 55 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 56 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 57 | github.com/x448/float16 v0.8.4 // indirect 58 | github.com/xlab/treeprint v1.2.0 // indirect 59 | golang.org/x/net v0.32.0 // indirect 60 | golang.org/x/oauth2 v0.24.0 // indirect 61 | golang.org/x/sync v0.10.0 // indirect 62 | golang.org/x/sys v0.28.0 // indirect 63 | golang.org/x/term v0.27.0 // indirect 64 | golang.org/x/text v0.21.0 // indirect 65 | golang.org/x/time v0.8.0 // indirect 66 | google.golang.org/protobuf v1.35.2 // indirect 67 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 68 | gopkg.in/inf.v0 v0.9.1 // indirect 69 | gopkg.in/yaml.v3 v3.0.1 // indirect 70 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 // indirect 71 | k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect 72 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 73 | sigs.k8s.io/kustomize/api v0.18.0 // indirect 74 | sigs.k8s.io/kustomize/kyaml v0.18.1 // indirect 75 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0 // indirect 76 | sigs.k8s.io/yaml v1.4.0 // indirect 77 | ) 78 | -------------------------------------------------------------------------------- /pkg/cmd/open-svc_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | v1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | "k8s.io/cli-runtime/pkg/genericclioptions" 11 | ) 12 | 13 | func TestOpenServiceOptionsValidate(t *testing.T) { 14 | tests := []struct { 15 | name string 16 | o *OpenServiceOptions 17 | err error 18 | }{ 19 | { 20 | "single arg", 21 | &OpenServiceOptions{ 22 | args: []string{"nginx"}, 23 | }, 24 | nil, 25 | }, 26 | { 27 | "args >= 2", 28 | &OpenServiceOptions{ 29 | args: []string{"svc", "nginx"}, 30 | }, 31 | errors.New("exactly one SERVICE is required, got 2"), 32 | }, 33 | { 34 | "valid scheme", 35 | &OpenServiceOptions{ 36 | args: []string{"nginx"}, 37 | scheme: "https", 38 | }, 39 | nil, 40 | }, 41 | { 42 | "invalid scheme", 43 | &OpenServiceOptions{ 44 | args: []string{"nginx"}, 45 | scheme: "tcp", 46 | }, 47 | errors.New(`scheme must be "http" or "https" if specified`), 48 | }, 49 | } 50 | 51 | for _, tt := range tests { 52 | t.Run(tt.name, func(t *testing.T) { 53 | err := tt.o.Validate() 54 | if err == nil { 55 | if tt.err != err { 56 | assert.Equal(t, tt.err, err) 57 | } 58 | } else if tt.err.Error() != err.Error() { 59 | assert.Equal(t, tt.err, err) 60 | } 61 | }) 62 | } 63 | } 64 | 65 | func TestOpenServiceOptionsGetServiceProxyPath(t *testing.T) { 66 | tests := []struct { 67 | name string 68 | o *OpenServiceOptions 69 | svc *v1.Service 70 | proxyPath string 71 | err string 72 | }{ 73 | { 74 | "not specified scheme", 75 | &OpenServiceOptions{IOStreams: genericclioptions.NewTestIOStreamsDiscard()}, 76 | &v1.Service{ 77 | ObjectMeta: metav1.ObjectMeta{ 78 | Name: "nginx", 79 | Namespace: "default", 80 | }, 81 | Spec: v1.ServiceSpec{ 82 | Ports: []v1.ServicePort{ 83 | { 84 | Port: 80, 85 | }, 86 | }, 87 | }, 88 | }, 89 | "/api/v1/namespaces/default/services/nginx/proxy", 90 | "", 91 | }, 92 | { 93 | "specified scheme", 94 | &OpenServiceOptions{ 95 | scheme: "https", 96 | IOStreams: genericclioptions.NewTestIOStreamsDiscard(), 97 | }, 98 | &v1.Service{ 99 | ObjectMeta: metav1.ObjectMeta{ 100 | Name: "nginx", 101 | Namespace: "default", 102 | }, 103 | Spec: v1.ServiceSpec{ 104 | Ports: []v1.ServicePort{ 105 | { 106 | Port: 80, 107 | }, 108 | }, 109 | }, 110 | }, 111 | "/api/v1/namespaces/default/services/https:nginx:/proxy", 112 | "", 113 | }, 114 | { 115 | "443 port", 116 | &OpenServiceOptions{IOStreams: genericclioptions.NewTestIOStreamsDiscard()}, 117 | &v1.Service{ 118 | ObjectMeta: metav1.ObjectMeta{ 119 | Name: "nginx", 120 | Namespace: "default", 121 | }, 122 | Spec: v1.ServiceSpec{ 123 | Ports: []v1.ServicePort{ 124 | { 125 | Port: 443, 126 | }, 127 | }, 128 | }, 129 | }, 130 | "/api/v1/namespaces/default/services/https:nginx:/proxy", 131 | "", 132 | }, 133 | { 134 | "443 port with specified scheme", 135 | &OpenServiceOptions{ 136 | scheme: "http", 137 | IOStreams: genericclioptions.NewTestIOStreamsDiscard(), 138 | }, 139 | &v1.Service{ 140 | ObjectMeta: metav1.ObjectMeta{ 141 | Name: "nginx", 142 | Namespace: "default", 143 | }, 144 | Spec: v1.ServiceSpec{ 145 | Ports: []v1.ServicePort{ 146 | { 147 | Port: 443, 148 | }, 149 | }, 150 | }, 151 | }, 152 | "/api/v1/namespaces/default/services/http:nginx:/proxy", 153 | "", 154 | }, 155 | { 156 | "service port https", 157 | &OpenServiceOptions{IOStreams: genericclioptions.NewTestIOStreamsDiscard()}, 158 | &v1.Service{ 159 | ObjectMeta: metav1.ObjectMeta{ 160 | Name: "nginx", 161 | Namespace: "default", 162 | }, 163 | Spec: v1.ServiceSpec{ 164 | Ports: []v1.ServicePort{ 165 | { 166 | Name: "https", 167 | Port: 80, 168 | }, 169 | }, 170 | }, 171 | }, 172 | "/api/v1/namespaces/default/services/https:nginx:https/proxy", 173 | "", 174 | }, 175 | { 176 | "multiple ports", 177 | &OpenServiceOptions{IOStreams: genericclioptions.NewTestIOStreamsDiscard()}, 178 | &v1.Service{ 179 | ObjectMeta: metav1.ObjectMeta{ 180 | Name: "nginx", 181 | Namespace: "default", 182 | }, 183 | Spec: v1.ServiceSpec{ 184 | Ports: []v1.ServicePort{ 185 | { 186 | Name: "https", 187 | Port: 80, 188 | }, 189 | { 190 | Port: 8080, 191 | }, 192 | }, 193 | }, 194 | }, 195 | "/api/v1/namespaces/default/services/https:nginx:https/proxy", 196 | "", 197 | }, 198 | { 199 | "no ports", 200 | &OpenServiceOptions{IOStreams: genericclioptions.NewTestIOStreamsDiscard()}, 201 | &v1.Service{ 202 | ObjectMeta: metav1.ObjectMeta{ 203 | Name: "nginx", 204 | Namespace: "default", 205 | }, 206 | Spec: v1.ServiceSpec{ 207 | Ports: []v1.ServicePort{}, 208 | }, 209 | }, 210 | "", 211 | "Looks like service/nginx is a headless service", 212 | }, 213 | { 214 | "no ports by service port name", 215 | &OpenServiceOptions{ 216 | IOStreams: genericclioptions.NewTestIOStreamsDiscard(), 217 | svcPort: "noport", 218 | }, 219 | &v1.Service{ 220 | ObjectMeta: metav1.ObjectMeta{ 221 | Name: "nginx", 222 | Namespace: "default", 223 | }, 224 | Spec: v1.ServiceSpec{ 225 | Ports: []v1.ServicePort{ 226 | { 227 | Port: 8080, 228 | }, 229 | }, 230 | }, 231 | }, 232 | "", 233 | "port \"noport\" not found in service/nginx", 234 | }, 235 | { 236 | "not specified scheme by service port name", 237 | &OpenServiceOptions{ 238 | IOStreams: genericclioptions.NewTestIOStreamsDiscard(), 239 | svcPort: "metrics", 240 | }, 241 | &v1.Service{ 242 | ObjectMeta: metav1.ObjectMeta{ 243 | Name: "nginx", 244 | Namespace: "default", 245 | }, 246 | Spec: v1.ServiceSpec{ 247 | Ports: []v1.ServicePort{ 248 | { 249 | Name: "https", 250 | Port: 443, 251 | }, 252 | { 253 | Name: "metrics", 254 | Port: 10254, 255 | }, 256 | }, 257 | }, 258 | }, 259 | "/api/v1/namespaces/default/services/nginx:metrics/proxy", 260 | "", 261 | }, 262 | { 263 | "specified scheme by service port name", 264 | &OpenServiceOptions{ 265 | IOStreams: genericclioptions.NewTestIOStreamsDiscard(), 266 | svcPort: "https", 267 | }, 268 | &v1.Service{ 269 | ObjectMeta: metav1.ObjectMeta{ 270 | Name: "nginx", 271 | Namespace: "default", 272 | }, 273 | Spec: v1.ServiceSpec{ 274 | Ports: []v1.ServicePort{ 275 | { 276 | Name: "https", 277 | Port: 443, 278 | }, 279 | { 280 | Name: "metrics", 281 | Port: 10254, 282 | }, 283 | }, 284 | }, 285 | }, 286 | "/api/v1/namespaces/default/services/https:nginx:https/proxy", 287 | "", 288 | }, 289 | { 290 | "specifying port by service port number", 291 | &OpenServiceOptions{ 292 | IOStreams: genericclioptions.NewTestIOStreamsDiscard(), 293 | svcPort: "10254", 294 | }, 295 | &v1.Service{ 296 | ObjectMeta: metav1.ObjectMeta{ 297 | Name: "nginx", 298 | Namespace: "default", 299 | }, 300 | Spec: v1.ServiceSpec{ 301 | Ports: []v1.ServicePort{ 302 | { 303 | Name: "https", 304 | Port: 443, 305 | }, 306 | { 307 | Name: "metrics", 308 | Port: 10254, 309 | }, 310 | }, 311 | }, 312 | }, 313 | "/api/v1/namespaces/default/services/nginx:metrics/proxy", 314 | "", 315 | }, 316 | { 317 | "no port name with service port number", 318 | &OpenServiceOptions{ 319 | IOStreams: genericclioptions.NewTestIOStreamsDiscard(), 320 | svcPort: "10254", 321 | }, 322 | &v1.Service{ 323 | ObjectMeta: metav1.ObjectMeta{ 324 | Name: "nginx", 325 | Namespace: "default", 326 | }, 327 | Spec: v1.ServiceSpec{ 328 | Ports: []v1.ServicePort{ 329 | { 330 | Port: 10254, 331 | }, 332 | }, 333 | }, 334 | }, 335 | "/api/v1/namespaces/default/services/nginx/proxy", 336 | "", 337 | }, 338 | } 339 | 340 | for _, tt := range tests { 341 | t.Run(tt.name, func(t *testing.T) { 342 | proxyPath, err := tt.o.getServiceProxyPath(tt.svc) 343 | if err != nil { 344 | assert.Equal(t, tt.err, err.Error()) 345 | } 346 | assert.Equal(t, tt.proxyPath, proxyPath) 347 | }) 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /pkg/cmd/open-svc.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "net/http" 8 | "net/http/httputil" 9 | "net/url" 10 | "os" 11 | "os/signal" 12 | "strconv" 13 | "time" 14 | 15 | "github.com/pkg/browser" 16 | "github.com/spf13/cobra" 17 | "github.com/superbrothers/kubectl-open-svc-plugin/pkg/utils" 18 | v1 "k8s.io/api/core/v1" 19 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 20 | utilnet "k8s.io/apimachinery/pkg/util/net" 21 | "k8s.io/cli-runtime/pkg/genericclioptions" 22 | "k8s.io/client-go/kubernetes" 23 | "k8s.io/klog/v2" 24 | "k8s.io/kubectl/pkg/util/templates" 25 | 26 | "k8s.io/kubectl/pkg/proxy" 27 | // Initialize all known client auth plugins. 28 | _ "k8s.io/client-go/plugin/pkg/client/auth" 29 | ) 30 | 31 | var ( 32 | defaultPort = 8001 33 | defaultAddress = "127.0.0.1" 34 | defaultKeepalive = 0 * time.Second 35 | defaultScheme = "" 36 | 37 | schemeTypes = map[string]interface{}{ 38 | "": nil, 39 | "http": nil, 40 | "https": nil, 41 | } 42 | 43 | openServiceLong = templates.LongDesc(` 44 | Open the Kubernetes URL(s) for the specified service in your browser 45 | through a local proxy server. 46 | `) 47 | openServiceExample = templates.Examples(` 48 | # Open service/kubernetes-dashboard in namespace/kube-system 49 | kubectl open-svc kubernetes-dashboard -n kube-system 50 | 51 | # Open port "http-monitoring" of service/istiod in namespace/istio-system 52 | kubectl open-svc istiod -n istio-system --svc-port http-monitoring 53 | 54 | # Open port 15014 of service/istiod in namespace/istio-system 55 | kubectl open-svc istiod -n istio-system --svc-port 15014 56 | 57 | # Use "https" scheme with --scheme option for connections between the apiserver 58 | # and service/rook-ceph-mgr-dashboard in namespace/rook-ceph 59 | kubectl open-svc rook-ceph-mgr-dashboard -n rook-ceph --scheme https 60 | `) 61 | ) 62 | 63 | // OpenServiceOptions provides information required to open the service in the 64 | // browser 65 | type OpenServiceOptions struct { 66 | configFlags *genericclioptions.ConfigFlags 67 | 68 | args []string 69 | port int 70 | svcPort string 71 | address string 72 | keepalive time.Duration 73 | scheme string 74 | 75 | genericclioptions.IOStreams 76 | } 77 | 78 | // NewOpenServiceOptions provides an instance of OpenServiceOptions with 79 | // default values 80 | func NewOpenServiceOptions(streams genericclioptions.IOStreams) *OpenServiceOptions { 81 | return &OpenServiceOptions{ 82 | configFlags: genericclioptions.NewConfigFlags(true), 83 | 84 | port: defaultPort, 85 | address: defaultAddress, 86 | keepalive: defaultKeepalive, 87 | scheme: defaultScheme, 88 | 89 | IOStreams: streams, 90 | } 91 | } 92 | 93 | // NewCmdOpenService provides a cobra command wrapping OpenServiceOptions 94 | func NewCmdOpenService(streams genericclioptions.IOStreams) *cobra.Command { 95 | o := NewOpenServiceOptions(streams) 96 | 97 | cmd := &cobra.Command{ 98 | Use: fmt.Sprintf("kubectl open-svc SERVICE [--port=%d] [--address=%s] [--keepalive=%d]", defaultPort, defaultAddress, defaultKeepalive), 99 | Short: "Open the Kubernetes URL(s) for the specified service in your browser.", 100 | Long: openServiceLong, 101 | Example: openServiceExample, 102 | RunE: func(c *cobra.Command, args []string) error { 103 | if err := o.Complete(c, args); err != nil { 104 | return err 105 | } 106 | if err := o.Validate(); err != nil { 107 | return err 108 | } 109 | c.SilenceUsage = true 110 | if err := o.Run(); err != nil { 111 | return err 112 | } 113 | 114 | return nil 115 | }, 116 | } 117 | 118 | cmd.Flags().IntVarP(&o.port, "port", "p", o.port, "The port on which to run the proxy. Set to 0 to pick a random port.") 119 | cmd.Flags().StringVar(&o.svcPort, "svc-port", o.svcPort, "The service port name or number. default is empty and uses the first port") 120 | cmd.Flags().StringVar(&o.address, "address", o.address, "The IP address on which to serve on.") 121 | cmd.Flags().DurationVar(&o.keepalive, "keepalive", o.keepalive, "keepalive specifies the keep-alive period for an active network connection. Set to 0 to disable keepalive.") 122 | cmd.Flags().StringVar(&o.scheme, "scheme", o.scheme, `The scheme for connections between the apiserver and the service. It must be "http" or "https" if specfied.`) 123 | o.configFlags.AddFlags(cmd.Flags()) 124 | 125 | // add the klog flags 126 | cmd.PersistentFlags().AddGoFlagSet(flag.CommandLine) 127 | 128 | return cmd 129 | } 130 | 131 | // Complete sets all information required for opening the service 132 | func (o *OpenServiceOptions) Complete(cmd *cobra.Command, args []string) error { 133 | o.args = args 134 | 135 | return nil 136 | } 137 | 138 | // Validate ensures that all required arguments and flag values are provided 139 | func (o *OpenServiceOptions) Validate() error { 140 | if len(o.args) != 1 { 141 | return fmt.Errorf("exactly one SERVICE is required, got %d", len(o.args)) 142 | } 143 | 144 | if _, ok := schemeTypes[o.scheme]; !ok { 145 | return fmt.Errorf(`scheme must be "http" or "https" if specified`) 146 | } 147 | 148 | return nil 149 | } 150 | 151 | // Run opens the service in the browser 152 | func (o *OpenServiceOptions) Run() error { 153 | serviceName := o.args[0] 154 | 155 | restConfig, err := o.configFlags.ToRESTConfig() 156 | if err != nil { 157 | return err 158 | } 159 | 160 | kubeConfig := o.configFlags.ToRawKubeConfigLoader() 161 | 162 | client := kubernetes.NewForConfigOrDie(restConfig) 163 | namespace, _, err := kubeConfig.Namespace() 164 | if err != nil { 165 | return err 166 | } 167 | 168 | service, err := client.CoreV1().Services(namespace).Get(context.TODO(), serviceName, metav1.GetOptions{}) 169 | if err != nil { 170 | return fmt.Errorf("Failed to get service/%s in namespace/%s: %v\n", serviceName, namespace, err) 171 | } 172 | 173 | proxyPath, err := o.getServiceProxyPath(service) 174 | if err != nil { 175 | return err 176 | } 177 | 178 | server, err := proxy.NewServer("", "/", "", nil, restConfig, o.keepalive, false) 179 | if err != nil { 180 | return err 181 | } 182 | 183 | l, err := server.Listen("127.0.0.1", 0) 184 | if err != nil { 185 | return err 186 | } 187 | 188 | klog.V(4).Infof("Starting to serve kubectl proxy on %s\n", l.Addr().String()) 189 | 190 | go func() { 191 | klog.Fatal(server.ServeOnListener(l)) 192 | }() 193 | 194 | target, err := url.Parse("http://" + l.Addr().String() + proxyPath) 195 | if err != nil { 196 | return err 197 | } 198 | 199 | reverseProxy := httputil.NewSingleHostReverseProxy(target) 200 | reverseProxy.ModifyResponse = utils.StripModifierFunc(target.Path) 201 | srv := &http.Server{ 202 | Addr: o.getListenAddr(), 203 | Handler: reverseProxy, 204 | } 205 | 206 | fmt.Fprintf(o.Out, "Starting to serve on %s\n", o.getListenAddr()) 207 | 208 | go func() { 209 | klog.Fatal(srv.ListenAndServe()) 210 | }() 211 | 212 | fmt.Fprintf(o.Out, "Opening service/%s in the default browser...\n", serviceName) 213 | if err := browser.OpenURL(o.getListenURL()); err != nil { 214 | return err 215 | } 216 | 217 | quit := make(chan os.Signal, 1) 218 | defer close(quit) 219 | 220 | signal.Notify(quit, os.Interrupt) 221 | <-quit 222 | 223 | return nil 224 | } 225 | 226 | func (o *OpenServiceOptions) getListenAddr() string { 227 | return fmt.Sprintf("%s:%d", o.address, o.port) 228 | } 229 | 230 | func (o *OpenServiceOptions) getListenURL() string { 231 | return "http://" + o.getListenAddr() 232 | } 233 | 234 | func (o *OpenServiceOptions) getServiceProxyPath(svc *v1.Service) (string, error) { 235 | l := len(svc.Spec.Ports) 236 | 237 | if l == 0 { 238 | return "", fmt.Errorf("Looks like service/%s is a headless service", svc.GetName()) 239 | } 240 | 241 | var port *v1.ServicePort 242 | 243 | if o.svcPort == "" { 244 | port = &svc.Spec.Ports[0] 245 | 246 | if l > 1 { 247 | fmt.Fprintf(o.ErrOut, "service/%s has %d ports, defaulting port %d. You can use the another port with --svc-port flag.\n", svc.GetName(), l, port.Port) 248 | } 249 | } else { 250 | var isMatchingPort func(p v1.ServicePort) bool 251 | 252 | portNumber, err := strconv.ParseInt(o.svcPort, 10, 32) 253 | if err == nil { 254 | klog.V(4).Infof("treat svc-port %q as a port number", o.svcPort) 255 | isMatchingPort = func(p v1.ServicePort) bool { 256 | return p.Port == int32(portNumber) 257 | } 258 | } else { 259 | klog.V(4).Infof("treat svc-port %q as a port name", o.svcPort) 260 | isMatchingPort = func(p v1.ServicePort) bool { 261 | return p.Name == o.svcPort 262 | } 263 | } 264 | 265 | for _, p := range svc.Spec.Ports { 266 | if isMatchingPort(p) { 267 | port = &p 268 | break 269 | } 270 | } 271 | 272 | if port == nil { 273 | return "", fmt.Errorf("port %q not found in service/%s", o.svcPort, svc.GetName()) 274 | } 275 | } 276 | 277 | scheme := o.scheme 278 | if scheme == "" { 279 | // guess if the scheme is https 280 | if port.Name == "https" || port.Port == 443 { 281 | scheme = "https" 282 | } 283 | } 284 | 285 | // format is :: 286 | name := utilnet.JoinSchemeNamePort(scheme, svc.GetName(), port.Name) 287 | return fmt.Sprintf("/api/v1/namespaces/%s/services/%s/proxy", svc.GetNamespace(), name), nil 288 | } 289 | -------------------------------------------------------------------------------- /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/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= 4 | github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= 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/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 8 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 9 | github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 10 | github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= 11 | github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= 12 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 14 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 15 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/emicklei/go-restful/v3 v3.12.1 h1:PJMDIM/ak7btuL8Ex0iYET9hxM3CI2sjZtzpL63nKAU= 17 | github.com/emicklei/go-restful/v3 v3.12.1/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 18 | github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= 19 | github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= 20 | github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= 21 | github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 22 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 23 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 24 | github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= 25 | github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= 26 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 27 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 28 | github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= 29 | github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= 30 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 31 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 32 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 33 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 34 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= 35 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= 36 | github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= 37 | github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= 38 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 39 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 40 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 41 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 42 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 43 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 44 | github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= 45 | github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 46 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 47 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 48 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 49 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 50 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 51 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 52 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= 53 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 54 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= 55 | github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= 56 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 57 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 58 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 59 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 60 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 61 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 62 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 63 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 64 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 65 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 66 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 67 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 68 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= 69 | github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= 70 | github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= 71 | github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 72 | github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= 73 | github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= 74 | github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= 75 | github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= 76 | github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= 77 | github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= 78 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 79 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 80 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 81 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 82 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 83 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 84 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 85 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 86 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 87 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 88 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 89 | github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= 90 | github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 91 | github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= 92 | github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 93 | github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= 94 | github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= 95 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= 96 | github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= 97 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 98 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 99 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 100 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 101 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 102 | github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= 103 | github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= 104 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 105 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 106 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 107 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 108 | github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= 109 | github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= 110 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 111 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 112 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 113 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 114 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 115 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 116 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 117 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 118 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 119 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 120 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 121 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 122 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 123 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 124 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 125 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 126 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 127 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 128 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 129 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 130 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 131 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 132 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 133 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 134 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 135 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 136 | golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= 137 | golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= 138 | golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE= 139 | golang.org/x/oauth2 v0.24.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= 140 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 141 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 143 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= 144 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 145 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 146 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 148 | golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 149 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 150 | golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= 151 | golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 152 | golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= 153 | golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= 154 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 155 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 156 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= 157 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= 158 | golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= 159 | golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= 160 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 161 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 162 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 163 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 164 | golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= 165 | golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= 166 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 167 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 168 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 169 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 170 | google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= 171 | google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= 172 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 173 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 174 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 175 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 176 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 177 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 178 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 179 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 180 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 181 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 182 | k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= 183 | k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= 184 | k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= 185 | k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= 186 | k8s.io/cli-runtime v0.32.0 h1:dP+OZqs7zHPpGQMCGAhectbHU2SNCuZtIimRKTv2T1c= 187 | k8s.io/cli-runtime v0.32.0/go.mod h1:Mai8ht2+esoDRK5hr861KRy6z0zHsSTYttNVJXgP3YQ= 188 | k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= 189 | k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= 190 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 191 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 192 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7 h1:hcha5B1kVACrLujCKLbr8XWMxCxzQx42DY8QKYJrDLg= 193 | k8s.io/kube-openapi v0.0.0-20241212222426-2c72e554b1e7/go.mod h1:GewRfANuJ70iYzvn+i4lezLDAFzvjxZYK1gn1lWcfas= 194 | k8s.io/kubectl v0.32.0 h1:rpxl+ng9qeG79YA4Em9tLSfX0G8W0vfaiPVrc/WR7Xw= 195 | k8s.io/kubectl v0.32.0/go.mod h1:qIjSX+QgPQUgdy8ps6eKsYNF+YmFOAO3WygfucIqFiE= 196 | k8s.io/utils v0.0.0-20241210054802-24370beab758 h1:sdbE21q2nlQtFh65saZY+rRM6x6aJJI8IUa1AmH/qa0= 197 | k8s.io/utils v0.0.0-20241210054802-24370beab758/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 198 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 199 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 200 | sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= 201 | sigs.k8s.io/kustomize/api v0.18.0/go.mod h1:f8isXnX+8b+SGLHQ6yO4JG1rdkZlvhaCf/uZbLVMb0U= 202 | sigs.k8s.io/kustomize/kyaml v0.18.1 h1:WvBo56Wzw3fjS+7vBjN6TeivvpbW9GmRaWZ9CIVmt4E= 203 | sigs.k8s.io/kustomize/kyaml v0.18.1/go.mod h1:C3L2BFVU1jgcddNBE1TxuVLgS46TjObMwW5FT9FcjYo= 204 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0 h1:nbCitCK2hfnhyiKo6uf2HxUPTCodY6Qaf85SbDIaMBk= 205 | sigs.k8s.io/structured-merge-diff/v4 v4.5.0/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= 206 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 207 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 208 | --------------------------------------------------------------------------------