Compare commits

..

6 Commits

Author SHA1 Message Date
Kubernetes Publisher 2b3fd9b306 Update dependencies to v0.31.11 tag 2025-07-16 03:47:31 +00:00
Kubernetes Publisher 370dc1d47d Merge pull request #129519 from kishen-v/automated-cherry-pick-of-#127422-upstream-release-1.31
Automated cherry pick of #127422: Fix Go vet errors for master golang

Kubernetes-commit: d7fc7e30cb630dab3351480c806c58a8c480156f
2025-01-22 22:02:31 +00:00
Abhishek Kr Srivastav d44c3e5538 Fix Go vet errors for master golang
Co-authored-by: Rajalakshmi-Girish <rajalakshmi.girish1@ibm.com>
Co-authored-by: Abhishek Kr Srivastav <Abhishek.kr.srivastav@ibm.com>

Kubernetes-commit: 9d10ddb0608aa20ce287c89be879f888f5823cf9
2024-09-12 18:15:22 +05:30
Kubernetes Publisher 5ba0fa9e7b Merge remote-tracking branch 'origin/master' into release-1.31
Kubernetes-commit: 57846e1f1f1618b1c0c8e49667c3633762d866ac
2024-08-12 21:22:43 +00:00
Maciej Szulik 07d386f255 wait: don't lowercase condition in --for argument
Kubernetes-commit: fad6c424ac9b69229e02f9d9058c8131005f86e2
2024-08-12 16:30:20 +02:00
carlory 8fc44bc846 Promote VolumeAttributesClass to beta
Kubernetes-commit: 0260c7d023551f85621049ca604a4fd5110ba0a9
2024-08-01 02:08:59 +00:00
183 changed files with 1168 additions and 10728 deletions

View File

@ -1,8 +1,3 @@
> ⚠️ **This is an automatically published [staged repository](https://git.k8s.io/kubernetes/staging#external-repository-staging-area) for Kubernetes**.
> Pull requests, should be made to the main Kubernetes repository: [https://github.com/kubernetes/kubernetes](https://github.com/kubernetes/kubernetes).
> This repository is read-only for importing, and not used for direct contributions.
> See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.
# Kubectl # Kubectl
![kubectl logo](./images/kubectl-logo-medium.png) ![kubectl logo](./images/kubectl-logo-medium.png)

View File

@ -10,7 +10,7 @@
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE # DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
# INSTRUCTIONS AT https://kubernetes.io/security/ # INSTRUCTIONS AT https://kubernetes.io/security/
ardaguclu
eddiezane eddiezane
mpuckett159 KnVerey
natasha41575
soltysh soltysh

2
doc.go
View File

@ -14,4 +14,4 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package kubectl package kubectl // import "k8s.io/kubectl"

113
go.mod
View File

@ -2,93 +2,96 @@
module k8s.io/kubectl module k8s.io/kubectl
go 1.24.0 go 1.22.0
godebug default=go1.24
require ( require (
github.com/MakeNowJust/heredoc v1.0.0 github.com/MakeNowJust/heredoc v1.0.0
github.com/chai2010/gettext-go v1.0.2 github.com/chai2010/gettext-go v1.0.2
github.com/distribution/reference v0.6.0 github.com/daviddengcn/go-colortext v1.0.0
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f github.com/distribution/reference v0.5.0
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d
github.com/fatih/camelcase v1.0.0 github.com/fatih/camelcase v1.0.0
github.com/go-openapi/jsonreference v0.20.2 github.com/go-openapi/jsonreference v0.20.2
github.com/google/gnostic-models v0.7.0 github.com/google/gnostic-models v0.6.8
github.com/google/go-cmp v0.7.0 github.com/google/go-cmp v0.6.0
github.com/jonboulle/clockwork v0.5.0 github.com/jonboulle/clockwork v0.2.2
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
github.com/lithammer/dedent v1.1.0 github.com/lithammer/dedent v1.1.0
github.com/mitchellh/go-wordwrap v1.0.1 github.com/mitchellh/go-wordwrap v1.0.1
github.com/moby/term v0.5.0 github.com/moby/term v0.5.0
github.com/onsi/ginkgo/v2 v2.21.0 github.com/onsi/ginkgo/v2 v2.19.0
github.com/onsi/gomega v1.35.1 github.com/onsi/gomega v1.33.1
github.com/pkg/errors v0.9.1
github.com/russross/blackfriday/v2 v2.1.0 github.com/russross/blackfriday/v2 v2.1.0
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.9.0
go.yaml.in/yaml/v2 v2.4.2 golang.org/x/sys v0.21.0
golang.org/x/sys v0.33.0 gopkg.in/evanphx/json-patch.v4 v4.12.0
gopkg.in/evanphx/json-patch.v4 v4.13.0 gopkg.in/yaml.v2 v2.4.0
k8s.io/api v0.0.0-20250830163657-b903cd06836a k8s.io/api v0.31.11
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4 k8s.io/apimachinery v0.31.11
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e k8s.io/cli-runtime v0.31.11
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97 k8s.io/client-go v0.31.11
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7 k8s.io/component-base v0.31.11
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc k8s.io/component-helpers v0.31.11
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.130.1
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd k8s.io/metrics v0.31.11
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 sigs.k8s.io/kustomize/kustomize/v5 v5.4.2
sigs.k8s.io/kustomize/kyaml v0.20.1 sigs.k8s.io/kustomize/kyaml v0.17.1
sigs.k8s.io/randfill v1.0.0 sigs.k8s.io/structured-merge-diff/v4 v4.4.1
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 sigs.k8s.io/yaml v1.4.0
sigs.k8s.io/yaml v1.6.0
) )
require ( require (
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-errors/errors v1.4.2 // indirect github.com/go-errors/errors v1.4.2 // indirect
github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/swag v0.23.0 // indirect github.com/go-openapi/swag v0.22.4 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/btree v1.1.3 // indirect github.com/golang/protobuf v1.5.4 // indirect
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect github.com/google/btree v1.0.1 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect github.com/gorilla/websocket v1.5.0 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/moby/spdystream v0.5.0 // indirect github.com/moby/spdystream v0.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect
golang.org/x/net v0.40.0 // indirect golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.14.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/term v0.32.0 // indirect golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.25.0 // indirect golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.9.0 // indirect golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.26.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/protobuf v1.36.6 // indirect google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/api v0.17.2 // indirect
) )

291
go.sum
View File

@ -1,65 +1,106 @@
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= github.com/daviddengcn/go-colortext v1.0.0 h1:ANqDyC0ys6qCSvuEK7l3g5RaehL/Xck9EX8ATG8oKsE=
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/daviddengcn/go-colortext v1.0.0/go.mod h1:zDqEI5NVUop5QPpVJUxE9UO10hRnmkD5G4Pmri9+m4c=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0=
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM=
github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4=
github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8=
github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc=
github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k=
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.22.4 h1:QLMzNJnMGPRNDCbySlcj1x01tzU8/9LTTL9hZZZogBU=
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-openapi/swag v0.22.4/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golangplus/bytes v0.0.0-20160111154220-45c989fe5450/go.mod h1:Bk6SMAONeMXrxql8uvOKuAZSu8aM5RUGv+1C6IJaEho=
github.com/golangplus/bytes v1.0.0/go.mod h1:AdRaCFwmc/00ZzELMWb01soso6W1R/++O1XL80yAn+A=
github.com/golangplus/fmt v1.0.0/go.mod h1:zpM0OfbMCjPtd2qkTD/jX2MgiFCqklhSUFyDW44gVQE=
github.com/golangplus/testing v1.0.0 h1:+ZeeiKZENNOMkTTELoSySazi+XaEhVO0mb+eanrSEUQ=
github.com/golangplus/testing v1.0.0/go.mod h1:ZDreixUV3YzhoVraIDyOzHrr76p6NUh6k/pPg/Q3gYA=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2Rrd27c3VGxi6a/6HNq8QmHRKM=
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ=
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@ -81,152 +122,190 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0=
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/spdystream v0.4.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8=
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0=
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus=
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA=
github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk=
github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca h1:VdD38733bfYv5tUZwEIskMM93VanwNIi5bIKnDrJdEY=
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca/go.mod h1:jxU+3+j+71eXOW14274+SmmuW82qJzl6iZSeqEtTGds=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.40.0 h1:79Xs7wF06Gbdcg4kdCCIQArK11Z1hr5POQ6+fIYHNuY= golang.org/x/net v0.26.0 h1:soB7SVo0PWrY4vPW/+ay0jKDNScG2X9wFeYlXIvJsOQ=
golang.org/x/net v0.40.0/go.mod h1:y0hY0exeL2Pku80/zKK7tpntoX23cqL3Oa6njdgRtds= golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4= golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA= golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4=
gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
k8s.io/api v0.0.0-20250830163657-b903cd06836a h1:qS+abmAu2zbGFkbN1vA7LKS07jsXBN1BvTFXFvaGOLI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.0.0-20250830163657-b903cd06836a/go.mod h1:/IpJMZ4ur2JBuX+kkBc115bnq09sFfUnbuFNrdEe5yc= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4 h1:ObQoOWhkcPbMnU7PIHT2pkO2wK66CcBn6vD+77CidHM= k8s.io/api v0.31.11 h1:jmrs+NBmdyaYMOIhAmi2g2eemugqiMwRSKtdpk0FAFc=
k8s.io/apimachinery v0.0.0-20250830163350-eb2c6e0d1ec4/go.mod h1:fDax9lidUgmNSmBlzUrSISURQmHpeyamBbKX9jGbJ3g= k8s.io/api v0.31.11/go.mod h1:t1/N5KEcY/DCwEzCbtCoJeJt4b00phLCRVgM11Ijb6o=
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e h1:dovWIA2PM0UrJrZdUBV8uy5pExliSBXSFeL0bI6IX6E= k8s.io/apimachinery v0.31.11 h1:QsYPEVrKOTBtuzj0gmfwVkM7RwPB+IbOUceBAuK4ips=
k8s.io/cli-runtime v0.0.0-20250830171832-3e7914c55f7e/go.mod h1:/yp9r2rD6AV7MYM/gmb55/6LttRuURzjhgqbfiFQ0Rg= k8s.io/apimachinery v0.31.11/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97 h1:6ks7Y8CNm05xZ6eyE0db5IDP54PIyRM3aZhpflG55hI= k8s.io/cli-runtime v0.31.11 h1:ebbX7cQytFTIoxA8zHw8LxLdD7SNa1jv1Rlf+rDgtcw=
k8s.io/client-go v0.0.0-20250830164107-2a8d855d0d97/go.mod h1:xTpANYjBhGsmpO7Gdw8kMt3yQfciVwyRhbqcq77qwyI= k8s.io/cli-runtime v0.31.11/go.mod h1:MXt1GlyXuxr/QHmlhZ0D9hZAHt8gU4ZFcBMfog4LOnA=
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7 h1:JQ/dO+EIXNQ+y3Vlez6PC6r7T+0JvNQWrBx6CD3jKls= k8s.io/client-go v0.31.11 h1:uhc+e8vfsGtm9xVoIs0+ajGW0mWNW64VSjej3bXF+WA=
k8s.io/component-base v0.0.0-20250830165319-75dce96cfea7/go.mod h1:ZZMk2BFRSF/kI9Y5qKmvTk4SJM654XsQ5eJ9cP7mrhw= k8s.io/client-go v0.31.11/go.mod h1:y11StjQTOP0nd/o/RHxqwmdr1CWdBvnpvGs9k7MWYnA=
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc h1:hEo6RVciD0e0QvtMBgwG9a7fFWb/vkx0Jvw/iQ5i+lQ= k8s.io/component-base v0.31.11 h1:uPQCr7SJGhk/oFp4YmxG5ctHrDocZRGSOaheFrbAvoM=
k8s.io/component-helpers v0.0.0-20250830165448-f84446610ecc/go.mod h1:TmnJ20kJrkgbEqHgHoUxoTksgIUJNCWtv/QM+yBqCF0= k8s.io/component-base v0.31.11/go.mod h1:huE8dXdht4KoOPo8CSb0ifLjpGUc/dh+cmNGplRGZno=
k8s.io/component-helpers v0.31.11 h1:A3UbO910SOVoUmhkbGX8b3V4k5Ed2a02DtNgyTHBtss=
k8s.io/component-helpers v0.31.11/go.mod h1:+APbittkpj1TRGnNiQFUfJvq7zJ0ekvDu7IX+FZpPRg=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd h1:GNGeQ8o/xw8TwhavcXmWrGKHd0ez5TRx8qRqyncbFH4= k8s.io/metrics v0.31.11 h1:aN1Oj9UvJXma2ZDaSVG3XCDo8n0/77cNVNfbYooXAR0=
k8s.io/metrics v0.0.0-20250830171643-3daf20f585bd/go.mod h1:OYYW2zJf2TAQHmxQwQgSsP/i40dCZig4RQkw3o3vFPE= k8s.io/metrics v0.31.11/go.mod h1:714ZBduoufaqgT/56SgWe9Dnr0ClrO5wFxgNcF+32f8=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g=
sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= sigs.k8s.io/kustomize/api v0.17.2/go.mod h1:UWTz9Ct+MvoeQsHcJ5e+vziRRkwimm3HytpZgIYqye0=
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1 h1:sYJsarwy/SDJfjjLMUqwFDGPwzUtMOQ1i1Ed49+XSbw= sigs.k8s.io/kustomize/kustomize/v5 v5.4.2 h1:9Zl5Gqg3XMdBEvkR54pVLCBj7FVO7W+VPNDDEzD6AyE=
sigs.k8s.io/kustomize/kustomize/v5 v5.7.1/go.mod h1:+5/SrBcJ4agx1SJknGuR/c9thwRSKLxnKoI5BzXFaLU= sigs.k8s.io/kustomize/kustomize/v5 v5.4.2/go.mod h1:5ypfJVYlPb2MKKeoGknVLxvHemDlQT+szI4+KOhnD6k=
sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= sigs.k8s.io/kustomize/kyaml v0.17.1 h1:TnxYQxFXzbmNG6gOINgGWQt09GghzgTP6mIurOgrLCQ=
sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= sigs.k8s.io/kustomize/kyaml v0.17.1/go.mod h1:9V0mCjIEYjlXuCdYsSXvyoy2BTsLESH7TlGV81S282U=
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4=
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=

View File

@ -23,11 +23,10 @@ import (
"strings" "strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/genericiooptions"
@ -62,10 +61,12 @@ var (
// APIResourceOptions is the start of the data required to perform the operation. // APIResourceOptions is the start of the data required to perform the operation.
// As new fields are added, add them here instead of referencing the cmd.Flags() // As new fields are added, add them here instead of referencing the cmd.Flags()
type APIResourceOptions struct { type APIResourceOptions struct {
Output string
SortBy string SortBy string
APIGroup string APIGroup string
Namespaced bool Namespaced bool
Verbs []string Verbs []string
NoHeaders bool
Cached bool Cached bool
Categories []string Categories []string
@ -75,8 +76,13 @@ type APIResourceOptions struct {
discoveryClient discovery.CachedDiscoveryInterface discoveryClient discovery.CachedDiscoveryInterface
genericiooptions.IOStreams genericiooptions.IOStreams
PrintFlags *PrintFlags }
PrintObj printers.ResourcePrinterFunc
// groupResource contains the APIGroup and APIResource
type groupResource struct {
APIGroup string
APIGroupVersion string
APIResource metav1.APIResource
} }
// NewAPIResourceOptions creates the options for APIResource // NewAPIResourceOptions creates the options for APIResource
@ -84,7 +90,6 @@ func NewAPIResourceOptions(ioStreams genericiooptions.IOStreams) *APIResourceOpt
return &APIResourceOptions{ return &APIResourceOptions{
IOStreams: ioStreams, IOStreams: ioStreams,
Namespaced: true, Namespaced: true,
PrintFlags: NewPrintFlags(),
} }
} }
@ -104,7 +109,8 @@ func NewCmdAPIResources(restClientGetter genericclioptions.RESTClientGetter, ioS
}, },
} }
o.PrintFlags.AddFlags(cmd) cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, `Output format. One of: (wide, name).`)
cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.") cmd.Flags().StringVar(&o.APIGroup, "api-group", o.APIGroup, "Limit to resources in the specified API group.")
cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.") cmd.Flags().BoolVar(&o.Namespaced, "namespaced", o.Namespaced, "If false, non-namespaced resources will be returned, otherwise returning namespaced resources by default.")
@ -117,7 +123,11 @@ func NewCmdAPIResources(restClientGetter genericclioptions.RESTClientGetter, ioS
// Validate checks to the APIResourceOptions to see if there is sufficient information run the command // Validate checks to the APIResourceOptions to see if there is sufficient information run the command
func (o *APIResourceOptions) Validate() error { func (o *APIResourceOptions) Validate() error {
supportedSortTypes := sets.New[string]("", "name", "kind") supportedOutputTypes := sets.NewString("", "wide", "name")
if !supportedOutputTypes.Has(o.Output) {
return fmt.Errorf("--output %v is not available", o.Output)
}
supportedSortTypes := sets.NewString("", "name", "kind")
if len(o.SortBy) > 0 { if len(o.SortBy) > 0 {
if !supportedSortTypes.Has(o.SortBy) { if !supportedSortTypes.Has(o.SortBy) {
return fmt.Errorf("--sort-by accepts only name or kind") return fmt.Errorf("--sort-by accepts only name or kind")
@ -141,28 +151,6 @@ func (o *APIResourceOptions) Complete(restClientGetter genericclioptions.RESTCli
o.groupChanged = cmd.Flags().Changed("api-group") o.groupChanged = cmd.Flags().Changed("api-group")
o.nsChanged = cmd.Flags().Changed("namespaced") o.nsChanged = cmd.Flags().Changed("namespaced")
var printer printers.ResourcePrinter
if o.PrintFlags.OutputFormat != nil {
printer, err = o.PrintFlags.ToPrinter()
if err != nil {
return err
}
o.PrintObj = func(object runtime.Object, out io.Writer) error {
errs := []error{}
if !*o.PrintFlags.NoHeaders &&
(o.PrintFlags.OutputFormat == nil || *o.PrintFlags.OutputFormat == "" || *o.PrintFlags.OutputFormat == "wide") {
if err = printContextHeaders(out, *o.PrintFlags.OutputFormat); err != nil {
errs = append(errs, err)
}
}
if err := printer.PrintObj(object, out); err != nil {
errs = append(errs, err)
}
return utilerrors.NewAggregate(errs)
}
}
return nil return nil
} }
@ -182,7 +170,7 @@ func (o *APIResourceOptions) RunAPIResources() error {
errs = append(errs, err) errs = append(errs, err)
} }
var allResources []*metav1.APIResourceList resources := []groupResource{}
for _, list := range lists { for _, list := range lists {
if len(list.APIResources) == 0 { if len(list.APIResources) == 0 {
@ -192,14 +180,6 @@ func (o *APIResourceOptions) RunAPIResources() error {
if err != nil { if err != nil {
continue continue
} }
apiList := &metav1.APIResourceList{
TypeMeta: metav1.TypeMeta{
Kind: "APIResourceList",
APIVersion: "v1",
},
GroupVersion: gv.String(),
}
var apiResources []metav1.APIResource
for _, resource := range list.APIResources { for _, resource := range list.APIResources {
if len(resource.Verbs) == 0 { if len(resource.Verbs) == 0 {
continue continue
@ -213,39 +193,65 @@ func (o *APIResourceOptions) RunAPIResources() error {
continue continue
} }
// filter to resources that support the specified verbs // filter to resources that support the specified verbs
if len(o.Verbs) > 0 && !sets.New[string](resource.Verbs...).HasAll(o.Verbs...) { if len(o.Verbs) > 0 && !sets.NewString(resource.Verbs...).HasAll(o.Verbs...) {
continue continue
} }
// filter to resources that belong to the specified categories // filter to resources that belong to the specified categories
if len(o.Categories) > 0 && !sets.New[string](resource.Categories...).HasAll(o.Categories...) { if len(o.Categories) > 0 && !sets.NewString(resource.Categories...).HasAll(o.Categories...) {
continue continue
} }
// set these because we display a concatenation of these two values under APIVERSION column of human-readable output resources = append(resources, groupResource{
resource.Group = gv.Group APIGroup: gv.Group,
resource.Version = gv.Version APIGroupVersion: gv.String(),
apiResources = append(apiResources, resource) APIResource: resource,
})
} }
apiList.APIResources = apiResources
allResources = append(allResources, apiList)
} }
flatList := &metav1.APIResourceList{ if o.NoHeaders == false && o.Output != "name" {
TypeMeta: metav1.TypeMeta{ if err = printContextHeaders(w, o.Output); err != nil {
APIVersion: allResources[0].APIVersion, return err
Kind: allResources[0].Kind,
},
} }
for _, resource := range allResources {
flatList.APIResources = append(flatList.APIResources, resource.APIResources...)
} }
sort.Stable(sortableResource{flatList.APIResources, o.SortBy}) sort.Stable(sortableResource{resources, o.SortBy})
for _, r := range resources {
err = o.PrintObj(flatList, w) switch o.Output {
if err != nil { case "name":
name := r.APIResource.Name
if len(r.APIGroup) > 0 {
name += "." + r.APIGroup
}
if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
errs = append(errs, err) errs = append(errs, err)
} }
return utilerrors.NewAggregate(errs) case "wide":
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\t%v\n",
r.APIResource.Name,
strings.Join(r.APIResource.ShortNames, ","),
r.APIGroupVersion,
r.APIResource.Namespaced,
r.APIResource.Kind,
strings.Join(r.APIResource.Verbs, ","),
strings.Join(r.APIResource.Categories, ",")); err != nil {
errs = append(errs, err)
}
case "":
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
r.APIResource.Name,
strings.Join(r.APIResource.ShortNames, ","),
r.APIGroupVersion,
r.APIResource.Namespaced,
r.APIResource.Kind); err != nil {
errs = append(errs, err)
}
}
}
if len(errs) > 0 {
return errors.NewAggregate(errs)
}
return nil
} }
func printContextHeaders(out io.Writer, output string) error { func printContextHeaders(out io.Writer, output string) error {
@ -258,7 +264,7 @@ func printContextHeaders(out io.Writer, output string) error {
} }
type sortableResource struct { type sortableResource struct {
resources []metav1.APIResource resources []groupResource
sortBy string sortBy string
} }
@ -271,7 +277,7 @@ func (s sortableResource) Less(i, j int) bool {
if ret > 0 { if ret > 0 {
return false return false
} else if ret == 0 { } else if ret == 0 {
return strings.Compare(s.resources[i].Name, s.resources[j].Name) < 0 return strings.Compare(s.resources[i].APIResource.Name, s.resources[j].APIResource.Name) < 0
} }
return true return true
} }
@ -279,9 +285,9 @@ func (s sortableResource) Less(i, j int) bool {
func (s sortableResource) compareValues(i, j int) (string, string) { func (s sortableResource) compareValues(i, j int) (string, string) {
switch s.sortBy { switch s.sortBy {
case "name": case "name":
return s.resources[i].Name, s.resources[j].Name return s.resources[i].APIResource.Name, s.resources[j].APIResource.Name
case "kind": case "kind":
return s.resources[i].Kind, s.resources[j].Kind return s.resources[i].APIResource.Kind, s.resources[j].APIResource.Kind
} }
return s.resources[i].Group, s.resources[j].Group return s.resources[i].APIGroup, s.resources[j].APIGroup
} }

View File

@ -17,17 +17,13 @@ limitations under the License.
package apiresources package apiresources
import ( import (
"encoding/json"
"strings"
"testing" "testing"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/stretchr/testify/require"
apiequality "k8s.io/apimachinery/pkg/api/equality"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/genericiooptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing" cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
"sigs.k8s.io/yaml"
) )
func TestAPIResourcesComplete(t *testing.T) { func TestAPIResourcesComplete(t *testing.T) {
@ -52,16 +48,6 @@ See 'kubectl api-resources -h' for help and examples`
if err.Error() != expectedError { if err.Error() != expectedError {
t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError) t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError)
} }
*o.PrintFlags.OutputFormat = "foo"
err = o.Complete(tf, cmd, []string{})
if err == nil {
t.Fatalf("An error was expected but not returned")
}
expectedError = `unable to match a printer suitable for the output format "foo", allowed formats are:`
if !strings.HasPrefix(err.Error(), expectedError) {
t.Fatalf("Unexpected error: %v\n expected: %v", err, expectedError)
}
} }
func TestAPIResourcesValidate(t *testing.T) { func TestAPIResourcesValidate(t *testing.T) {
@ -75,6 +61,13 @@ func TestAPIResourcesValidate(t *testing.T) {
optionSetupFn: func(o *APIResourceOptions) {}, optionSetupFn: func(o *APIResourceOptions) {},
expectedError: "", expectedError: "",
}, },
{
name: "invalid output",
optionSetupFn: func(o *APIResourceOptions) {
o.Output = "foo"
},
expectedError: "--output foo is not available",
},
{ {
name: "invalid sort by", name: "invalid sort by",
optionSetupFn: func(o *APIResourceOptions) { optionSetupFn: func(o *APIResourceOptions) {
@ -329,92 +322,3 @@ bazzes b somegroup/v1 true Baz
}) })
} }
} }
// TestAPIResourcesRunJsonYaml is doing same thing as TestAPIResourcesRun but for JSON and YAML outputs
// A separate test function is created because we are using apieqaulity.Semantic.DeepEqual
// to check equality between input and output
func TestAPIResourcesRunJsonYaml(t *testing.T) {
dc := cmdtesting.NewFakeCachedDiscoveryClient()
tf := cmdtesting.NewTestFactory().WithDiscoveryClient(dc)
defer tf.Cleanup()
testCases := []struct {
name string
expectedInvalidations int
preferredResources []*v1.APIResourceList
}{
{
name: "one",
preferredResources: []*v1.APIResourceList{
{
GroupVersion: "v1",
APIResources: []v1.APIResource{
{
Name: "foos",
Namespaced: false,
Kind: "Foo",
Verbs: []string{"get", "list"},
ShortNames: []string{"f", "fo"},
Categories: []string{"some-category"},
},
},
},
},
},
{
name: "two",
preferredResources: []*v1.APIResourceList{
{
GroupVersion: "somegroup/v1",
APIResources: []v1.APIResource{
{
Name: "bazzes",
Namespaced: true,
Kind: "Baz",
Verbs: []string{"get", "list", "create", "delete"},
ShortNames: []string{"b"},
Categories: []string{"some-category", "another-category"},
},
},
},
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(tt *testing.T) {
dc.PreferredResources = tc.preferredResources
ioStreams, _, out, errOut := genericiooptions.NewTestIOStreams()
for _, v := range []string{"json", "yaml"} {
cmd := NewCmdAPIResources(tf, ioStreams)
err := cmd.Flags().Set("output", v)
require.NoError(tt, err)
cmd.Run(cmd, []string{})
if errOut.Len() > 0 {
t.Fatalf("unexpected error output: %s", errOut.String())
}
apiResourceList := v1.APIResourceList{}
switch v {
case "json":
err = json.Unmarshal(out.Bytes(), &apiResourceList)
case "yaml":
err = yaml.Unmarshal(out.Bytes(), &apiResourceList)
}
require.NoError(tt, err)
// this will undo custom value we add in RunAPIResources in the lines:
// resource.Group = gv.Group
// resource.Version = gv.Version
apiResourceList.GroupVersion = apiResourceList.APIResources[0].Group + "/" + apiResourceList.APIResources[0].Version
apiResourceList.APIResources[0].Version = ""
apiResourceList.APIResources[0].Group = ""
if !apiequality.Semantic.DeepEqual(tc.preferredResources[0].APIResources[0], apiResourceList.APIResources[0]) {
tt.Fatalf("expected output: [%v]\n, but got [%v]", tc.preferredResources[0].APIResources[0], apiResourceList.APIResources[0])
}
}
})
}
}

View File

@ -1,233 +0,0 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package apiresources
import (
"fmt"
"io"
"strings"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/printers"
)
type PrintFlags struct {
JSONYamlPrintFlags *genericclioptions.JSONYamlPrintFlags
NamePrintFlags NamePrintFlags
HumanReadableFlags HumanPrintFlags
NoHeaders *bool
OutputFormat *string
}
func NewPrintFlags() *PrintFlags {
outputFormat := ""
noHeaders := false
return &PrintFlags{
OutputFormat: &outputFormat,
NoHeaders: &noHeaders,
JSONYamlPrintFlags: genericclioptions.NewJSONYamlPrintFlags(),
NamePrintFlags: APIResourcesNewNamePrintFlags(),
HumanReadableFlags: APIResourcesHumanReadableFlags(),
}
}
func (f *PrintFlags) AddFlags(cmd *cobra.Command) {
f.JSONYamlPrintFlags.AddFlags(cmd)
f.HumanReadableFlags.AddFlags(cmd)
f.NamePrintFlags.AddFlags(cmd)
if f.OutputFormat != nil {
cmd.Flags().StringVarP(f.OutputFormat, "output", "o", *f.OutputFormat, fmt.Sprintf("Output format. One of: (%s).", strings.Join(f.AllowedFormats(), ", ")))
}
if f.NoHeaders != nil {
cmd.Flags().BoolVar(f.NoHeaders, "no-headers", *f.NoHeaders, "When using the default or custom-column output format, don't print headers (default print headers).")
}
}
// PrintOptions struct defines a struct for various print options
type PrintOptions struct {
SortBy *string
NoHeaders bool
Wide bool
}
type HumanPrintFlags struct {
SortBy *string
NoHeaders bool
}
func (f *HumanPrintFlags) AllowedFormats() []string {
return []string{"wide"}
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to human-readable printing to it
func (f *HumanPrintFlags) AddFlags(c *cobra.Command) {
if f.SortBy != nil {
c.Flags().StringVar(f.SortBy, "sort-by", *f.SortBy, "If non-empty, sort list types using this field specification. The field specification is expressed as a JSONPath expression (e.g. '{.metadata.name}'). The field in the API resource specified by this JSONPath expression must be an integer or a string.")
}
}
// ToPrinter receives an outputFormat and returns a printer capable of
// handling human-readable output.
func (f *HumanPrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
if len(outputFormat) > 0 && outputFormat != "wide" {
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, AllowedFormats: f.AllowedFormats()}
}
p := HumanReadablePrinter{
options: PrintOptions{
NoHeaders: f.NoHeaders,
Wide: outputFormat == "wide",
},
}
return p, nil
}
type HumanReadablePrinter struct {
options PrintOptions
}
func (f HumanReadablePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
flatList, ok := obj.(*metav1.APIResourceList)
if !ok {
return fmt.Errorf("object is not a APIResourceList")
}
var errs []error
for _, r := range flatList.APIResources {
gv, err := schema.ParseGroupVersion(strings.Join([]string{r.Group, r.Version}, "/"))
if err != nil {
errs = append(errs, err)
continue
}
if f.options.Wide {
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\t%v\t%v\n",
r.Name,
strings.Join(r.ShortNames, ","),
gv.String(),
r.Namespaced,
r.Kind,
strings.Join(r.Verbs, ","),
strings.Join(r.Categories, ",")); err != nil {
errs = append(errs, err)
}
continue
}
if _, err := fmt.Fprintf(w, "%s\t%s\t%s\t%v\t%s\n",
r.Name,
strings.Join(r.ShortNames, ","),
gv.String(),
r.Namespaced,
r.Kind); err != nil {
errs = append(errs, err)
}
}
return utilerrors.NewAggregate(errs)
}
type NamePrintFlags struct{}
func APIResourcesNewNamePrintFlags() NamePrintFlags {
return NamePrintFlags{}
}
func (f *NamePrintFlags) AllowedFormats() []string {
return []string{"name"}
}
// AddFlags receives a *cobra.Command reference and binds
// flags related to name printing to it
func (f *NamePrintFlags) AddFlags(_ *cobra.Command) {}
// ToPrinter receives an outputFormat and returns a printer capable of
// handling human-readable output.
func (f *NamePrintFlags) ToPrinter(outputFormat string) (printers.ResourcePrinter, error) {
if outputFormat == "name" {
return NamePrinter{}, nil
}
return nil, genericclioptions.NoCompatiblePrinterError{Options: f, AllowedFormats: f.AllowedFormats()}
}
type NamePrinter struct{}
func (f NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error {
flatList, ok := obj.(*metav1.APIResourceList)
if !ok {
return fmt.Errorf("object is not a APIResourceList")
}
var errs []error
for _, r := range flatList.APIResources {
name := r.Name
if len(r.Group) > 0 {
name += "." + r.Group
}
if _, err := fmt.Fprintf(w, "%s\n", name); err != nil {
errs = append(errs, err)
}
}
return utilerrors.NewAggregate(errs)
}
func APIResourcesHumanReadableFlags() HumanPrintFlags {
return HumanPrintFlags{
SortBy: nil,
NoHeaders: false,
}
}
func (f *PrintFlags) AllowedFormats() []string {
ret := []string{}
ret = append(ret, f.JSONYamlPrintFlags.AllowedFormats()...)
ret = append(ret, f.NamePrintFlags.AllowedFormats()...)
ret = append(ret, f.HumanReadableFlags.AllowedFormats()...)
return ret
}
func (f *PrintFlags) ToPrinter() (printers.ResourcePrinter, error) {
outputFormat := ""
if f.OutputFormat != nil {
outputFormat = *f.OutputFormat
}
noHeaders := false
if f.NoHeaders != nil {
noHeaders = *f.NoHeaders
}
f.HumanReadableFlags.NoHeaders = noHeaders
if p, err := f.JSONYamlPrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return p, err
}
if p, err := f.HumanReadableFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return p, err
}
if p, err := f.NamePrintFlags.ToPrinter(outputFormat); !genericclioptions.IsNoCompatiblePrinterError(err) {
return p, err
}
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: f.AllowedFormats()}
}

View File

@ -23,7 +23,7 @@ import (
"net/http" "net/http"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"sigs.k8s.io/structured-merge-diff/v6/fieldpath" "sigs.k8s.io/structured-merge-diff/v4/fieldpath"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/errors"
@ -71,7 +71,6 @@ type ApplyFlags struct {
All bool All bool
Overwrite bool Overwrite bool
OpenAPIPatch bool OpenAPIPatch bool
Subresource string
PruneAllowlist []string PruneAllowlist []string
@ -98,7 +97,6 @@ type ApplyOptions struct {
All bool All bool
Overwrite bool Overwrite bool
OpenAPIPatch bool OpenAPIPatch bool
Subresource string
ValidationDirective string ValidationDirective string
Validator validation.Schema Validator validation.Schema
@ -237,7 +235,6 @@ func (flags *ApplyFlags) AddFlags(cmd *cobra.Command) {
cmdutil.AddPruningFlags(cmd, &flags.Prune, &flags.PruneAllowlist, &flags.All, &flags.ApplySetRef) cmdutil.AddPruningFlags(cmd, &flags.Prune, &flags.PruneAllowlist, &flags.All, &flags.ApplySetRef)
cmd.Flags().BoolVar(&flags.Overwrite, "overwrite", flags.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration") cmd.Flags().BoolVar(&flags.Overwrite, "overwrite", flags.Overwrite, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration")
cmd.Flags().BoolVar(&flags.OpenAPIPatch, "openapi-patch", flags.OpenAPIPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.") cmd.Flags().BoolVar(&flags.OpenAPIPatch, "openapi-patch", flags.OpenAPIPatch, "If true, use openapi to calculate diff when the openapi presents and the resource can be found in the openapi spec. Otherwise, fall back to use baked-in types.")
cmdutil.AddSubresourceFlags(cmd, &flags.Subresource, "If specified, apply will operate on the subresource of the requested object. Only allowed when using --server-side.")
} }
// ToOptions converts from CLI inputs to runtime inputs // ToOptions converts from CLI inputs to runtime inputs
@ -359,7 +356,6 @@ func (flags *ApplyFlags) ToOptions(f cmdutil.Factory, cmd *cobra.Command, baseNa
All: flags.All, All: flags.All,
Overwrite: flags.Overwrite, Overwrite: flags.Overwrite,
OpenAPIPatch: flags.OpenAPIPatch, OpenAPIPatch: flags.OpenAPIPatch,
Subresource: flags.Subresource,
Recorder: recorder, Recorder: recorder,
Namespace: namespace, Namespace: namespace,
@ -442,9 +438,6 @@ func (o *ApplyOptions) Validate() error {
} }
} }
} }
if len(o.Subresource) > 0 && !o.ServerSideApply {
return fmt.Errorf("--subresource can only be specified for --server-side")
}
return nil return nil
} }
@ -584,9 +577,7 @@ func (o *ApplyOptions) applyOneObject(info *resource.Info) error {
options := metav1.PatchOptions{ options := metav1.PatchOptions{
Force: &o.ForceConflicts, Force: &o.ForceConflicts,
} }
obj, err := helper. obj, err := helper.Patch(
WithSubresource(o.Subresource).
Patch(
info.Namespace, info.Namespace,
info.Name, info.Name,
types.ApplyPatchType, types.ApplyPatchType,

View File

@ -25,7 +25,6 @@ import (
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"testing" "testing"
"time" "time"
@ -61,6 +60,7 @@ import (
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/openapi" "k8s.io/kubectl/pkg/util/openapi"
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/yaml" "sigs.k8s.io/yaml"
) )
@ -2605,6 +2605,7 @@ metadata:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: ReplicationController applyset.kubernetes.io/contains-group-kinds: ReplicationController
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels: labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1 applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set name: my-set
@ -2638,6 +2639,7 @@ metadata:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels: labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1 applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set name: my-set
@ -2672,6 +2674,7 @@ metadata:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels: labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1 applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set name: my-set
@ -2706,6 +2709,7 @@ metadata:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-kinds: Service applyset.kubernetes.io/contains-group-kinds: Service
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels: labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1 applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set name: my-set
@ -2868,6 +2872,7 @@ metadata:
applyset.kubernetes.io/additional-namespaces: test applyset.kubernetes.io/additional-namespaces: test
applyset.kubernetes.io/contains-group-kinds: ReplicationController applyset.kubernetes.io/contains-group-kinds: ReplicationController
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels: labels:
applyset.kubernetes.io/id: applyset-rhp1a-HVAVT_dFgyEygyA1BEB82HPp2o10UiFTpqtAs-v1 applyset.kubernetes.io/id: applyset-rhp1a-HVAVT_dFgyEygyA1BEB82HPp2o10UiFTpqtAs-v1
name: my-set name: my-set
@ -3085,6 +3090,7 @@ metadata:
applyset.kubernetes.io/additional-namespaces: "" applyset.kubernetes.io/additional-namespaces: ""
applyset.kubernetes.io/contains-group-resources: replicationcontrollers applyset.kubernetes.io/contains-group-resources: replicationcontrollers
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$ applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
creationTimestamp: null
labels: labels:
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1 applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
name: my-set name: my-set

View File

@ -17,12 +17,13 @@ limitations under the License.
package apply package apply
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"time" "time"
"github.com/pkg/errors"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
apierrors "k8s.io/apimachinery/pkg/api/errors" apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
@ -59,7 +60,7 @@ const (
// patchRetryBackOffPeriod is the period to back off when apply patch results in error. // patchRetryBackOffPeriod is the period to back off when apply patch results in error.
var patchRetryBackOffPeriod = 1 * time.Second var patchRetryBackOffPeriod = 1 * time.Second
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor: %w" var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
// Patcher defines options to patch OpenAPI objects. // Patcher defines options to patch OpenAPI objects.
type Patcher struct { type Patcher struct {
@ -118,13 +119,13 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
// Serialize the current configuration of the object from the server. // Serialize the current configuration of the object from the server.
current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj) current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("serializing current configuration from:\n%v\nfor: %w", obj, err) return nil, nil, errors.Wrapf(err, "serializing current configuration from:\n%v\nfor:", obj)
} }
// Retrieve the original configuration of the object from the annotation. // Retrieve the original configuration of the object from the annotation.
original, err := util.GetOriginalConfiguration(obj) original, err := util.GetOriginalConfiguration(obj)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("retrieving original configuration from:\n%v\nfor: %w", obj, err) return nil, nil, errors.Wrapf(err, "retrieving original configuration from:\n%v\nfor:", obj)
} }
var patchType types.PatchType var patchType types.PatchType
@ -176,17 +177,17 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
patchType = types.StrategicMergePatchType patchType = types.StrategicMergePatchType
patch, err = p.buildStrategicMergeFromBuiltins(versionedObj, original, modified, current) patch, err = p.buildStrategicMergeFromBuiltins(versionedObj, original, modified, current)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf(createPatchErrFormat, original, modified, current, err) return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
} }
} else { } else {
if !runtime.IsNotRegisteredError(err) { if !runtime.IsNotRegisteredError(err) {
return nil, nil, fmt.Errorf("getting instance of versioned object for %v: %w", p.Mapping.GroupVersionKind, err) return nil, nil, errors.Wrapf(err, "getting instance of versioned object for %v:", p.Mapping.GroupVersionKind)
} }
patchType = types.MergePatchType patchType = types.MergePatchType
patch, err = p.buildMergePatch(original, modified, current) patch, err = p.buildMergePatch(original, modified, current)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf(createPatchErrFormat, original, modified, current, err) return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
} }
} }
} }
@ -198,7 +199,7 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
if p.ResourceVersion != nil { if p.ResourceVersion != nil {
patch, err = addResourceVersion(patch, *p.ResourceVersion) patch, err = addResourceVersion(patch, *p.ResourceVersion)
if err != nil { if err != nil {
return nil, nil, fmt.Errorf("failed to insert resourceVersion in patch: %w", err) return nil, nil, errors.Wrap(err, "Failed to insert resourceVersion in patch")
} }
} }
@ -386,7 +387,7 @@ func (p *Patcher) deleteAndCreate(original runtime.Object, modified []byte, name
return modified, nil, err return modified, nil, err
} }
// TODO: use wait // TODO: use wait
if err := wait.PollUntilContextTimeout(context.Background(), 1*time.Second, p.Timeout, true, func(ctx context.Context) (bool, error) { if err := wait.PollImmediate(1*time.Second, p.Timeout, func() (bool, error) {
if _, err := p.Helper.Get(namespace, name); !apierrors.IsNotFound(err) { if _, err := p.Helper.Get(namespace, name); !apierrors.IsNotFound(err) {
return false, err return false, err
} }

View File

@ -42,7 +42,6 @@ import (
"k8s.io/kubectl/pkg/util/completion" "k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/util/term"
) )
var ( var (
@ -301,16 +300,13 @@ func (o *AttachOptions) Run() error {
sizePlusOne.Height++ sizePlusOne.Height++
// this call spawns a goroutine to monitor/update the terminal size // this call spawns a goroutine to monitor/update the terminal size
sizeQueue = &terminalSizeQueueAdapter{ sizeQueue = t.MonitorSize(&sizePlusOne, size)
delegate: t.MonitorSize(&sizePlusOne, size),
}
} }
o.DisableStderr = true o.DisableStderr = true
} }
if !o.Quiet { if !o.Quiet {
_, _ = fmt.Fprintln(o.ErrOut, "All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.")
fmt.Fprintln(o.ErrOut, "If you don't see a command prompt, try pressing enter.") fmt.Fprintln(o.ErrOut, "If you don't see a command prompt, try pressing enter.")
} }
if err := t.Safe(o.AttachFunc(o, containerToAttach, t.Raw, sizeQueue)); err != nil { if err := t.Safe(o.AttachFunc(o, containerToAttach, t.Raw, sizeQueue)); err != nil {
@ -360,18 +356,3 @@ func (o *AttachOptions) reattachMessage(containerName string, rawTTY bool) strin
} }
return fmt.Sprintf("Session ended, resume using '%s %s -c %s -i -t' command when the pod is running", o.CommandName, o.Pod.Name, containerName) return fmt.Sprintf("Session ended, resume using '%s %s -c %s -i -t' command when the pod is running", o.CommandName, o.Pod.Name, containerName)
} }
type terminalSizeQueueAdapter struct {
delegate term.TerminalSizeQueue
}
func (a *terminalSizeQueueAdapter) Next() *remotecommand.TerminalSize {
next := a.delegate.Next()
if next == nil {
return nil
}
return &remotecommand.TerminalSize{
Width: next.Width,
Height: next.Height,
}
}

View File

@ -242,7 +242,6 @@ func TestAttach(t *testing.T) {
pod *corev1.Pod pod *corev1.Pod
remoteAttachErr bool remoteAttachErr bool
expectedErr string expectedErr string
expectedErrOut []string
}{ }{
{ {
name: "pod attach", name: "pod attach",
@ -252,10 +251,6 @@ func TestAttach(t *testing.T) {
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach", attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
pod: attachPod(), pod: attachPod(),
container: "bar", container: "bar",
expectedErrOut: []string{
"All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.",
"If you don't see a command prompt, try pressing enter.",
},
}, },
{ {
name: "pod attach error", name: "pod attach error",
@ -310,11 +305,10 @@ func TestAttach(t *testing.T) {
if test.remoteAttachErr { if test.remoteAttachErr {
remoteAttach.err = fmt.Errorf("attach error") remoteAttach.err = fmt.Errorf("attach error")
} }
streams, _, _, errOut := genericiooptions.NewTestIOStreams()
options := &AttachOptions{ options := &AttachOptions{
StreamOptions: exec.StreamOptions{ StreamOptions: exec.StreamOptions{
ContainerName: test.container, ContainerName: test.container,
IOStreams: streams, IOStreams: genericiooptions.NewTestIOStreamsDiscard(),
}, },
Attach: remoteAttach, Attach: remoteAttach,
GetPodTimeout: 1000, GetPodTimeout: 1000,
@ -355,14 +349,6 @@ func TestAttach(t *testing.T) {
if remoteAttach.url.Query().Get("container") != "bar" { if remoteAttach.url.Query().Get("container") != "bar" {
t.Errorf("%s: Did not have query parameters: %s", test.name, remoteAttach.url.Query()) t.Errorf("%s: Did not have query parameters: %s", test.name, remoteAttach.url.Query())
} }
if test.expectedErrOut != nil {
for _, expect := range test.expectedErrOut {
if !strings.Contains(errOut.String(), expect) {
t.Errorf("%s: expected message %s not found, got: %s", test.name, expect, strings.ReplaceAll(errOut.String(), "\n", ""))
return
}
}
}
}) })
} }
} }

View File

@ -105,10 +105,10 @@ var (
# List all allowed actions in namespace "foo" # List all allowed actions in namespace "foo"
kubectl auth can-i --list --namespace=foo`) kubectl auth can-i --list --namespace=foo`)
resourceVerbs = sets.New[string]("get", "list", "watch", "create", "update", "patch", "delete", "deletecollection", "use", "bind", "impersonate", "*", "approve", "sign", "escalate", "attest") resourceVerbs = sets.NewString("get", "list", "watch", "create", "update", "patch", "delete", "deletecollection", "use", "bind", "impersonate", "*", "approve")
nonResourceURLVerbs = sets.New[string]("get", "put", "post", "head", "options", "delete", "patch", "*") nonResourceURLVerbs = sets.NewString("get", "put", "post", "head", "options", "delete", "patch", "*")
// holds all the server-supported resources that cannot be discovered by clients. i.e. users and groups for the impersonate verb // holds all the server-supported resources that cannot be discovered by clients. i.e. users and groups for the impersonate verb
nonStandardResourceNames = sets.New[string]("users", "groups") nonStandardResourceNames = sets.NewString("users", "groups")
) )
// NewCmdCanI returns an initialized Command for 'auth can-i' sub command // NewCmdCanI returns an initialized Command for 'auth can-i' sub command

View File

@ -161,7 +161,7 @@ func TestRunAccessCheck(t *testing.T) {
test.serverErr test.serverErr
}), }),
} }
tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}, ContentType: runtime.ContentTypeJSON}} tf.ClientConfigVal = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &schema.GroupVersion{Group: "", Version: "v1"}}}
if err := test.o.Complete(tf, test.args); err != nil { if err := test.o.Complete(tf, test.args); err != nil {
t.Errorf("%s: %v", test.name, err) t.Errorf("%s: %v", test.name, err)
@ -196,7 +196,6 @@ func TestRunAccessList(t *testing.T) {
" [/version] [] [get]\n" " [/version] [] [get]\n"
tf := cmdtesting.NewTestFactory().WithNamespace("test") tf := cmdtesting.NewTestFactory().WithNamespace("test")
tf.ClientConfigVal.ContentType = runtime.ContentTypeJSON
defer tf.Cleanup() defer tf.Cleanup()
ns := scheme.Codecs.WithoutConversion() ns := scheme.Codecs.WithoutConversion()

View File

@ -22,7 +22,6 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/klog/v2" "k8s.io/klog/v2"
rbacv1 "k8s.io/api/rbac/v1" rbacv1 "k8s.io/api/rbac/v1"
@ -34,7 +33,6 @@ import (
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1" corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
rbacv1client "k8s.io/client-go/kubernetes/typed/rbac/v1" rbacv1client "k8s.io/client-go/kubernetes/typed/rbac/v1"
"k8s.io/client-go/util/retry"
"k8s.io/component-helpers/auth/rbac/reconciliation" "k8s.io/component-helpers/auth/rbac/reconciliation"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
@ -199,9 +197,6 @@ func (o *ReconcileOptions) Validate() error {
// RunReconcile performs the execution // RunReconcile performs the execution
func (o *ReconcileOptions) RunReconcile() error { func (o *ReconcileOptions) RunReconcile() error {
// conflictBackoff retries up to 3 times on conflict, with no delay
conflictBackoff := wait.Backoff{Steps: 3}
return o.Visitor.Visit(func(info *resource.Info, err error) error { return o.Visitor.Visit(func(info *resource.Info, err error) error {
if err != nil { if err != nil {
return err return err
@ -218,14 +213,7 @@ func (o *ReconcileOptions) RunReconcile() error {
Client: o.RBACClient, Client: o.RBACClient,
}, },
} }
var ( result, err := reconcileOptions.Run()
result *reconciliation.ReconcileClusterRoleResult
err error
)
retry.RetryOnConflict(conflictBackoff, func() error {
result, err = reconcileOptions.Run()
return err
})
if err != nil { if err != nil {
return err return err
} }
@ -240,14 +228,7 @@ func (o *ReconcileOptions) RunReconcile() error {
Client: o.RBACClient.ClusterRoles(), Client: o.RBACClient.ClusterRoles(),
}, },
} }
var ( result, err := reconcileOptions.Run()
result *reconciliation.ReconcileClusterRoleResult
err error
)
retry.RetryOnConflict(conflictBackoff, func() error {
result, err = reconcileOptions.Run()
return err
})
if err != nil { if err != nil {
return err return err
} }
@ -263,14 +244,10 @@ func (o *ReconcileOptions) RunReconcile() error {
NamespaceClient: o.NamespaceClient.Namespaces(), NamespaceClient: o.NamespaceClient.Namespaces(),
}, },
} }
var ( result, err := reconcileOptions.Run()
result *reconciliation.ReconcileClusterRoleBindingResult if err != nil {
err error
)
retry.RetryOnConflict(conflictBackoff, func() error {
result, err = reconcileOptions.Run()
return err return err
}) }
o.printResults(result.RoleBinding.GetObject(), result.MissingSubjects, result.ExtraSubjects, nil, nil, result.Operation, result.Protected) o.printResults(result.RoleBinding.GetObject(), result.MissingSubjects, result.ExtraSubjects, nil, nil, result.Operation, result.Protected)
case *rbacv1.ClusterRoleBinding: case *rbacv1.ClusterRoleBinding:
@ -282,14 +259,10 @@ func (o *ReconcileOptions) RunReconcile() error {
Client: o.RBACClient.ClusterRoleBindings(), Client: o.RBACClient.ClusterRoleBindings(),
}, },
} }
var ( result, err := reconcileOptions.Run()
result *reconciliation.ReconcileClusterRoleBindingResult if err != nil {
err error
)
retry.RetryOnConflict(conflictBackoff, func() error {
result, err = reconcileOptions.Run()
return err return err
}) }
o.printResults(result.RoleBinding.GetObject(), result.MissingSubjects, result.ExtraSubjects, nil, nil, result.Operation, result.Protected) o.printResults(result.RoleBinding.GetObject(), result.MissingSubjects, result.ExtraSubjects, nil, nil, result.Operation, result.Protected)
case *rbacv1beta1.Role, case *rbacv1beta1.Role,

View File

@ -250,7 +250,7 @@ func printTableSelfSubjectAccessReview(obj runtime.Object, out io.Writer) error
} }
if len(ui.Extra) > 0 { if len(ui.Extra) > 0 {
for _, k := range sets.List(sets.KeySet(ui.Extra)) { for _, k := range sets.StringKeySet(ui.Extra).List() {
v := ui.Extra[k] v := ui.Extra[k]
_, err := fmt.Fprintf(w, "Extra: %s\t%v\n", k, v) _, err := fmt.Fprintf(w, "Extra: %s\t%v\n", k, v)
if err != nil { if err != nil {

View File

@ -75,7 +75,9 @@ func TestWhoAmIRun(t *testing.T) {
`{ `{
"kind": "SelfSubjectReview", "kind": "SelfSubjectReview",
"apiVersion": "authentication.k8s.io/v1", "apiVersion": "authentication.k8s.io/v1",
"metadata": {}, "metadata": {
"creationTimestamp": null
},
"status": { "status": {
"userInfo": { "userInfo": {
"username": "jane.doe", "username": "jane.doe",
@ -129,7 +131,9 @@ func TestWhoAmIRun(t *testing.T) {
`{ `{
"kind": "SelfSubjectReview", "kind": "SelfSubjectReview",
"apiVersion": "authentication.k8s.io/v1beta1", "apiVersion": "authentication.k8s.io/v1beta1",
"metadata": {}, "metadata": {
"creationTimestamp": null
},
"status": { "status": {
"userInfo": { "userInfo": {
"username": "jane.doe", "username": "jane.doe",
@ -182,7 +186,9 @@ func TestWhoAmIRun(t *testing.T) {
`{ `{
"kind": "SelfSubjectReview", "kind": "SelfSubjectReview",
"apiVersion": "authentication.k8s.io/v1", "apiVersion": "authentication.k8s.io/v1",
"metadata": {}, "metadata": {
"creationTimestamp": null
},
"status": { "status": {
"userInfo": { "userInfo": {
"username": "jane.doe", "username": "jane.doe",

View File

@ -19,26 +19,19 @@ package autoscale
import ( import (
"context" "context"
"fmt" "fmt"
"strconv"
"strings"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/klog/v2"
autoscalingv1 "k8s.io/api/autoscaling/v1" autoscalingv1 "k8s.io/api/autoscaling/v1"
autoscalingv2 "k8s.io/api/autoscaling/v2"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
apiresource "k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/cli-runtime/pkg/printers" "k8s.io/cli-runtime/pkg/printers"
"k8s.io/cli-runtime/pkg/resource" "k8s.io/cli-runtime/pkg/resource"
autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1" autoscalingv1client "k8s.io/client-go/kubernetes/typed/autoscaling/v1"
autoscalingv2client "k8s.io/client-go/kubernetes/typed/autoscaling/v2"
"k8s.io/client-go/scale" "k8s.io/client-go/scale"
"k8s.io/klog/v2"
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util"
@ -50,7 +43,6 @@ import (
var ( var (
autoscaleLong = templates.LongDesc(i18n.T(` autoscaleLong = templates.LongDesc(i18n.T(`
Creates an autoscaler that automatically chooses and sets the number of pods that run in a Kubernetes cluster. Creates an autoscaler that automatically chooses and sets the number of pods that run in a Kubernetes cluster.
The command will attempt to use the autoscaling/v2 API first, in case of an error, it will fall back to autoscaling/v1 API.
Looks up a deployment, replica set, stateful set, or replication controller by name and creates an autoscaler that uses the given resource as a reference. Looks up a deployment, replica set, stateful set, or replication controller by name and creates an autoscaler that uses the given resource as a reference.
An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`)) An autoscaler can automatically increase or decrease number of pods deployed within the system as needed.`))
@ -60,13 +52,7 @@ var (
kubectl autoscale deployment foo --min=2 --max=10 kubectl autoscale deployment foo --min=2 --max=10
# Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80% # Auto scale a replication controller "foo", with the number of pods between 1 and 5, target CPU utilization at 80%
kubectl autoscale rc foo --max=5 --cpu=80% kubectl autoscale rc foo --max=5 --cpu-percent=80`))
# Auto scale a deployment "bar", with the number of pods between 3 and 6, target average CPU of 500m and memory of 200Mi
kubectl autoscale deployment bar --min=3 --max=6 --cpu=500m --memory=200Mi
# Auto scale a deployment "bar", with the number of pods between 2 and 8, target CPU utilization 60% and memory utilization 70%
kubectl autoscale deployment bar --min=3 --max=6 --cpu=60% --memory=70%`))
) )
// AutoscaleOptions declares the arguments accepted by the Autoscale command // AutoscaleOptions declares the arguments accepted by the Autoscale command
@ -83,8 +69,6 @@ type AutoscaleOptions struct {
Min int32 Min int32
Max int32 Max int32
CPUPercent int32 CPUPercent int32
CPU string
Memory string
createAnnotation bool createAnnotation bool
args []string args []string
@ -94,8 +78,7 @@ type AutoscaleOptions struct {
builder *resource.Builder builder *resource.Builder
fieldManager string fieldManager string
HPAClientV1 autoscalingv1client.HorizontalPodAutoscalersGetter HPAClient autoscalingv1client.HorizontalPodAutoscalersGetter
HPAClientV2 autoscalingv2client.HorizontalPodAutoscalersGetter
scaleKindResolver scale.ScaleKindResolver scaleKindResolver scale.ScaleKindResolver
genericiooptions.IOStreams genericiooptions.IOStreams
@ -120,7 +103,7 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"} validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu=CPU] [--memory=MEMORY]", Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Short: i18n.T("Auto-scale a deployment, replica set, stateful set, or replication controller"), Short: i18n.T("Auto-scale a deployment, replica set, stateful set, or replication controller"),
Long: autoscaleLong, Long: autoscaleLong,
@ -140,11 +123,7 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.") cmd.Flags().Int32Var(&o.Max, "max", -1, "The upper limit for the number of pods that can be set by the autoscaler. Required.")
cmd.MarkFlagRequired("max") cmd.MarkFlagRequired("max")
cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, "The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.") cmd.Flags().Int32Var(&o.CPUPercent, "cpu-percent", -1, "The target average CPU utilization (represented as a percent of requested CPU) over all the pods. If it's not specified or negative, a default autoscaling policy will be used.")
cmd.Flags().StringVar(&o.CPU, "cpu", "", `Target CPU utilization over all the pods. When specified as a percentage (e.g."70%" for 70% of requested CPU) it will target average utilization. When specified as quantity (e.g."500m" for 500 milliCPU) it will target average value. Value without units is treated as a quantity with miliCPU being the unit (e.g."500" is "500m").`)
cmd.Flags().StringVar(&o.Memory, "memory", "", `Target memory utilization over all the pods. When specified as a percentage (e.g."60%" for 60% of requested memory) it will target average utilization. When specified as quantity (e.g."200Mi" for 200 MiB, "1Gi" for 1 GiB) it will target average value. Value without units is treated as a quantity with mebibytes being the unit (e.g."200" is "200Mi").`)
cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used.")) cmd.Flags().StringVar(&o.Name, "name", "", i18n.T("The name for the newly created object. If not specified, the name of the input resource will be used."))
_ = cmd.Flags().MarkDeprecated("cpu-percent",
"Use --cpu with percentage or resource quantity format (e.g., '70%' for utilization or '500m' for milliCPU).")
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.") cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
cmdutil.AddApplyAnnotationFlags(cmd) cmdutil.AddApplyAnnotationFlags(cmd)
@ -178,8 +157,7 @@ func (o *AutoscaleOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args
if err != nil { if err != nil {
return err return err
} }
o.HPAClientV2 = kubeClient.AutoscalingV2() o.HPAClient = kubeClient.AutoscalingV1()
o.HPAClientV1 = kubeClient.AutoscalingV1()
o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace() o.namespace, o.enforceNamespace, err = f.ToRawKubeConfigLoader().Namespace()
if err != nil { if err != nil {
@ -204,25 +182,11 @@ func (o *AutoscaleOptions) Validate() error {
if o.Max < o.Min { if o.Max < o.Min {
return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min) return fmt.Errorf("--max=MAXPODS must be larger or equal to --min=MINPODS, max: %d, min: %d", o.Max, o.Min)
} }
// only one of the CPUPercent or CPU param is allowed
if o.CPUPercent > 0 && o.CPU != "" {
return fmt.Errorf("--cpu-percent and --cpu are mutually exclusive")
}
// validate CPU target if specified
if o.CPU != "" {
if _, _, _, err := parseResourceInput(o.CPU, corev1.ResourceCPU); err != nil {
return err
}
}
// validate Memory target if specified
if o.Memory != "" {
if _, _, _, err := parseResourceInput(o.Memory, corev1.ResourceMemory); err != nil {
return err
}
}
return nil return nil
} }
// Run performs the execution
func (o *AutoscaleOptions) Run() error { func (o *AutoscaleOptions) Run() error {
r := o.builder. r := o.builder.
Unstructured(). Unstructured().
@ -244,60 +208,19 @@ func (o *AutoscaleOptions) Run() error {
mapping := info.ResourceMapping() mapping := info.ResourceMapping()
gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource) gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
if _, err = o.scaleKindResolver.ScaleForResource(gvr); err != nil { if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
return fmt.Errorf("cannot autoscale a %s: %w", mapping.GroupVersionKind.Kind, err) return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
} }
// handles the creation of HorizontalPodAutoscaler objects for both autoscaling/v2 and autoscaling/v1 APIs. hpa := o.createHorizontalPodAutoscaler(info.Name, mapping)
// If autoscaling/v2 API fails, try to create and handle HorizontalPodAutoscaler using autoscaling/v1 API
var hpaV2 runtime.Object
hpaV2, err = o.createHorizontalPodAutoscalerV2(info.Name, mapping)
if err != nil {
return fmt.Errorf("failed to create HorizontalPodAutoscaler using autoscaling/v2 API: %w", err)
}
if err = o.handleHPA(hpaV2); err != nil {
klog.V(1).Infof("Encountered an error with the autoscaling/v2 HorizontalPodAutoscaler: %v. "+
"Falling back to try the autoscaling/v1 HorizontalPodAutoscaler", err)
// check if the HPA can be created using v1 API.
if ok, err := o.canCreateHPAV1(); !ok {
return fmt.Errorf("failed to create autoscaling/v2 HPA and the configuration is incompatible with autoscaling/v1: %w", err)
}
hpaV1 := o.createHorizontalPodAutoscalerV1(info.Name, mapping)
if err := o.handleHPA(hpaV1); err != nil {
return err
}
}
count++
return nil
})
if err != nil {
return err
}
if count == 0 {
return fmt.Errorf("no objects passed to autoscale")
}
return nil
}
func (o *AutoscaleOptions) canCreateHPAV1() (bool, error) {
// Allow fallback to v1 HPA only if:
// 1. CPUPercent is set and Memory is not set.
// 2. Or, Memory is not set and the metric type is UtilizationMetricType.
_, _, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
if err != nil {
return false, err
}
return (o.CPUPercent >= 0 && o.Memory == "") ||
(o.Memory == "" && metricsType == autoscalingv2.UtilizationMetricType), nil
}
// handleHPA handles the creation and management of a single HPA object.
func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
if err := o.Recorder.Record(hpa); err != nil { if err := o.Recorder.Record(hpa); err != nil {
return fmt.Errorf("error recording current command: %w", err) klog.V(4).Infof("error recording current command: %v", err)
} }
if o.dryRunStrategy == cmdutil.DryRunClient { if o.dryRunStrategy == cmdutil.DryRunClient {
count++
printer, err := o.ToPrinter("created") printer, err := o.ToPrinter("created")
if err != nil { if err != nil {
return err return err
@ -316,132 +239,28 @@ func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
if o.dryRunStrategy == cmdutil.DryRunServer { if o.dryRunStrategy == cmdutil.DryRunServer {
createOptions.DryRun = []string{metav1.DryRunAll} createOptions.DryRun = []string{metav1.DryRunAll}
} }
actualHPA, err := o.HPAClient.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), hpa, createOptions)
var actualHPA runtime.Object
var err error
switch typedHPA := hpa.(type) {
case *autoscalingv2.HorizontalPodAutoscaler:
actualHPA, err = o.HPAClientV2.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), typedHPA, createOptions)
case *autoscalingv1.HorizontalPodAutoscaler:
actualHPA, err = o.HPAClientV1.HorizontalPodAutoscalers(o.namespace).Create(context.TODO(), typedHPA, createOptions)
default:
return fmt.Errorf("unsupported HorizontalPodAutoscaler type %T", hpa)
}
if err != nil { if err != nil {
return err return err
} }
count++
printer, err := o.ToPrinter("autoscaled") printer, err := o.ToPrinter("autoscaled")
if err != nil { if err != nil {
return err return err
} }
return printer.PrintObj(actualHPA, o.Out) return printer.PrintObj(actualHPA, o.Out)
} })
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) (*autoscalingv2.HorizontalPodAutoscaler, error) {
name := o.Name
if len(name) == 0 {
name = refName
}
scaler := autoscalingv2.HorizontalPodAutoscaler{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
APIVersion: mapping.GroupVersionKind.GroupVersion().String(),
Kind: mapping.GroupVersionKind.Kind,
Name: refName,
},
MaxReplicas: o.Max,
},
}
if o.Min > 0 {
scaler.Spec.MinReplicas = &o.Min
}
metrics := []autoscalingv2.MetricSpec{}
// add CPU metric if any of the CPU targets are specified
if o.CPUPercent > 0 {
cpuMetric := autoscalingv2.MetricSpec{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{},
},
}
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
cpuMetric.Resource.Target.AverageUtilization = &o.CPUPercent
metrics = append(metrics, cpuMetric)
}
// add Cpu metric if any of the cpu targets are specified
if o.CPU != "" {
cpuMetric := autoscalingv2.MetricSpec{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: corev1.ResourceCPU,
Target: autoscalingv2.MetricTarget{},
},
}
quantity, value, metricsType, err := parseResourceInput(o.CPU, corev1.ResourceCPU)
if err != nil { if err != nil {
return nil, err return err
} }
switch metricsType { if count == 0 {
case autoscalingv2.UtilizationMetricType: return fmt.Errorf("no objects passed to autoscale")
cpuMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
cpuMetric.Resource.Target.AverageUtilization = &value
case autoscalingv2.AverageValueMetricType:
cpuMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
cpuMetric.Resource.Target.AverageValue = &quantity
default:
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
} }
metrics = append(metrics, cpuMetric) return nil
} }
// add Memory metric if any of the memory targets are specified func (o *AutoscaleOptions) createHorizontalPodAutoscaler(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
if o.Memory != "" {
memoryMetric := autoscalingv2.MetricSpec{
Type: autoscalingv2.ResourceMetricSourceType,
Resource: &autoscalingv2.ResourceMetricSource{
Name: corev1.ResourceMemory,
Target: autoscalingv2.MetricTarget{},
},
}
quantity, value, metricsType, err := parseResourceInput(o.Memory, corev1.ResourceMemory)
if err != nil {
return nil, err
}
switch metricsType {
case autoscalingv2.UtilizationMetricType:
memoryMetric.Resource.Target.Type = autoscalingv2.UtilizationMetricType
memoryMetric.Resource.Target.AverageUtilization = &value
case autoscalingv2.AverageValueMetricType:
memoryMetric.Resource.Target.Type = autoscalingv2.AverageValueMetricType
memoryMetric.Resource.Target.AverageValue = &quantity
default:
return nil, fmt.Errorf("unsupported metric type: %v", metricsType)
}
metrics = append(metrics, memoryMetric)
}
// Only set Metrics if there are any defined
if len(metrics) > 0 {
scaler.Spec.Metrics = metrics
} else {
scaler.Spec.Metrics = nil
}
return &scaler, nil
}
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mapping *meta.RESTMapping) *autoscalingv1.HorizontalPodAutoscaler {
name := o.Name name := o.Name
if len(name) == 0 { if len(name) == 0 {
name = refName name = refName
@ -472,72 +291,3 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mappi
return &scaler return &scaler
} }
// parseResourceInput parses a resource input string into either a utilization percentage or a quantity value.
// It supports:
// - Percentage values (e.g., "70%") for UtilizationMetricType
// - Quantity values with units (e.g., "500m", "2Gi")
// - Bare numbers without units, which are interpreted as:
// - CPU: milliCPU ("500" → "500m")
// - Memory: Mebibytes ("512" → "512Mi")
func parseResourceInput(input string, resourceType corev1.ResourceName) (apiresource.Quantity, int32, autoscalingv2.MetricTargetType, error) {
input = strings.TrimSpace(input)
if input == "" {
return apiresource.Quantity{}, 0, "", fmt.Errorf("empty input")
}
// Case 1: Handle percentage-based metrics like "70%"
percentValue, isPercent, err := parsePercentage(input)
if isPercent {
if err != nil {
return apiresource.Quantity{}, 0, "", err
}
return apiresource.Quantity{}, percentValue, autoscalingv2.UtilizationMetricType, nil
}
// Case 2: Try to interpret input as a bare number (e.g., "500"), and apply default float
valueFloat, err := strconv.ParseFloat(input, 64)
if err == nil {
unit, err := getDefaultUnitForResource(resourceType)
if err != nil {
return apiresource.Quantity{}, 0, "", err
}
inputWithUnit := fmt.Sprintf("%g%s", valueFloat, unit)
quantity, err := apiresource.ParseQuantity(inputWithUnit)
if err != nil {
return apiresource.Quantity{}, 0, "", err
}
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
}
// Case 3: Parse normally if input has a valid unit (e.g., "500m", "2Gi")
quantity, err := apiresource.ParseQuantity(input)
if err != nil {
return apiresource.Quantity{}, 0, "", fmt.Errorf("invalid resource %s value: %s", resourceType, input)
}
return quantity, 0, autoscalingv2.AverageValueMetricType, nil
}
func getDefaultUnitForResource(resourceType corev1.ResourceName) (string, error) {
switch resourceType {
case corev1.ResourceCPU:
return "m", nil
case corev1.ResourceMemory:
return "Mi", nil
default:
return "", fmt.Errorf("unsupported resource type: %v", resourceType)
}
}
func parsePercentage(input string) (int32, bool, error) {
if !strings.HasSuffix(input, "%") {
return 0, false, nil
}
trimmed := strings.TrimSuffix(input, "%")
valueInt64, err := strconv.ParseInt(trimmed, 10, 32)
if err != nil || valueInt64 < 0 {
return 0, true, fmt.Errorf("invalid percentage value: %s", trimmed)
}
return int32(valueInt64), true, nil
}

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
ct "github.com/daviddengcn/go-colortext"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -155,8 +156,12 @@ func (o *ClusterInfoOptions) Run() error {
} }
func printService(out io.Writer, name, link string) { func printService(out io.Writer, name, link string) {
ct.ChangeColor(ct.Green, false, ct.None, false)
fmt.Fprint(out, name) fmt.Fprint(out, name)
ct.ResetColor()
fmt.Fprint(out, " is running at ") fmt.Fprint(out, " is running at ")
ct.ChangeColor(ct.Yellow, false, ct.None, false)
fmt.Fprint(out, link) fmt.Fprint(out, link)
ct.ResetColor()
fmt.Fprintln(out, "") fmt.Fprintln(out, "")
} }

View File

@ -73,7 +73,6 @@ import (
cmdutil "k8s.io/kubectl/pkg/cmd/util" cmdutil "k8s.io/kubectl/pkg/cmd/util"
"k8s.io/kubectl/pkg/cmd/version" "k8s.io/kubectl/pkg/cmd/version"
"k8s.io/kubectl/pkg/cmd/wait" "k8s.io/kubectl/pkg/cmd/wait"
"k8s.io/kubectl/pkg/kuberc"
utilcomp "k8s.io/kubectl/pkg/util/completion" utilcomp "k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
@ -83,6 +82,8 @@ import (
"k8s.io/kubectl/pkg/cmd/kustomize" "k8s.io/kubectl/pkg/cmd/kustomize"
) )
const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS"
type KubectlOptions struct { type KubectlOptions struct {
PluginHandler PluginHandler PluginHandler PluginHandler
Arguments []string Arguments []string
@ -141,6 +142,7 @@ func NewDefaultKubectlCommandWithArgs(o KubectlOptions) *cobra.Command {
} }
} }
} else if err == nil { } else if err == nil {
if !cmdutil.CmdPluginAsSubcommand.IsDisabled() {
// Command exists(e.g. kubectl create), but it is not certain that // Command exists(e.g. kubectl create), but it is not certain that
// subcommand also exists (e.g. kubectl create networkpolicy) // subcommand also exists (e.g. kubectl create networkpolicy)
// we also have to eliminate kubectl create -f // we also have to eliminate kubectl create -f
@ -163,6 +165,7 @@ func NewDefaultKubectlCommandWithArgs(o KubectlOptions) *cobra.Command {
} }
} }
} }
}
return cmd return cmd
} }
@ -358,11 +361,6 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code") flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")
pref := kuberc.NewPreferences()
if !cmdutil.KubeRC.IsDisabled() {
pref.AddFlags(flags)
}
kubeConfigFlags := o.ConfigFlags kubeConfigFlags := o.ConfigFlags
if kubeConfigFlags == nil { if kubeConfigFlags == nil {
kubeConfigFlags = defaultConfigFlags().WithWarningPrinter(o.IOStreams) kubeConfigFlags = defaultConfigFlags().WithWarningPrinter(o.IOStreams)
@ -385,8 +383,6 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
// Avoid import cycle by setting ValidArgsFunction here instead of in NewCmdGet() // Avoid import cycle by setting ValidArgsFunction here instead of in NewCmdGet()
getCmd := get.NewCmdGet("kubectl", f, o.IOStreams) getCmd := get.NewCmdGet("kubectl", f, o.IOStreams)
getCmd.ValidArgsFunction = utilcomp.ResourceTypeAndNameCompletionFunc(f) getCmd.ValidArgsFunction = utilcomp.ResourceTypeAndNameCompletionFunc(f)
debugCmd := debug.NewCmdDebug(f, o.IOStreams)
debugCmd.ValidArgsFunction = utilcomp.ResourceTypeAndNameCompletionFunc(f)
groups := templates.CommandGroups{ groups := templates.CommandGroups{
{ {
@ -438,7 +434,7 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
proxyCmd, proxyCmd,
cp.NewCmdCp(f, o.IOStreams), cp.NewCmdCp(f, o.IOStreams),
auth.NewCmdAuth(f, o.IOStreams), auth.NewCmdAuth(f, o.IOStreams),
debugCmd, debug.NewCmdDebug(f, o.IOStreams),
events.NewCmdEvents(f, o.IOStreams), events.NewCmdEvents(f, o.IOStreams),
}, },
}, },
@ -494,15 +490,6 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
// Stop warning about normalization of flags. That makes it possible to // Stop warning about normalization of flags. That makes it possible to
// add the klog flags later. // add the klog flags later.
cmds.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc) cmds.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)
if !cmdutil.KubeRC.IsDisabled() {
_, err := pref.Apply(cmds, o.Arguments, o.IOStreams.ErrOut)
if err != nil {
fmt.Fprintf(o.IOStreams.ErrOut, "error occurred while applying preferences %v\n", err)
os.Exit(1)
}
}
return cmds return cmds
} }
@ -520,10 +507,13 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
// //
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers // https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
func addCmdHeaderHooks(cmds *cobra.Command, kubeConfigFlags *genericclioptions.ConfigFlags) { func addCmdHeaderHooks(cmds *cobra.Command, kubeConfigFlags *genericclioptions.ConfigFlags) {
if cmdutil.CmdHeaders.IsDisabled() { // If the feature gate env var is set to "false", then do no add kubectl command headers.
if value, exists := os.LookupEnv(kubectlCmdHeaders); exists {
if value == "false" || value == "0" {
klog.V(5).Infoln("kubectl command headers turned off") klog.V(5).Infoln("kubectl command headers turned off")
return return
} }
}
klog.V(5).Infoln("kubectl command headers turned on") klog.V(5).Infoln("kubectl command headers turned on")
crt := &genericclioptions.CommandHeaderRoundTripper{} crt := &genericclioptions.CommandHeaderRoundTripper{}
existingPreRunE := cmds.PersistentPreRunE existingPreRunE := cmds.PersistentPreRunE
@ -576,29 +566,3 @@ func registerCompletionFuncForGlobalFlags(cmd *cobra.Command, f cmdutil.Factory)
return utilcomp.ListUsersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp return utilcomp.ListUsersInConfig(toComplete), cobra.ShellCompDirectiveNoFileComp
})) }))
} }
// GetLogVerbosity parses the provided command-line arguments to determine
// the verbosity level for logging. Returns string representing the verbosity
// level, or 0 if no verbosity flag is specified.
func GetLogVerbosity(args []string) string {
for i, arg := range args {
if arg == "--" {
// flags after "--" does not represent any flag of
// the command. We should short cut the iteration in here.
break
}
if arg == "--v" || arg == "-v" {
if i+1 < len(args) {
return args[i+1]
}
} else if strings.Contains(arg, "--v=") || strings.Contains(arg, "-v=") {
parg := strings.Split(arg, "=")
if len(parg) > 1 && parg[1] != "" {
return parg[1]
}
}
}
return "0"
}

View File

@ -29,7 +29,6 @@ import (
"k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/genericiooptions"
"k8s.io/kubectl/pkg/cmd/plugin" "k8s.io/kubectl/pkg/cmd/plugin"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
) )
func TestNormalizationFuncGlobalExistence(t *testing.T) { func TestNormalizationFuncGlobalExistence(t *testing.T) {
@ -382,6 +381,10 @@ func TestKubectlCommandHeadersHooks(t *testing.T) {
envVar: "false", envVar: "false",
addsHooks: false, addsHooks: false,
}, },
"zero env var value; hooks NOT added": {
envVar: "0",
addsHooks: false,
},
} }
for name, testCase := range tests { for name, testCase := range tests {
@ -391,7 +394,7 @@ func TestKubectlCommandHeadersHooks(t *testing.T) {
if kubeConfigFlags.WrapConfigFn != nil { if kubeConfigFlags.WrapConfigFn != nil {
t.Fatal("expected initial nil WrapConfigFn") t.Fatal("expected initial nil WrapConfigFn")
} }
t.Setenv(string(cmdutil.CmdHeaders), testCase.envVar) t.Setenv(kubectlCmdHeaders, testCase.envVar)
addCmdHeaderHooks(cmds, kubeConfigFlags) addCmdHeaderHooks(cmds, kubeConfigFlags)
// Valdidate whether the hooks were added. // Valdidate whether the hooks were added.
if testCase.addsHooks && kubeConfigFlags.WrapConfigFn == nil { if testCase.addsHooks && kubeConfigFlags.WrapConfigFn == nil {

View File

@ -69,6 +69,7 @@ func Example_view() {
// name: federal-context // name: federal-context
// current-context: federal-context // current-context: federal-context
// kind: Config // kind: Config
// preferences: {}
// users: // users:
// - name: red-user // - name: red-user
// user: // user:

View File

@ -89,7 +89,7 @@ func NewCmdConfigGetContexts(streams genericiooptions.IOStreams, configAccess cl
// Complete assigns GetContextsOptions from the args. // Complete assigns GetContextsOptions from the args.
func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string) error { func (o *GetContextsOptions) Complete(cmd *cobra.Command, args []string) error {
supportedOutputTypes := sets.New[string]("", "name") supportedOutputTypes := sets.NewString("", "name")
if !supportedOutputTypes.Has(o.outputFormat) { if !supportedOutputTypes.Has(o.outputFormat) {
return fmt.Errorf("--output %v is not available in kubectl config get-contexts; resetting to default output format", o.outputFormat) return fmt.Errorf("--output %v is not available in kubectl config get-contexts; resetting to default output format", o.outputFormat)
} }

View File

@ -55,7 +55,7 @@ func newNavigationSteps(path string) (*navigationSteps, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
nextPart := findNameStep(individualParts[currPartIndex:], sets.KeySet(mapValueOptions)) nextPart := findNameStep(individualParts[currPartIndex:], sets.StringKeySet(mapValueOptions))
steps = append(steps, navigationStep{nextPart, mapValueType}) steps = append(steps, navigationStep{nextPart, mapValueType})
currPartIndex += len(strings.Split(nextPart, ".")) currPartIndex += len(strings.Split(nextPart, "."))
@ -98,7 +98,7 @@ func (s *navigationSteps) moreStepsRemaining() bool {
// findNameStep takes the list of parts and a set of valid tags that can be used after the name. It then walks the list of parts // findNameStep takes the list of parts and a set of valid tags that can be used after the name. It then walks the list of parts
// until it find a valid "next" tag or until it reaches the end of the parts and then builds the name back up out of the individual parts // until it find a valid "next" tag or until it reaches the end of the parts and then builds the name back up out of the individual parts
func findNameStep(parts []string, typeOptions sets.Set[string]) string { func findNameStep(parts []string, typeOptions sets.String) string {
if len(parts) == 0 { if len(parts) == 0 {
return "" return ""
} }
@ -136,7 +136,7 @@ func getPotentialTypeValues(typeValue reflect.Type) (map[string]reflect.Type, er
return ret, nil return ret, nil
} }
func findKnownValue(parts []string, valueOptions sets.Set[string]) int { func findKnownValue(parts []string, valueOptions sets.String) int {
for i := range parts { for i := range parts {
if valueOptions.Has(parts[i]) { if valueOptions.Has(parts[i]) {
return i return i

View File

@ -129,8 +129,7 @@ func NewCmdConfigSetCredentials(out io.Writer, configAccess clientcmd.ConfigAcce
} }
// NewCmdConfigSetAuthInfo returns a Command instance for 'config set-credentials' sub command // NewCmdConfigSetAuthInfo returns a Command instance for 'config set-credentials' sub command
// // DEPRECATED: Use NewCmdConfigSetCredentials instead
// Deprecated: Use NewCmdConfigSetCredentials instead
func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command { func NewCmdConfigSetAuthInfo(out io.Writer, configAccess clientcmd.ConfigAccess) *cobra.Command {
return NewCmdConfigSetCredentials(out, configAccess) return NewCmdConfigSetCredentials(out, configAccess)
} }

View File

@ -85,6 +85,7 @@ contexts:
name: my-cluster name: my-cluster
current-context: minikube current-context: minikube
kind: Config kind: Config
preferences: {}
users: users:
- name: minikube - name: minikube
user: user:
@ -164,6 +165,7 @@ contexts:
name: my-cluster name: my-cluster
current-context: minikube current-context: minikube
kind: Config kind: Config
preferences: {}
users: users:
- name: minikube - name: minikube
user: user:
@ -245,6 +247,7 @@ contexts:
name: minikube name: minikube
current-context: minikube current-context: minikube
kind: Config kind: Config
preferences: {}
users: users:
- name: minikube - name: minikube
user: user:
@ -269,6 +272,7 @@ contexts:
name: my-cluster name: my-cluster
current-context: my-cluster current-context: my-cluster
kind: Config kind: Config
preferences: {}
users: users:
- name: mu-cluster - name: mu-cluster
user: user:

View File

@ -18,13 +18,12 @@ package cp
import ( import (
"archive/tar" "archive/tar"
"context" "bytes"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"os" "os"
"strings" "strings"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -268,8 +267,8 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
options := &exec.ExecOptions{ options := &exec.ExecOptions{
StreamOptions: exec.StreamOptions{ StreamOptions: exec.StreamOptions{
IOStreams: genericiooptions.IOStreams{ IOStreams: genericiooptions.IOStreams{
Out: io.Discard, Out: bytes.NewBuffer([]byte{}),
ErrOut: io.Discard, ErrOut: bytes.NewBuffer([]byte{}),
}, },
Namespace: dest.PodNamespace, Namespace: dest.PodNamespace,
@ -280,21 +279,7 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
Executor: &exec.DefaultRemoteExecutor{}, Executor: &exec.DefaultRemoteExecutor{},
} }
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) return o.execute(options)
defer cancel()
done := make(chan error)
go func() {
done <- o.execute(options)
}()
select {
case <-ctx.Done():
return ctx.Err()
case err := <-done:
return err
}
} }
func (o *CopyOptions) copyToPod(src, dest fileSpec, options *exec.ExecOptions) error { func (o *CopyOptions) copyToPod(src, dest fileSpec, options *exec.ExecOptions) error {
@ -310,10 +295,6 @@ func (o *CopyOptions) copyToPod(src, dest fileSpec, options *exec.ExecOptions) e
// If no error, dest.File was found to be a directory. // If no error, dest.File was found to be a directory.
// Copy specified src into it // Copy specified src into it
destFile = destFile.Join(srcFile.Base()) destFile = destFile.Join(srcFile.Base())
} else if errors.Is(err, context.DeadlineExceeded) {
// we haven't decided destination is directory or not because context timeout is exceeded.
// That's why, we should shortcut the process in here.
return err
} }
go func(src localPath, dest remotePath, writer io.WriteCloser) { go func(src localPath, dest remotePath, writer io.WriteCloser) {

View File

@ -106,7 +106,7 @@ func NewCmdCreateJob(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
cmdutil.AddValidateFlags(cmd) cmdutil.AddValidateFlags(cmd)
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.") cmd.Flags().StringVar(&o.Image, "image", o.Image, "Image name to run.")
cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only CronJob is supported).") cmd.Flags().StringVar(&o.From, "from", o.From, "The name of the resource to create a Job from (only cronjob is supported).")
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create") cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-create")
return cmd return cmd
} }

View File

@ -116,7 +116,7 @@ var (
func AddSpecialVerb(verb string, gr schema.GroupResource) { func AddSpecialVerb(verb string, gr schema.GroupResource) {
resources, ok := specialVerbs[verb] resources, ok := specialVerbs[verb]
if !ok { if !ok {
resources = make([]schema.GroupResource, 0, 1) resources = make([]schema.GroupResource, 1)
} }
resources = append(resources, gr) resources = append(resources, gr)
specialVerbs[verb] = resources specialVerbs[verb] = resources
@ -425,7 +425,7 @@ func generateResourcePolicyRules(mapper meta.RESTMapper, verbs []string, resourc
// Create separate rule for each of the api group. // Create separate rule for each of the api group.
rules := []rbacv1.PolicyRule{} rules := []rbacv1.PolicyRule{}
for _, g := range sets.List(sets.KeySet(groupResourceMapping)) { for _, g := range sets.StringKeySet(groupResourceMapping).List() {
rule := rbacv1.PolicyRule{} rule := rbacv1.PolicyRule{}
rule.Verbs = verbs rule.Verbs = verbs
rule.Resources = groupResourceMapping[g] rule.Resources = groupResourceMapping[g]

View File

@ -684,17 +684,14 @@ func TestAddSpecialVerb(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
verb string verb string
resource schema.GroupResource resource schema.GroupResource
isNew bool
}{ }{
"existing verb": { "existing verb": {
verb: "use", verb: "use",
resource: schema.GroupResource{Group: "my.custom.io", Resource: "one"}, resource: schema.GroupResource{Group: "my.custom.io", Resource: "one"},
isNew: false,
}, },
"new verb": { "new verb": {
verb: "new", verb: "new",
resource: schema.GroupResource{Group: "my.custom.io", Resource: "two"}, resource: schema.GroupResource{Group: "my.custom.io", Resource: "two"},
isNew: true,
}, },
} }
@ -706,16 +703,6 @@ func TestAddSpecialVerb(t *testing.T) {
t.Errorf("missing expected verb: %s", tc.verb) t.Errorf("missing expected verb: %s", tc.verb)
} }
if tc.isNew {
if len(resources) != 1 {
t.Errorf("new verb should only contain one resource resources:%#v", resources)
}
if !reflect.DeepEqual(tc.resource, resources[0]) {
t.Errorf("miss expected resource:%#v", tc.resource)
}
return
}
for _, res := range resources { for _, res := range resources {
if reflect.DeepEqual(tc.resource, res) { if reflect.DeepEqual(tc.resource, res) {
return return

View File

@ -98,12 +98,16 @@ var (
`) `)
) )
var boundObjectKinds = map[string]string{ func boundObjectKindToAPIVersions() map[string]string {
kinds := map[string]string{
"Pod": "v1", "Pod": "v1",
"Secret": "v1", "Secret": "v1",
"Node": "v1", "Node": "v1",
} }
return kinds
}
func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions { func NewTokenOpts(ioStreams genericiooptions.IOStreams) *TokenOptions {
return &TokenOptions{ return &TokenOptions{
PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme), PrintFlags: genericclioptions.NewPrintFlags("created").WithTypeSetter(scheme.Scheme),
@ -145,7 +149,7 @@ func NewCmdCreateToken(f cmdutil.Factory, ioStreams genericiooptions.IOStreams)
cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.") cmd.Flags().DurationVar(&o.Duration, "duration", o.Duration, "Requested lifetime of the issued token. If not set or if set to 0, the lifetime will be determined by the server automatically. The server may return a token with a longer or shorter lifetime.")
cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+ cmd.Flags().StringVar(&o.BoundObjectKind, "bound-object-kind", o.BoundObjectKind, "Kind of an object to bind the token to. "+
"Supported kinds are "+strings.Join(sets.List(sets.KeySet(boundObjectKinds)), ", ")+". "+ "Supported kinds are "+strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", ")+". "+
"If set, --bound-object-name must be provided.") "If set, --bound-object-name must be provided.")
cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+ cmd.Flags().StringVar(&o.BoundObjectName, "bound-object-name", o.BoundObjectName, "Name of an object to bind the token to. "+
"The token will expire when the object is deleted. "+ "The token will expire when the object is deleted. "+
@ -222,8 +226,8 @@ func (o *TokenOptions) Validate() error {
return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided") return fmt.Errorf("--bound-object-uid can only be set if --bound-object-kind is provided")
} }
} else { } else {
if _, ok := boundObjectKinds[o.BoundObjectKind]; !ok { if _, ok := boundObjectKindToAPIVersions()[o.BoundObjectKind]; !ok {
return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.List(sets.KeySet(boundObjectKinds)), ", ")) return fmt.Errorf("supported --bound-object-kind values are %s", strings.Join(sets.StringKeySet(boundObjectKindToAPIVersions()).List(), ", "))
} }
if len(o.BoundObjectName) == 0 { if len(o.BoundObjectName) == 0 {
return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided") return fmt.Errorf("--bound-object-name is required if --bound-object-kind is provided")
@ -246,7 +250,7 @@ func (o *TokenOptions) Run() error {
if len(o.BoundObjectKind) > 0 { if len(o.BoundObjectKind) > 0 {
request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{ request.Spec.BoundObjectRef = &authenticationv1.BoundObjectReference{
Kind: o.BoundObjectKind, Kind: o.BoundObjectKind,
APIVersion: boundObjectKinds[o.BoundObjectKind], APIVersion: boundObjectKindToAPIVersions()[o.BoundObjectKind],
Name: o.BoundObjectName, Name: o.BoundObjectName,
UID: types.UID(o.BoundObjectUID), UID: types.UID(o.BoundObjectUID),
} }

View File

@ -99,7 +99,8 @@ func TestCreateToken(t *testing.T) {
serverResponseToken: "abc", serverResponseToken: "abc",
expectStdout: `apiVersion: authentication.k8s.io/v1 expectStdout: `apiVersion: authentication.k8s.io/v1
kind: TokenRequest kind: TokenRequest
metadata: {} metadata:
creationTimestamp: null
spec: spec:
audiences: null audiences: null
boundObjectRef: null boundObjectRef: null

View File

@ -75,9 +75,6 @@ var (
debugging utilities without restarting the pod. debugging utilities without restarting the pod.
* Node: Create a new pod that runs in the node's host namespaces and can access * Node: Create a new pod that runs in the node's host namespaces and can access
the node's filesystem. the node's filesystem.
Note: When a non-root user is configured for the entire target Pod, some capabilities granted
by debug profile may not work.
`)) `))
debugExample = templates.Examples(i18n.T(` debugExample = templates.Examples(i18n.T(`
@ -215,8 +212,10 @@ func (o *DebugOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name.")) cmd.Flags().StringVar(&o.TargetContainer, "target", "", i18n.T("When using an ephemeral container, target processes in this container name."))
cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container.")) cmd.Flags().BoolVarP(&o.TTY, "tty", "t", o.TTY, i18n.T("Allocate a TTY for the debugging container."))
cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`)) cmd.Flags().StringVar(&o.Profile, "profile", ProfileLegacy, i18n.T(`Options are "legacy", "general", "baseline", "netadmin", "restricted" or "sysadmin".`))
if !cmdutil.DebugCustomProfile.IsDisabled() {
cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON or YAML file containing a partial container spec to customize built-in debug profiles.")) cmd.Flags().StringVar(&o.CustomProfileFile, "custom", o.CustomProfileFile, i18n.T("Path to a JSON or YAML file containing a partial container spec to customize built-in debug profiles."))
} }
}
// Complete finishes run-time initialization of debug.DebugOptions. // Complete finishes run-time initialization of debug.DebugOptions.
func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command, args []string) error { func (o *DebugOptions) Complete(restClientGetter genericclioptions.RESTClientGetter, cmd *cobra.Command, args []string) error {
@ -400,11 +399,6 @@ func (o *DebugOptions) Validate() error {
} }
} }
// Warning for legacy profile
if o.Profile == ProfileLegacy {
fmt.Fprintln(o.ErrOut, `--profile=legacy is deprecated and will be removed in the future. It is recommended to explicitly specify a profile, for example "--profile=general".`)
}
return nil return nil
} }
@ -498,8 +492,6 @@ func (o *DebugOptions) debugByEphemeralContainer(ctx context.Context, pod *corev
} }
klog.V(2).Infof("new ephemeral container: %#v", debugContainer) klog.V(2).Infof("new ephemeral container: %#v", debugContainer)
o.displayWarning((*corev1.Container)(&debugContainer.EphemeralContainerCommon), pod)
debugJS, err := json.Marshal(debugPod) debugJS, err := json.Marshal(debugPod)
if err != nil { if err != nil {
return nil, "", fmt.Errorf("error creating JSON for debug container: %v", err) return nil, "", fmt.Errorf("error creating JSON for debug container: %v", err)
@ -616,16 +608,6 @@ func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
var debugContainer *corev1.Container
for i := range copied.Spec.Containers {
if copied.Spec.Containers[i].Name == dc {
debugContainer = &copied.Spec.Containers[i]
break
}
}
o.displayWarning(debugContainer, copied)
created, err := o.podClient.Pods(copied.Namespace).Create(ctx, copied, metav1.CreateOptions{}) created, err := o.podClient.Pods(copied.Namespace).Create(ctx, copied, metav1.CreateOptions{})
if err != nil { if err != nil {
return nil, "", err return nil, "", err
@ -639,32 +621,6 @@ func (o *DebugOptions) debugByCopy(ctx context.Context, pod *corev1.Pod) (*corev
return created, dc, nil return created, dc, nil
} }
// Display warning message if some capabilities are set by profile and non-root user is specified in .Spec.SecurityContext.RunAsUser.(#1650)
func (o *DebugOptions) displayWarning(container *corev1.Container, pod *corev1.Pod) {
if container == nil {
return
}
if pod.Spec.SecurityContext.RunAsUser == nil || *pod.Spec.SecurityContext.RunAsUser == 0 {
return
}
if container.SecurityContext == nil {
return
}
if container.SecurityContext.RunAsUser != nil && *container.SecurityContext.RunAsUser == 0 {
return
}
if (container.SecurityContext.Privileged == nil || !*container.SecurityContext.Privileged) &&
(container.SecurityContext.Capabilities == nil || len(container.SecurityContext.Capabilities.Add) == 0) {
return
}
_, _ = fmt.Fprintln(o.ErrOut, `Warning: Non-root user is configured for the entire target Pod, and some capabilities granted by debug profile may not work. Please consider using "--custom" with a custom profile that specifies "securityContext.runAsUser: 0".`)
}
// generateDebugContainer returns a debugging pod and an EphemeralContainer suitable for use as a debug container // generateDebugContainer returns a debugging pod and an EphemeralContainer suitable for use as a debug container
// in the given pod. // in the given pod.
func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) (*corev1.Pod, *corev1.EphemeralContainer, error) { func (o *DebugOptions) generateDebugContainer(pod *corev1.Pod) (*corev1.Pod, *corev1.EphemeralContainer, error) {
@ -727,9 +683,6 @@ func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, err
p := &corev1.Pod{ p := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: pn, Name: pn,
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -989,12 +942,12 @@ func (o *DebugOptions) handleAttachPod(ctx context.Context, restClientGetter gen
} }
if status.State.Terminated != nil { if status.State.Terminated != nil {
klog.V(1).Info("Ephemeral container terminated, falling back to logs") klog.V(1).Info("Ephemeral container terminated, falling back to logs")
return logOpts(ctx, restClientGetter, pod, opts) return logOpts(restClientGetter, pod, opts)
} }
if err := opts.Run(); err != nil { if err := opts.Run(); err != nil {
fmt.Fprintf(opts.ErrOut, "warning: couldn't attach to pod/%s, falling back to streaming logs: %v\n", podName, err) fmt.Fprintf(opts.ErrOut, "warning: couldn't attach to pod/%s, falling back to streaming logs: %v\n", podName, err)
return logOpts(ctx, restClientGetter, pod, opts) return logOpts(restClientGetter, pod, opts)
} }
return nil return nil
} }
@ -1012,7 +965,7 @@ func getContainerStatusByName(pod *corev1.Pod, containerName string) *corev1.Con
} }
// logOpts logs output from opts to the pods log. // logOpts logs output from opts to the pods log.
func logOpts(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error { func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Pod, opts *attach.AttachOptions) error {
ctrName, err := opts.GetContainerName(pod) ctrName, err := opts.GetContainerName(pod)
if err != nil { if err != nil {
return err return err
@ -1023,7 +976,7 @@ func logOpts(ctx context.Context, restClientGetter genericclioptions.RESTClientG
return err return err
} }
for _, request := range requests { for _, request := range requests {
if err := logs.DefaultConsumeRequest(ctx, request, opts.Out); err != nil { if err := logs.DefaultConsumeRequest(request, opts.Out); err != nil {
return err return err
} }
} }

View File

@ -1468,9 +1468,6 @@ func TestGenerateNodeDebugPod(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1525,9 +1522,6 @@ func TestGenerateNodeDebugPod(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1584,9 +1578,6 @@ func TestGenerateNodeDebugPod(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1640,9 +1631,6 @@ func TestGenerateNodeDebugPod(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1695,9 +1683,6 @@ func TestGenerateNodeDebugPod(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1738,9 +1723,6 @@ func TestGenerateNodeDebugPod(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1789,9 +1771,6 @@ func TestGenerateNodeDebugPod(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1884,9 +1863,6 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1940,9 +1916,6 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: "node-debugger-node-XXX-1", Name: "node-debugger-node-XXX-1",
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
}, },
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
@ -1999,11 +1972,6 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
}, },
}, },
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
},
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
{ {
@ -2067,11 +2035,6 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
}, },
}, },
expected: &corev1.Pod{ expected: &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
"app.kubernetes.io/managed-by": "kubectl-debug",
},
},
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
Containers: []corev1.Container{ Containers: []corev1.Container{
{ {

View File

@ -103,10 +103,7 @@ var (
kubectl delete pod foo --force kubectl delete pod foo --force
# Delete all pods # Delete all pods
kubectl delete pods --all kubectl delete pods --all`))
# Delete all pods only if the user confirms the deletion
kubectl delete pods --all --interactive`))
) )
type DeleteOptions struct { type DeleteOptions struct {
@ -506,10 +503,6 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) {
operation = "force deleted" operation = "force deleted"
} }
if info.Namespaced() {
operation = fmt.Sprintf("%s from %s namespace", operation, info.Namespace)
}
switch o.DryRunStrategy { switch o.DryRunStrategy {
case cmdutil.DryRunClient: case cmdutil.DryRunClient:
operation = fmt.Sprintf("%s (dry run)", operation) operation = fmt.Sprintf("%s (dry run)", operation)
@ -528,7 +521,7 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) {
} }
func (o *DeleteOptions) confirmation(infos []*resource.Info) bool { func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos)) //nolint:errcheck fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos))
for _, info := range infos { for _, info := range infos {
groupKind := info.Mapping.GroupVersionKind groupKind := info.Mapping.GroupVersionKind
kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group) kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
@ -536,11 +529,11 @@ func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
kindString = strings.ToLower(groupKind.Kind) kindString = strings.ToLower(groupKind.Kind)
} }
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name) //nolint:errcheck fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
} }
fmt.Fprint(o.Out, i18n.T("Do you want to continue?")+" (y/N): ") //nolint:errcheck fmt.Fprint(o.Out, i18n.T("Do you want to continue?")+" (y/n): ")
var input string var input string
_, err := fmt.Fscanln(o.In, &input) _, err := fmt.Fscan(o.In, &input)
if err != nil { if err != nil {
return false return false
} }

View File

@ -375,7 +375,7 @@ func TestDeleteObjectWithInteractive(t *testing.T) {
} }
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/N): replicationcontroller/redis-master\n" { if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): replicationcontroller/redis-master\n" {
t.Errorf("unexpected output: %s", buf.String()) t.Errorf("unexpected output: %s", buf.String())
} }
@ -396,7 +396,7 @@ func TestDeleteObjectWithInteractive(t *testing.T) {
} }
cmd.Run(cmd, []string{}) cmd.Run(cmd, []string{})
if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/N): deletion is cancelled\n" { if buf.String() != "You are about to delete the following 1 resource(s):\nreplicationcontroller/redis-master\nDo you want to continue? (y/n): deletion is cancelled\n" {
t.Errorf("unexpected output: %s", buf.String()) t.Errorf("unexpected output: %s", buf.String())
} }
if buf.String() == ": replicationcontroller/redis-master\n" { if buf.String() == ": replicationcontroller/redis-master\n" {
@ -972,39 +972,3 @@ func TestResourceErrors(t *testing.T) {
}) })
} }
} }
func TestDeleteMessageOutput(t *testing.T) {
cmdtesting.InitTestErrorHandler(t)
_, _, rc := cmdtesting.TestData()
tf := cmdtesting.NewTestFactory().WithNamespace("test-specific")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test-specific/replicationcontrollers/redis-master" && m == "DELETE":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &rc.Items[0])}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
}
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdDelete(tf, streams)
err := cmd.Flags().Set("filename", "../../../testdata/redis-master-controller.yaml")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
cmd.Run(cmd, []string{})
if buf.String() != "replicationcontroller \"redis-master\" deleted from test-specific namespace\n" {
t.Errorf("unexpected output: %s", buf.String())
}
}

View File

@ -193,7 +193,7 @@ func (o *DescribeOptions) Run() error {
allErrs = append(allErrs, err) allErrs = append(allErrs, err)
} }
errs := sets.New[string]() errs := sets.NewString()
first := true first := true
for _, info := range infos { for _, info := range infos {
mapping := info.ResourceMapping() mapping := info.ResourceMapping()

View File

@ -64,7 +64,6 @@ func TestDiffProgram(t *testing.T) {
externalDiffCommands := [3]string{"diff", "diff -ruN", "diff --report-identical-files"} externalDiffCommands := [3]string{"diff", "diff -ruN", "diff --report-identical-files"}
t.Setenv("LANG", "C") t.Setenv("LANG", "C")
t.Setenv("LANGUAGE", "en_US")
for i, c := range externalDiffCommands { for i, c := range externalDiffCommands {
t.Setenv("KUBECTL_EXTERNAL_DIFF", c) t.Setenv("KUBECTL_EXTERNAL_DIFF", c)

View File

@ -19,7 +19,6 @@ package drain
import ( import (
"errors" "errors"
"fmt" "fmt"
"time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -153,7 +152,6 @@ func NewDrainCmdOptions(f cmdutil.Factory, ioStreams genericiooptions.IOStreams)
IOStreams: ioStreams, IOStreams: ioStreams,
drainer: &drain.Helper{ drainer: &drain.Helper{
GracePeriodSeconds: -1, GracePeriodSeconds: -1,
EvictErrorRetryDelay: 5 * time.Second,
Out: ioStreams.Out, Out: ioStreams.Out,
ErrOut: ioStreams.ErrOut, ErrOut: ioStreams.ErrOut,
ChunkSize: cmdutil.DefaultChunkSize, ChunkSize: cmdutil.DefaultChunkSize,
@ -328,7 +326,7 @@ func (o *DrainCmdOptions) RunDrain() error {
return err return err
} }
drainedNodes := sets.New[string]() drainedNodes := sets.NewString()
var fatal []error var fatal []error
remainingNodes := []string{} remainingNodes := []string{}

View File

@ -103,6 +103,6 @@ func NewCmdEdit(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra.
"Defaults to the line ending native to your platform.") "Defaults to the line ending native to your platform.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-edit") cmdutil.AddFieldManagerFlagVar(cmd, &o.FieldManager, "kubectl-edit")
cmdutil.AddApplyAnnotationVarFlags(cmd, &o.ApplyAnnotation) cmdutil.AddApplyAnnotationVarFlags(cmd, &o.ApplyAnnotation)
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, edit will operate on the subresource of the requested object.") cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, edit will operate on the subresource of the requested object.", editor.SupportedSubresources...)
return cmd return cmd
} }

View File

@ -30,7 +30,8 @@ import (
"github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp"
"github.com/spf13/cobra" "github.com/spf13/cobra"
yaml "go.yaml.in/yaml/v2" yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/genericiooptions"
@ -172,7 +173,7 @@ func TestEdit(t *testing.T) {
t.Setenv("KUBE_EDITOR", "testdata/test_editor.sh") t.Setenv("KUBE_EDITOR", "testdata/test_editor.sh")
t.Setenv("KUBE_EDITOR_CALLBACK", server.URL+"/callback") t.Setenv("KUBE_EDITOR_CALLBACK", server.URL+"/callback")
testcases := sets.New[string]() testcases := sets.NewString()
filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error { filepath.Walk("testdata", func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return err return err
@ -194,7 +195,7 @@ func TestEdit(t *testing.T) {
t.Fatalf("Error locating edit testcases") t.Fatalf("Error locating edit testcases")
} }
for _, testcaseName := range testcases.UnsortedList() { for _, testcaseName := range testcases.List() {
t.Run(testcaseName, func(t *testing.T) { t.Run(testcaseName, func(t *testing.T) {
i = 0 i = 0
name = testcaseName name = testcaseName

View File

@ -25,7 +25,7 @@ import (
"os" "os"
"strings" "strings"
yaml "go.yaml.in/yaml/v2" yaml "gopkg.in/yaml.v2"
) )
type EditTestCase struct { type EditTestCase struct {

View File

@ -3,7 +3,7 @@
"kind": "Service", "kind": "Service",
"metadata": { "metadata": {
"annotations": { "annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" "kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"creationTimestamp\":null,\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
}, },
"creationTimestamp": "2017-02-27T19:40:53Z", "creationTimestamp": "2017-02-27T19:40:53Z",
"labels": { "labels": {

View File

@ -7,7 +7,7 @@ kind: Service
metadata: metadata:
annotations: annotations:
kubectl.kubernetes.io/last-applied-configuration: | kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}} {"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: "2017-02-27T19:40:53Z" creationTimestamp: "2017-02-27T19:40:53Z"
labels: labels:
app: svc1 app: svc1

View File

@ -7,7 +7,7 @@ kind: Service
metadata: metadata:
annotations: annotations:
kubectl.kubernetes.io/last-applied-configuration: | kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}} {"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: "2017-02-27T19:40:53Z" creationTimestamp: "2017-02-27T19:40:53Z"
labels: labels:
app: svc1 app: svc1

View File

@ -33,6 +33,7 @@
}, },
"template": { "template": {
"metadata": { "metadata": {
"creationTimestamp": null,
"labels": { "labels": {
"app": "nginx" "app": "nginx"
} }

View File

@ -29,6 +29,7 @@ spec:
type: RollingUpdate type: RollingUpdate
template: template:
metadata: metadata:
creationTimestamp: null
labels: labels:
app: nginx app: nginx
spec: spec:

View File

@ -29,6 +29,7 @@ spec:
type: RollingUpdate type: RollingUpdate
template: template:
metadata: metadata:
creationTimestamp: null
labels: labels:
app: nginx app: nginx
spec: spec:

View File

@ -33,6 +33,7 @@
}, },
"template": { "template": {
"metadata": { "metadata": {
"creationTimestamp": null,
"labels": { "labels": {
"app": "nginx" "app": "nginx"
} }

View File

@ -3,7 +3,7 @@
"kind": "Service", "kind": "Service",
"metadata": { "metadata": {
"annotations": { "annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" "kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"creationTimestamp\":null,\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
}, },
"creationTimestamp": "2017-02-27T19:40:53Z", "creationTimestamp": "2017-02-27T19:40:53Z",
"labels": { "labels": {

View File

@ -7,7 +7,7 @@ kind: Service
metadata: metadata:
annotations: annotations:
kubectl.kubernetes.io/last-applied-configuration: | kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}} {"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: "2017-02-27T19:40:53Z" creationTimestamp: "2017-02-27T19:40:53Z"
labels: labels:
app: svc1 app: svc1

View File

@ -7,7 +7,7 @@ kind: Service
metadata: metadata:
annotations: annotations:
kubectl.kubernetes.io/last-applied-configuration: | kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}} {"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: "2017-02-27T19:40:53Z" creationTimestamp: "2017-02-27T19:40:53Z"
labels: labels:
app: svc1 app: svc1

View File

@ -3,7 +3,7 @@
"kind": "Service", "kind": "Service",
"metadata": { "metadata": {
"annotations": { "annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n" "kubectl.kubernetes.io/last-applied-configuration": "{\"kind\":\"Service\",\"apiVersion\":\"v1\",\"metadata\":{\"name\":\"svc1\",\"creationTimestamp\":null,\"labels\":{\"app\":\"svc1\"}},\"spec\":{\"ports\":[{\"name\":\"80\",\"protocol\":\"TCP\",\"port\":80,\"targetPort\":80}],\"selector\":{\"app\":\"svc1\"},\"type\":\"ClusterIP\"},\"status\":{\"loadBalancer\":{}}}\n"
}, },
"creationTimestamp": "2017-02-27T19:40:53Z", "creationTimestamp": "2017-02-27T19:40:53Z",
"labels": { "labels": {

View File

@ -7,7 +7,7 @@ kind: Service
metadata: metadata:
annotations: annotations:
kubectl.kubernetes.io/last-applied-configuration: | kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}} {"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: "2017-02-27T19:40:53Z" creationTimestamp: "2017-02-27T19:40:53Z"
labels: labels:
app: svc1 app: svc1

View File

@ -7,7 +7,7 @@ kind: Service
metadata: metadata:
annotations: annotations:
kubectl.kubernetes.io/last-applied-configuration: | kubectl.kubernetes.io/last-applied-configuration: |
{"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}} {"kind":"Service","apiVersion":"v1","metadata":{"name":"svc1","creationTimestamp":null,"labels":{"app":"svc1"}},"spec":{"ports":[{"name":"80","protocol":"TCP","port":80,"targetPort":80}],"selector":{"app":"svc1"},"type":"ClusterIP"},"status":{"loadBalancer":{}}}
creationTimestamp: "2017-02-27T19:40:53Z" creationTimestamp: "2017-02-27T19:40:53Z"
labels: labels:
app: svc1 app: svc1

View File

@ -18,12 +18,11 @@ package events
import ( import (
"bytes" "bytes"
"testing"
"time"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"testing"
"time"
) )
func TestPrintObj(t *testing.T) { func TestPrintObj(t *testing.T) {
@ -51,7 +50,7 @@ func TestPrintObj(t *testing.T) {
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -60,7 +59,7 @@ func TestPrintObj(t *testing.T) {
}, },
}, },
expected: `LAST SEEN TYPE REASON OBJECT MESSAGE expected: `LAST SEEN TYPE REASON OBJECT MESSAGE
12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
`, `,
}, },
{ {
@ -84,7 +83,7 @@ func TestPrintObj(t *testing.T) {
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -106,7 +105,7 @@ func TestPrintObj(t *testing.T) {
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-15 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-15 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -117,8 +116,8 @@ func TestPrintObj(t *testing.T) {
}, },
}, },
expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica set bar-002 from 0 to 1 bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica set bar-002 to 1
`, `,
}, },
{ {
@ -140,7 +139,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -148,7 +147,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)), LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
}, },
}, },
expected: "12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1\n", expected: "12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1\n",
}, },
{ {
printer: EventPrinter{ printer: EventPrinter{
@ -169,7 +168,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -178,7 +177,7 @@ bar 11m (x3 over 15m) Normal ScalingReplicaSet Deployment/bar2 Scaled up replica
}, },
}, },
expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE expected: `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
`, `,
}, },
{ {
@ -200,7 +199,7 @@ foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-20 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -208,7 +207,7 @@ foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica
LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)), LastObservedTime: metav1.NewMicroTime(time.Now().Add(-12 * time.Minute)),
}, },
}, },
expected: `foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 expected: `foo 12m (x3 over 20m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
`, `,
}, },
{ {

View File

@ -188,7 +188,7 @@ func (flags *EventsFlags) ToOptions() (*EventsOptions, error) {
} }
if len(o.FilterTypes) > 0 { if len(o.FilterTypes) > 0 {
o.FilterTypes = sets.List(sets.New[string](o.FilterTypes...)) o.FilterTypes = sets.NewString(o.FilterTypes...).List()
} }
var printer printers.ResourcePrinter var printer printers.ResourcePrinter

View File

@ -50,7 +50,7 @@ func getFakeEvents() *corev1.EventList {
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-30 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-30 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -72,7 +72,7 @@ func getFakeEvents() *corev1.EventList {
}, },
Type: corev1.EventTypeWarning, Type: corev1.EventTypeWarning,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-28 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-28 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -94,7 +94,7 @@ func getFakeEvents() *corev1.EventList {
}, },
Type: corev1.EventTypeNormal, Type: corev1.EventTypeNormal,
Reason: "ScalingReplicaSet", Reason: "ScalingReplicaSet",
Message: "Scaled up replica set bar-002 from 0 to 1", Message: "Scaled up replica set bar-002 to 1",
ReportingController: "deployment-controller", ReportingController: "deployment-controller",
EventTime: metav1.NewMicroTime(time.Now().Add(-25 * time.Minute)), EventTime: metav1.NewMicroTime(time.Now().Add(-25 * time.Minute)),
Series: &corev1.EventSeries{ Series: &corev1.EventSeries{
@ -135,9 +135,9 @@ func TestEventIsSorted(t *testing.T) {
} }
expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
` `
if e, a := expected, buf.String(); e != a { if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a) t.Errorf("expected\n%v\ngot\n%v", e, a)
@ -172,9 +172,9 @@ func TestEventNoHeaders(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
expected := `foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 expected := `foo 20m (x3 over 30m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 otherfoo 15m (x3 over 25m) Normal ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
` `
if e, a := expected, buf.String(); e != a { if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a) t.Errorf("expected\n%v\ngot\n%v", e, a)
@ -211,7 +211,7 @@ func TestEventFiltered(t *testing.T) {
} }
expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE expected := `NAMESPACE LAST SEEN TYPE REASON OBJECT MESSAGE
foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 from 0 to 1 foo 18m (x3 over 28m) Warning ScalingReplicaSet Deployment/bar Scaled up replica set bar-002 to 1
` `
if e, a := expected, buf.String(); e != a { if e, a := expected, buf.String(); e != a {
t.Errorf("expected\n%v\ngot\n%v", e, a) t.Errorf("expected\n%v\ngot\n%v", e, a)

View File

@ -114,26 +114,18 @@ func NewCmdExec(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Co
// RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing // RemoteExecutor defines the interface accepted by the Exec command - provided for test stubbing
type RemoteExecutor interface { type RemoteExecutor interface {
// Execute supports executing remote command in a pod.
Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error
// ExecuteWithContext, in contrast to Execute, supports stopping the remote command via context cancellation.
ExecuteWithContext(ctx context.Context, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error
} }
// DefaultRemoteExecutor is the standard implementation of remote command execution // DefaultRemoteExecutor is the standard implementation of remote command execution
type DefaultRemoteExecutor struct{} type DefaultRemoteExecutor struct{}
func (d *DefaultRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error { func (*DefaultRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
return d.ExecuteWithContext(context.Background(), url, config, stdin, stdout, stderr, tty, terminalSizeQueue)
}
func (*DefaultRemoteExecutor) ExecuteWithContext(ctx context.Context, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
exec, err := createExecutor(url, config) exec, err := createExecutor(url, config)
if err != nil { if err != nil {
return err return err
} }
return exec.StreamWithContext(ctx, remotecommand.StreamOptions{ return exec.StreamWithContext(context.Background(), remotecommand.StreamOptions{
Stdin: stdin, Stdin: stdin,
Stdout: stdout, Stdout: stdout,
Stderr: stderr, Stderr: stderr,
@ -366,9 +358,7 @@ func (p *ExecOptions) Run() error {
var sizeQueue remotecommand.TerminalSizeQueue var sizeQueue remotecommand.TerminalSizeQueue
if t.Raw { if t.Raw {
// this call spawns a goroutine to monitor/update the terminal size // this call spawns a goroutine to monitor/update the terminal size
sizeQueue = &terminalSizeQueueAdapter{ sizeQueue = t.MonitorSize(t.GetSize())
delegate: t.MonitorSize(t.GetSize()),
}
// unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is // unset p.Err if it was previously set because both stdout and stderr go over p.Out when tty is
// true // true
@ -405,18 +395,3 @@ func (p *ExecOptions) Run() error {
return nil return nil
} }
type terminalSizeQueueAdapter struct {
delegate term.TerminalSizeQueue
}
func (a *terminalSizeQueueAdapter) Next() *remotecommand.TerminalSize {
next := a.delegate.Next()
if next == nil {
return nil
}
return &remotecommand.TerminalSize{
Width: next.Width,
Height: next.Height,
}
}

View File

@ -18,7 +18,6 @@ package exec
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
@ -46,10 +45,6 @@ type fakeRemoteExecutor struct {
} }
func (f *fakeRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error { func (f *fakeRemoteExecutor) Execute(url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
return f.ExecuteWithContext(context.Background(), url, config, stdin, stdout, stderr, tty, terminalSizeQueue)
}
func (f *fakeRemoteExecutor) ExecuteWithContext(ctx context.Context, url *url.URL, config *restclient.Config, stdin io.Reader, stdout, stderr io.Writer, tty bool, terminalSizeQueue remotecommand.TerminalSizeQueue) error {
f.url = url f.url = url
return f.execErr return f.execErr
} }

View File

@ -59,112 +59,82 @@ var (
# Get the documentation of resources in different format # Get the documentation of resources in different format
kubectl explain deployment --output=plaintext-openapiv2`)) kubectl explain deployment --output=plaintext-openapiv2`))
)
const (
plaintextTemplateName = "plaintext" plaintextTemplateName = "plaintext"
plaintextOpenAPIV2TemplateName = "plaintext-openapiv2" plaintextOpenAPIV2TemplateName = "plaintext-openapiv2"
) )
// ExplainFlags directly reflect the information that CLI is gathering via flags.
// They will be converted to Options, which reflect the runtime requirements for
// the command.
type ExplainFlags struct {
APIVersion string
OutputFormat string
Recursive bool
genericiooptions.IOStreams
}
// NewExplainFlags returns a default ExplainFlags
func NewExplainFlags(streams genericiooptions.IOStreams) *ExplainFlags {
return &ExplainFlags{
OutputFormat: plaintextTemplateName,
IOStreams: streams,
}
}
// AddFlags registers flags for a cli
func (flags *ExplainFlags) AddFlags(cmd *cobra.Command) {
cmd.Flags().BoolVar(&flags.Recursive, "recursive", flags.Recursive, "Print the fields of fields (Currently only 1 level deep)")
cmd.Flags().StringVar(&flags.APIVersion, "api-version", flags.APIVersion, "Get different explanations for particular API version (API group/version)")
cmd.Flags().StringVarP(&flags.OutputFormat, "output", "o", plaintextTemplateName, "Format in which to render the schema (plaintext, plaintext-openapiv2)")
}
// ToOptions converts from CLI inputs to runtime input
func (flags *ExplainFlags) ToOptions(f cmdutil.Factory, parent string, args []string) (*ExplainOptions, error) {
mapper, err := f.ToRESTMapper()
if err != nil {
return nil, err
}
// Only openapi v3 needs the discovery client.
openAPIV3Client, err := f.OpenAPIV3Client()
if err != nil {
return nil, err
}
o := &ExplainOptions{
IOStreams: flags.IOStreams,
Recursive: flags.Recursive,
APIVersion: flags.APIVersion,
OutputFormat: flags.OutputFormat,
CmdParent: parent,
args: args,
Mapper: mapper,
openAPIGetter: f,
OpenAPIV3Client: openAPIV3Client,
}
return o, nil
}
// NewCmdExplain returns a cobra command for swagger docs
func NewCmdExplain(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
flags := NewExplainFlags(streams)
cmd := &cobra.Command{
Use: "explain TYPE [--recursive=FALSE|TRUE] [--api-version=api-version-group] [-o|--output=plaintext|plaintext-openapiv2]",
DisableFlagsInUseLine: true,
Short: i18n.T("Get documentation for a resource"),
Long: explainLong + "\n\n" + cmdutil.SuggestAPIResources(parent),
Example: explainExamples,
Run: func(cmd *cobra.Command, args []string) {
o, err := flags.ToOptions(f, parent, args)
cmdutil.CheckErr(err)
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
flags.AddFlags(cmd)
return cmd
}
type ExplainOptions struct { type ExplainOptions struct {
genericiooptions.IOStreams genericiooptions.IOStreams
Recursive bool
APIVersion string
// Name of the template to use with the openapiv3 template renderer.
OutputFormat string
CmdParent string CmdParent string
APIVersion string
Recursive bool
args []string args []string
Mapper meta.RESTMapper Mapper meta.RESTMapper
openAPIGetter openapi.OpenAPIResourcesGetter openAPIGetter openapi.OpenAPIResourcesGetter
// Name of the template to use with the openapiv3 template renderer.
OutputFormat string
// Client capable of fetching openapi documents from the user's cluster // Client capable of fetching openapi documents from the user's cluster
OpenAPIV3Client openapiclient.Client OpenAPIV3Client openapiclient.Client
} }
func NewExplainOptions(parent string, streams genericiooptions.IOStreams) *ExplainOptions {
return &ExplainOptions{
IOStreams: streams,
CmdParent: parent,
OutputFormat: plaintextTemplateName,
}
}
// NewCmdExplain returns a cobra command for swagger docs
func NewCmdExplain(parent string, f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
o := NewExplainOptions(parent, streams)
cmd := &cobra.Command{
Use: "explain TYPE [--recursive=FALSE|TRUE] [--api-version=api-version-group] [--output=plaintext|plaintext-openapiv2]",
DisableFlagsInUseLine: true,
Short: i18n.T("Get documentation for a resource"),
Long: explainLong + "\n\n" + cmdutil.SuggestAPIResources(parent),
Example: explainExamples,
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, cmd, args))
cmdutil.CheckErr(o.Validate())
cmdutil.CheckErr(o.Run())
},
}
cmd.Flags().BoolVar(&o.Recursive, "recursive", o.Recursive, "When true, print the name of all the fields recursively. Otherwise, print the available fields with their description.")
cmd.Flags().StringVar(&o.APIVersion, "api-version", o.APIVersion, "Use given api-version (group/version) of the resource.")
// Only enable --output as a valid flag if the feature is enabled
cmd.Flags().StringVar(&o.OutputFormat, "output", plaintextTemplateName, "Format in which to render the schema. Valid values are: (plaintext, plaintext-openapiv2).")
return cmd
}
func (o *ExplainOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
var err error
o.Mapper, err = f.ToRESTMapper()
if err != nil {
return err
}
// Only openapi v3 needs the discovery client.
o.OpenAPIV3Client, err = f.OpenAPIV3Client()
if err != nil {
return err
}
// Lazy-load the OpenAPI V2 Resources, so they're not loaded when using OpenAPI V3.
o.openAPIGetter = f
o.args = args
return nil
}
func (o *ExplainOptions) Validate() error { func (o *ExplainOptions) Validate() error {
if len(o.args) == 0 { if len(o.args) == 0 {
return fmt.Errorf("You must specify the type of resource to explain. %s\n", cmdutil.SuggestAPIResources(o.CmdParent)) return fmt.Errorf("You must specify the type of resource to explain. %s\n", cmdutil.SuggestAPIResources(o.CmdParent))

View File

@ -57,9 +57,9 @@ func TestExplainInvalidArgs(t *testing.T) {
tf := cmdtesting.NewTestFactory() tf := cmdtesting.NewTestFactory()
defer tf.Cleanup() defer tf.Cleanup()
flags := explain.NewExplainFlags(genericiooptions.NewTestIOStreamsDiscard()) opts := explain.NewExplainOptions("kubectl", genericiooptions.NewTestIOStreamsDiscard())
cmd := explain.NewCmdExplain("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
opts, err := flags.ToOptions(tf, "kubectl", []string{}) err := opts.Complete(tf, cmd, []string{})
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }
@ -69,7 +69,7 @@ func TestExplainInvalidArgs(t *testing.T) {
t.Error("unexpected non-error") t.Error("unexpected non-error")
} }
opts, err = flags.ToOptions(tf, "kubectl", []string{"resource1", "resource2"}) err = opts.Complete(tf, cmd, []string{"resource1", "resource2"})
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }
@ -84,9 +84,9 @@ func TestExplainNotExistResource(t *testing.T) {
tf := cmdtesting.NewTestFactory() tf := cmdtesting.NewTestFactory()
defer tf.Cleanup() defer tf.Cleanup()
flags := explain.NewExplainFlags(genericiooptions.NewTestIOStreamsDiscard()) opts := explain.NewExplainOptions("kubectl", genericiooptions.NewTestIOStreamsDiscard())
cmd := explain.NewCmdExplain("kubectl", tf, genericiooptions.NewTestIOStreamsDiscard())
opts, err := flags.ToOptions(tf, "kubectl", []string{"foo"}) err := opts.Complete(tf, cmd, []string{"foo"})
if err != nil { if err != nil {
t.Fatalf("unexpected error %v", err) t.Fatalf("unexpected error %v", err)
} }

View File

@ -206,7 +206,7 @@ func (flags *ExposeServiceFlags) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&flags.Port, "port", flags.Port, i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified")) cmd.Flags().StringVar(&flags.Port, "port", flags.Port, i18n.T("The port that the service should serve on. Copied from the resource being exposed, if unspecified"))
cmd.Flags().StringVar(&flags.Type, "type", flags.Type, i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'.")) cmd.Flags().StringVar(&flags.Type, "type", flags.Type, i18n.T("Type for this service: ClusterIP, NodePort, LoadBalancer, or ExternalName. Default is 'ClusterIP'."))
cmd.Flags().StringVar(&flags.LoadBalancerIP, "load-balancer-ip", flags.LoadBalancerIP, i18n.T("IP to assign to the LoadBalancer. If empty, an ephemeral IP will be created and used (cloud-provider specific).")) cmd.Flags().StringVar(&flags.LoadBalancerIP, "load-balancer-ip", flags.LoadBalancerIP, i18n.T("IP to assign to the LoadBalancer. If empty, an ephemeral IP will be created and used (cloud-provider specific)."))
cmd.Flags().StringVar(&flags.Selector, "selector", flags.Selector, i18n.T("A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the resource being exposed.")) cmd.Flags().StringVar(&flags.Selector, "selector", flags.Selector, i18n.T("A label selector to use for this service. Only equality-based selector requirements are supported. If empty (the default) infer the selector from the replication controller or replica set.)"))
cmd.Flags().StringVarP(&flags.Labels, "labels", "l", flags.Labels, "Labels to apply to the service created by this call.") cmd.Flags().StringVarP(&flags.Labels, "labels", "l", flags.Labels, "Labels to apply to the service created by this call.")
cmd.Flags().StringVar(&flags.TargetPort, "target-port", flags.TargetPort, i18n.T("Name or number for the port on the container that the service should direct traffic to. Optional.")) cmd.Flags().StringVar(&flags.TargetPort, "target-port", flags.TargetPort, i18n.T("Name or number for the port on the container that the service should direct traffic to. Optional."))
cmd.Flags().StringVar(&flags.ExternalIP, "external-ip", flags.ExternalIP, i18n.T("Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP.")) cmd.Flags().StringVar(&flags.ExternalIP, "external-ip", flags.ExternalIP, i18n.T("Additional external IP address (not managed by Kubernetes) to accept for the service. If this IP is routed to a node, the service can be accessed by this IP in addition to its generated service IP."))

View File

@ -694,6 +694,7 @@ func TestExposeOverride(t *testing.T) {
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: Service kind: Service
metadata: metadata:
creationTimestamp: null
labels: labels:
svc: test svc: test
name: foo name: foo
@ -716,6 +717,7 @@ status:
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: Service kind: Service
metadata: metadata:
creationTimestamp: null
labels: labels:
svc: test svc: test
name: foo name: foo
@ -743,6 +745,7 @@ status:
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: Service kind: Service
metadata: metadata:
creationTimestamp: null
labels: labels:
svc: test svc: test
name: foo name: foo
@ -770,6 +773,7 @@ status:
expected: `apiVersion: v1 expected: `apiVersion: v1
kind: Service kind: Service
metadata: metadata:
creationTimestamp: null
labels: labels:
svc: test svc: test
name: foo name: foo

View File

@ -47,6 +47,7 @@ import (
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/interrupt" "k8s.io/kubectl/pkg/util/interrupt"
"k8s.io/kubectl/pkg/util/slice"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
) )
@ -144,6 +145,8 @@ const (
useServerPrintColumns = "server-print" useServerPrintColumns = "server-print"
) )
var supportedSubresources = []string{"status", "scale"}
// NewGetOptions returns a GetOptions with default chunk size 500. // NewGetOptions returns a GetOptions with default chunk size 500.
func NewGetOptions(parent string, streams genericiooptions.IOStreams) *GetOptions { func NewGetOptions(parent string, streams genericiooptions.IOStreams) *GetOptions {
return &GetOptions{ return &GetOptions{
@ -182,14 +185,14 @@ func NewCmdGet(parent string, f cmdutil.Factory, streams genericiooptions.IOStre
cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes.") cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes.")
cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.") cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.")
cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.") cmd.Flags().BoolVar(&o.OutputWatchEvents, "output-watch-events", o.OutputWatchEvents, "Output watch event objects when --watch or --watch-only is used. Existing objects are output as initial ADDED events.")
cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If set to true, suppresses NotFound error for specific objects that do not exist. Using this flag with commands that query for collections of resources has no effect when no resources are found.") cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") cmd.Flags().BoolVarP(&o.AllNamespaces, "all-namespaces", "A", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
addServerPrintColumnFlags(cmd, o) addServerPrintColumnFlags(cmd, o)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.") cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.")
cmdutil.AddChunkSizeFlag(cmd, &o.ChunkSize) cmdutil.AddChunkSizeFlag(cmd, &o.ChunkSize)
cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector) cmdutil.AddLabelSelectorFlagVar(cmd, &o.LabelSelector)
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, gets the subresource of the requested object.") cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, gets the subresource of the requested object.", supportedSubresources...)
return cmd return cmd
} }
@ -316,6 +319,9 @@ func (o *GetOptions) Validate() error {
if o.OutputWatchEvents && !(o.Watch || o.WatchOnly) { if o.OutputWatchEvents && !(o.Watch || o.WatchOnly) {
return fmt.Errorf("--output-watch-events option can only be used with --watch or --watch-only") return fmt.Errorf("--output-watch-events option can only be used with --watch or --watch-only")
} }
if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) {
return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources)
}
return nil return nil
} }
@ -486,7 +492,7 @@ func (o *GetOptions) Run(f cmdutil.Factory, args []string) error {
} }
allErrs := []error{} allErrs := []error{}
errs := sets.New[string]() errs := sets.NewString()
infos, err := r.Infos() infos, err := r.Infos()
if err != nil { if err != nil {
allErrs = append(allErrs, err) allErrs = append(allErrs, err)
@ -623,10 +629,6 @@ func (o *GetOptions) watch(f cmdutil.Factory, args []string) error {
} }
infos, err := r.Infos() infos, err := r.Infos()
if err != nil { if err != nil {
// Ignore "NotFound" error when ignore-not-found is set to true
if apierrors.IsNotFound(err) && o.IgnoreNotFound {
return nil
}
return err return err
} }
if multipleGVKsRequested(infos) { if multipleGVKsRequested(infos) {

View File

@ -21,7 +21,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
cmdutil "k8s.io/kubectl/pkg/cmd/util"
"net/http" "net/http"
"reflect" "reflect"
"strings" "strings"
@ -712,7 +711,7 @@ func TestGetEmptyTable(t *testing.T) {
} }
} }
func TestGetNonExistObject(t *testing.T) { func TestGetObjectIgnoreNotFound(t *testing.T) {
cmdtesting.InitTestErrorHandler(t) cmdtesting.InitTestErrorHandler(t)
ns := &corev1.NamespaceList{ ns := &corev1.NamespaceList{
@ -746,63 +745,6 @@ func TestGetNonExistObject(t *testing.T) {
}), }),
} }
cmdutil.BehaviorOnFatal(func(str string, code int) {
expectedErr := "Error from server (NotFound): the server could not find the requested resource (get pods nonexistentpod)"
if str != expectedErr {
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
}
})
// Get nonexistentpod fails with above error message
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.Run(cmd, []string{"pods", "nonexistentpod"})
}
func TestGetNonExistObjectIgnoreNotFound(t *testing.T) {
cmdtesting.InitTestErrorHandler(t)
ns := &corev1.NamespaceList{
ListMeta: metav1.ListMeta{
ResourceVersion: "1",
},
Items: []corev1.Namespace{
{
ObjectMeta: metav1.ObjectMeta{Name: "testns", Namespace: "test", ResourceVersion: "11"},
Spec: corev1.NamespaceSpec{},
},
},
}
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/pods/nonexistentpod" && m == "GET":
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &ns.Items[0])}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
cmdutil.BehaviorOnFatal(func(str string, code int) {
expectedErr := ""
if str != expectedErr {
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
}
})
// Get nonexistentpod passes without error when setting ignore-not-found to true
streams, _, buf, _ := genericiooptions.NewTestIOStreams() streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams) cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOut(buf) cmd.SetOut(buf)
@ -1496,6 +1438,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Pod", "kind": "Pod",
"metadata": { "metadata": {
"creationTimestamp": null,
"name": "foo", "name": "foo",
"namespace": "test", "namespace": "test",
"resourceVersion": "10" "resourceVersion": "10"
@ -1514,6 +1457,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Pod", "kind": "Pod",
"metadata": { "metadata": {
"creationTimestamp": null,
"name": "bar", "name": "bar",
"namespace": "test", "namespace": "test",
"resourceVersion": "11" "resourceVersion": "11"
@ -1532,6 +1476,7 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
"apiVersion": "v1", "apiVersion": "v1",
"kind": "Service", "kind": "Service",
"metadata": { "metadata": {
"creationTimestamp": null,
"name": "baz", "name": "baz",
"namespace": "test", "namespace": "test",
"resourceVersion": "12" "resourceVersion": "12"
@ -2184,93 +2129,6 @@ foo <unknown>
} }
} }
func TestWatchNonExistObject(t *testing.T) {
pods, _ := watchTestData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/pods/nonexistentpod" && m == "GET":
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods[1])}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
cmdutil.BehaviorOnFatal(func(str string, code int) {
expectedErr := "Error from server (NotFound): the server could not find the requested resource (get pods nonexistentpod)"
if str != expectedErr {
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
}
})
// Get nonexistentpod fails with above error message
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.Flags().Set("watch", "true") //nolint:errcheck
cmd.Flags().Set("output", "yaml") //nolint:errcheck
cmd.Run(cmd, []string{"pods", "nonexistentpod"})
if buf.String() != "" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestWatchNonExistObjectIgnoreNotFound(t *testing.T) {
pods, _ := watchTestData()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
tf.UnstructuredClient = &fake.RESTClient{
NegotiatedSerializer: resource.UnstructuredPlusDefaultContentConfig().NegotiatedSerializer,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/pods/nonexistentpod" && m == "GET":
return &http.Response{StatusCode: http.StatusNotFound, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.StringBody("")}, nil
case p == "/api/v1/namespaces/test" && m == "GET":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, &pods[1])}, nil
default:
t.Fatalf("request url: %#v,and request: %#v", req.URL, req)
return nil, nil
}
}),
}
cmdutil.BehaviorOnFatal(func(str string, code int) {
expectedErr := ""
if str != expectedErr {
t.Errorf("unexpected error: %s\nexpected: %s", str, expectedErr)
}
})
// Get nonexistentpod passes without error when setting ignore-not-found to true
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdGet("kubectl", tf, streams)
cmd.SetOut(buf)
cmd.SetErr(buf)
cmd.Flags().Set("ignore-not-found", "true") //nolint:errcheck
cmd.Flags().Set("watch", "true") //nolint:errcheck
cmd.Flags().Set("output", "yaml") //nolint:errcheck
cmd.Run(cmd, []string{"pods", "nonexistentpod"})
if buf.String() != "" {
t.Errorf("unexpected output: %s", buf.String())
}
}
func TestWatchStatus(t *testing.T) { func TestWatchStatus(t *testing.T) {
pods, events := watchTestData() pods, events := watchTestData()
events = append(events, watch.Event{Type: "ERROR", Object: &metav1.Status{Status: "Failure", Reason: "InternalServerError", Message: "Something happened"}}) events = append(events, watch.Event{Type: "ERROR", Object: &metav1.Status{Status: "Failure", Reason: "InternalServerError", Message: "Something happened"}})
@ -2509,10 +2367,10 @@ DELETED test pod/foo 0/0 0 <unknown> <none>
}, },
{ {
format: "json", format: "json",
expected: `{"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"bar","namespace":"test","resourceVersion":"9"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}} expected: `{"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"bar","namespace":"test","resourceVersion":"9"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
{"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"foo","namespace":"test","resourceVersion":"10"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}} {"type":"ADDED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"foo","namespace":"test","resourceVersion":"10"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
{"type":"MODIFIED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"foo","namespace":"test","resourceVersion":"11"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}} {"type":"MODIFIED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"foo","namespace":"test","resourceVersion":"11"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
{"type":"DELETED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"name":"foo","namespace":"test","resourceVersion":"12"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}} {"type":"DELETED","object":{"apiVersion":"v1","kind":"Pod","metadata":{"creationTimestamp":null,"name":"foo","namespace":"test","resourceVersion":"12"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
`, `,
}, },
{ {
@ -2521,6 +2379,7 @@ DELETED test pod/foo 0/0 0 <unknown> <none>
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
name: bar name: bar
namespace: test namespace: test
resourceVersion: "9" resourceVersion: "9"
@ -2538,6 +2397,7 @@ object:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
name: foo name: foo
namespace: test namespace: test
resourceVersion: "10" resourceVersion: "10"
@ -2555,6 +2415,7 @@ object:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
name: foo name: foo
namespace: test namespace: test
resourceVersion: "11" resourceVersion: "11"
@ -2572,6 +2433,7 @@ object:
apiVersion: v1 apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
name: foo name: foo
namespace: test namespace: test
resourceVersion: "12" resourceVersion: "12"

View File

@ -41,7 +41,6 @@ import (
"k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/completion" "k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/interrupt"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
) )
@ -58,33 +57,15 @@ var (
# Return snapshot logs from pod nginx with only one container # Return snapshot logs from pod nginx with only one container
kubectl logs nginx kubectl logs nginx
# Return snapshot logs from pod nginx, prefixing each line with the source pod and container name
kubectl logs nginx --prefix
# Return snapshot logs from pod nginx, limiting output to 500 bytes
kubectl logs nginx --limit-bytes=500
# Return snapshot logs from pod nginx, waiting up to 20 seconds for it to start running.
kubectl logs nginx --pod-running-timeout=20s
# Return snapshot logs from pod nginx with multi containers # Return snapshot logs from pod nginx with multi containers
kubectl logs nginx --all-containers=true kubectl logs nginx --all-containers=true
# Return snapshot logs from all pods in the deployment nginx
kubectl logs deployment/nginx --all-pods=true
# Return snapshot logs from all containers in pods defined by label app=nginx # Return snapshot logs from all containers in pods defined by label app=nginx
kubectl logs -l app=nginx --all-containers=true kubectl logs -l app=nginx --all-containers=true
# Return snapshot logs from all pods defined by label app=nginx, limiting concurrent log requests to 10 pods
kubectl logs -l app=nginx --max-log-requests=10
# Return snapshot of previous terminated ruby container logs from pod web-1 # Return snapshot of previous terminated ruby container logs from pod web-1
kubectl logs -p -c ruby web-1 kubectl logs -p -c ruby web-1
# Begin streaming the logs from pod nginx, continuing even if errors occur
kubectl logs nginx -f --ignore-errors=true
# Begin streaming the logs of the ruby container in pod web-1 # Begin streaming the logs of the ruby container in pod web-1
kubectl logs -f -c ruby web-1 kubectl logs -f -c ruby web-1
@ -97,9 +78,6 @@ var (
# Show all logs from pod nginx written in the last hour # Show all logs from pod nginx written in the last hour
kubectl logs --since=1h nginx kubectl logs --since=1h nginx
# Show all logs with timestamps from pod nginx starting from August 30, 2024, at 06:00:00 UTC
kubectl logs nginx --since-time=2024-08-30T06:00:00Z --timestamps=true
# Show logs from a kubelet with an expired serving certificate # Show logs from a kubelet with an expired serving certificate
kubectl logs --insecure-skip-tls-verify-backend nginx kubectl logs --insecure-skip-tls-verify-backend nginx
@ -125,7 +103,7 @@ type LogsOptions struct {
Options runtime.Object Options runtime.Object
Resources []string Resources []string
ConsumeRequestFn func(context.Context, rest.ResponseWrapper, io.Writer) error ConsumeRequestFn func(rest.ResponseWrapper, io.Writer) error
// PodLogOptions // PodLogOptions
SinceTime string SinceTime string
@ -376,21 +354,14 @@ func (o LogsOptions) RunLogs() error {
len(requests), o.MaxFollowConcurrency, len(requests), o.MaxFollowConcurrency,
) )
} }
return o.parallelConsumeRequest(requests)
} }
ctx, cancel := context.WithCancel(context.Background()) return o.sequentialConsumeRequest(requests)
defer cancel()
intr := interrupt.New(nil, cancel)
return intr.Run(func() error {
if o.Follow && len(requests) > 1 {
return o.parallelConsumeRequest(ctx, requests)
} }
return o.sequentialConsumeRequest(ctx, requests) func (o LogsOptions) parallelConsumeRequest(requests map[corev1.ObjectReference]rest.ResponseWrapper) error {
})
}
func (o LogsOptions) parallelConsumeRequest(ctx context.Context, requests map[corev1.ObjectReference]rest.ResponseWrapper) error {
reader, writer := io.Pipe() reader, writer := io.Pipe()
wg := &sync.WaitGroup{} wg := &sync.WaitGroup{}
wg.Add(len(requests)) wg.Add(len(requests))
@ -398,7 +369,7 @@ func (o LogsOptions) parallelConsumeRequest(ctx context.Context, requests map[co
go func(objRef corev1.ObjectReference, request rest.ResponseWrapper) { go func(objRef corev1.ObjectReference, request rest.ResponseWrapper) {
defer wg.Done() defer wg.Done()
out := o.addPrefixIfNeeded(objRef, writer) out := o.addPrefixIfNeeded(objRef, writer)
if err := o.ConsumeRequestFn(ctx, request, out); err != nil { if err := o.ConsumeRequestFn(request, out); err != nil {
if !o.IgnoreLogErrors { if !o.IgnoreLogErrors {
writer.CloseWithError(err) writer.CloseWithError(err)
@ -421,10 +392,10 @@ func (o LogsOptions) parallelConsumeRequest(ctx context.Context, requests map[co
return err return err
} }
func (o LogsOptions) sequentialConsumeRequest(ctx context.Context, requests map[corev1.ObjectReference]rest.ResponseWrapper) error { func (o LogsOptions) sequentialConsumeRequest(requests map[corev1.ObjectReference]rest.ResponseWrapper) error {
for objRef, request := range requests { for objRef, request := range requests {
out := o.addPrefixIfNeeded(objRef, o.Out) out := o.addPrefixIfNeeded(objRef, o.Out)
if err := o.ConsumeRequestFn(ctx, request, out); err != nil { if err := o.ConsumeRequestFn(request, out); err != nil {
if !o.IgnoreLogErrors { if !o.IgnoreLogErrors {
return err return err
} }
@ -465,8 +436,8 @@ func (o LogsOptions) addPrefixIfNeeded(ref corev1.ObjectReference, writer io.Wri
// A successful read returns err == nil, not err == io.EOF. // A successful read returns err == nil, not err == io.EOF.
// Because the function is defined to read from request until io.EOF, it does // Because the function is defined to read from request until io.EOF, it does
// not treat an io.EOF as an error to be reported. // not treat an io.EOF as an error to be reported.
func DefaultConsumeRequest(ctx context.Context, request rest.ResponseWrapper, out io.Writer) error { func DefaultConsumeRequest(request rest.ResponseWrapper, out io.Writer) error {
readCloser, err := request.Stream(ctx) readCloser, err := request.Stream(context.TODO())
if err != nil { if err != nil {
return err return err
} }

View File

@ -304,7 +304,7 @@ func TestLog(t *testing.T) {
o := NewLogsOptions(streams) o := NewLogsOptions(streams)
o.LogsForObject = mock.mockLogsForObject o.LogsForObject = mock.mockLogsForObject
o.ConsumeRequestFn = func(ctx context.Context, req restclient.ResponseWrapper, out io.Writer) error { o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
return errors.New("Error from the ConsumeRequestFn") return errors.New("Error from the ConsumeRequestFn")
} }
return o return o
@ -378,7 +378,7 @@ func TestLog(t *testing.T) {
o := NewLogsOptions(streams) o := NewLogsOptions(streams)
o.LogsForObject = mock.mockLogsForObject o.LogsForObject = mock.mockLogsForObject
o.ConsumeRequestFn = func(ctx context.Context, req restclient.ResponseWrapper, out io.Writer) error { o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
return errors.New("Error from the ConsumeRequestFn") return errors.New("Error from the ConsumeRequestFn")
} }
o.Follow = true o.Follow = true
@ -401,7 +401,7 @@ func TestLog(t *testing.T) {
o := NewLogsOptions(streams) o := NewLogsOptions(streams)
o.LogsForObject = mock.mockLogsForObject o.LogsForObject = mock.mockLogsForObject
o.ConsumeRequestFn = func(ctx context.Context, req restclient.ResponseWrapper, out io.Writer) error { o.ConsumeRequestFn = func(req restclient.ResponseWrapper, out io.Writer) error {
return errors.New("Error from the ConsumeRequestFn") return errors.New("Error from the ConsumeRequestFn")
} }
o.Follow = true o.Follow = true
@ -808,7 +808,7 @@ func TestDefaultConsumeRequest(t *testing.T) {
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
err := DefaultConsumeRequest(context.TODO(), test.request, buf) err := DefaultConsumeRequest(test.request, buf)
if err != nil && !strings.Contains(err.Error(), test.expectedErr) { if err != nil && !strings.Contains(err.Error(), test.expectedErr) {
t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expectedErr, err.Error()) t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expectedErr, err.Error())
@ -932,8 +932,8 @@ type logTestMock struct {
wg *sync.WaitGroup wg *sync.WaitGroup
} }
func (l *logTestMock) mockConsumeRequest(ctx context.Context, request restclient.ResponseWrapper, out io.Writer) error { func (l *logTestMock) mockConsumeRequest(request restclient.ResponseWrapper, out io.Writer) error {
readCloser, err := request.Stream(ctx) readCloser, err := request.Stream(context.Background())
if err != nil { if err != nil {
return err return err
} }

View File

@ -22,6 +22,7 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/pkg/errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
jsonpatch "gopkg.in/evanphx/json-patch.v4" jsonpatch "gopkg.in/evanphx/json-patch.v4"
"k8s.io/klog/v2" "k8s.io/klog/v2"
@ -44,6 +45,7 @@ import (
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util/completion" "k8s.io/kubectl/pkg/util/completion"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/slice"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
) )
@ -105,6 +107,8 @@ var (
kubectl patch deployment nginx-deployment --subresource='scale' --type='merge' -p '{"spec":{"replicas":2}}'`)) kubectl patch deployment nginx-deployment --subresource='scale' --type='merge' -p '{"spec":{"replicas":2}}'`))
) )
var supportedSubresources = []string{"status", "scale"}
func NewPatchOptions(ioStreams genericiooptions.IOStreams) *PatchOptions { func NewPatchOptions(ioStreams genericiooptions.IOStreams) *PatchOptions {
return &PatchOptions{ return &PatchOptions{
RecordFlags: genericclioptions.NewRecordFlags(), RecordFlags: genericclioptions.NewRecordFlags(),
@ -136,12 +140,12 @@ func NewCmdPatch(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cobra
cmd.Flags().StringVarP(&o.Patch, "patch", "p", "", "The patch to be applied to the resource JSON file.") cmd.Flags().StringVarP(&o.Patch, "patch", "p", "", "The patch to be applied to the resource JSON file.")
cmd.Flags().StringVar(&o.PatchFile, "patch-file", "", "A file containing a patch to be applied to the resource.") cmd.Flags().StringVar(&o.PatchFile, "patch-file", "", "A file containing a patch to be applied to the resource.")
cmd.Flags().StringVar(&o.PatchType, "type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.List(sets.KeySet(patchTypes)))) cmd.Flags().StringVar(&o.PatchType, "type", "strategic", fmt.Sprintf("The type of patch being provided; one of %v", sets.StringKeySet(patchTypes).List()))
cmdutil.AddDryRunFlag(cmd) cmdutil.AddDryRunFlag(cmd)
cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update") cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to update")
cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, patch will operate on the content of the file, not the server-side resource.") cmd.Flags().BoolVar(&o.Local, "local", o.Local, "If true, patch will operate on the content of the file, not the server-side resource.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-patch") cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-patch")
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, patch will operate on the subresource of the requested object.") cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, patch will operate on the subresource of the requested object.", supportedSubresources...)
return cmd return cmd
} }
@ -193,9 +197,12 @@ func (o *PatchOptions) Validate() error {
} }
if len(o.PatchType) != 0 { if len(o.PatchType) != 0 {
if _, ok := patchTypes[strings.ToLower(o.PatchType)]; !ok { if _, ok := patchTypes[strings.ToLower(o.PatchType)]; !ok {
return fmt.Errorf("--type must be one of %v, not %q", sets.List(sets.KeySet(patchTypes)), o.PatchType) return fmt.Errorf("--type must be one of %v, not %q", sets.StringKeySet(patchTypes).List(), o.PatchType)
} }
} }
if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) {
return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources)
}
return nil return nil
} }
@ -259,7 +266,7 @@ func (o *PatchOptions) RunPatch() error {
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes, nil) patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes, nil)
if err != nil { if err != nil {
if apierrors.IsUnsupportedMediaType(err) { if apierrors.IsUnsupportedMediaType(err) {
return fmt.Errorf("%s is not supported by %s: %w", patchType, mapping.GroupVersionKind, err) return errors.Wrap(err, fmt.Sprintf("%s is not supported by %s", patchType, mapping.GroupVersionKind))
} }
return err return err
} }

View File

@ -223,9 +223,7 @@ func convertPodNamedPortToNumber(ports []string, pod corev1.Pod) ([]string, erro
var converted []string var converted []string
for _, port := range ports { for _, port := range ports {
localPort, remotePort := splitPort(port) localPort, remotePort := splitPort(port)
if remotePort == "" {
return nil, fmt.Errorf("remote port cannot be empty")
}
containerPortStr := remotePort containerPortStr := remotePort
_, err := strconv.Atoi(remotePort) _, err := strconv.Atoi(remotePort)
if err != nil { if err != nil {
@ -247,7 +245,7 @@ func convertPodNamedPortToNumber(ports []string, pod corev1.Pod) ([]string, erro
return converted, nil return converted, nil
} }
func checkUDPPorts(udpOnlyPorts sets.Set[int], ports []string, obj metav1.Object) error { func checkUDPPorts(udpOnlyPorts sets.Int, ports []string, obj metav1.Object) error {
for _, port := range ports { for _, port := range ports {
_, remotePort := splitPort(port) _, remotePort := splitPort(port)
portNum, err := strconv.Atoi(remotePort) portNum, err := strconv.Atoi(remotePort)
@ -281,8 +279,8 @@ func checkUDPPorts(udpOnlyPorts sets.Set[int], ports []string, obj metav1.Object
// checkUDPPortInService returns an error if remote port in Service is a UDP port // checkUDPPortInService returns an error if remote port in Service is a UDP port
// TODO: remove this check after #47862 is solved // TODO: remove this check after #47862 is solved
func checkUDPPortInService(ports []string, svc *corev1.Service) error { func checkUDPPortInService(ports []string, svc *corev1.Service) error {
udpPorts := sets.New[int]() udpPorts := sets.NewInt()
tcpPorts := sets.New[int]() tcpPorts := sets.NewInt()
for _, port := range svc.Spec.Ports { for _, port := range svc.Spec.Ports {
portNum := int(port.Port) portNum := int(port.Port)
switch port.Protocol { switch port.Protocol {
@ -298,8 +296,8 @@ func checkUDPPortInService(ports []string, svc *corev1.Service) error {
// checkUDPPortInPod returns an error if remote port in Pod is a UDP port // checkUDPPortInPod returns an error if remote port in Pod is a UDP port
// TODO: remove this check after #47862 is solved // TODO: remove this check after #47862 is solved
func checkUDPPortInPod(ports []string, pod *corev1.Pod) error { func checkUDPPortInPod(ports []string, pod *corev1.Pod) error {
udpPorts := sets.New[int]() udpPorts := sets.NewInt()
tcpPorts := sets.New[int]() tcpPorts := sets.NewInt()
for _, ct := range pod.Spec.Containers { for _, ct := range pod.Spec.Containers {
for _, ctPort := range ct.Ports { for _, ctPort := range ct.Ports {
portNum := int(ctPort.ContainerPort) portNum := int(ctPort.ContainerPort)

View File

@ -820,24 +820,6 @@ func TestConvertPodNamedPortToNumber(t *testing.T) {
ports: []string{"https", "http"}, ports: []string{"https", "http"},
err: true, err: true,
}, },
{
name: "empty port name",
pod: corev1.Pod{
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Ports: []corev1.ContainerPort{
{
ContainerPort: int32(27017)},
},
},
},
},
},
ports: []string{"28015:"},
converted: nil,
err: true,
},
} }
for _, tc := range cases { for _, tc := range cases {

View File

@ -17,7 +17,6 @@ limitations under the License.
package replace package replace
import ( import (
"context"
"fmt" "fmt"
"net/url" "net/url"
"os" "os"
@ -41,6 +40,7 @@ import (
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/i18n" "k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/slice"
"k8s.io/kubectl/pkg/util/templates" "k8s.io/kubectl/pkg/util/templates"
"k8s.io/kubectl/pkg/validation" "k8s.io/kubectl/pkg/validation"
) )
@ -68,6 +68,8 @@ var (
kubectl replace --force -f ./pod.json`)) kubectl replace --force -f ./pod.json`))
) )
var supportedSubresources = []string{"status", "scale"}
type ReplaceOptions struct { type ReplaceOptions struct {
PrintFlags *genericclioptions.PrintFlags PrintFlags *genericclioptions.PrintFlags
RecordFlags *genericclioptions.RecordFlags RecordFlags *genericclioptions.RecordFlags
@ -134,7 +136,7 @@ func NewCmdReplace(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra
cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to PUT to the server. Uses the transport specified by the kubeconfig file.") cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to PUT to the server. Uses the transport specified by the kubeconfig file.")
cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-replace") cmdutil.AddFieldManagerFlagVar(cmd, &o.fieldManager, "kubectl-replace")
cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, replace will operate on the subresource of the requested object.") cmdutil.AddSubresourceFlags(cmd, &o.Subresource, "If specified, replace will operate on the subresource of the requested object.", supportedSubresources...)
return cmd return cmd
} }
@ -247,6 +249,10 @@ func (o *ReplaceOptions) Validate() error {
} }
} }
if len(o.Subresource) > 0 && !slice.ContainsString(supportedSubresources, o.Subresource, nil) {
return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, supportedSubresources)
}
return nil return nil
} }
@ -359,7 +365,8 @@ func (o *ReplaceOptions) forceReplace() error {
if err != nil { if err != nil {
return err return err
} }
return wait.PollUntilContextTimeout(context.Background(), 1*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
return wait.PollImmediate(1*time.Second, timeout, func() (bool, error) {
if err := info.Get(); !errors.IsNotFound(err) { if err := info.Get(); !errors.IsNotFound(err) {
return false, err return false, err
} }

View File

@ -105,7 +105,7 @@ func NewCmdRolloutHistory(f cmdutil.Factory, streams genericiooptions.IOStreams)
return cmd return cmd
} }
// Complete completes all the required options // Complete completes al the required options
func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error { func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
o.Resources = args o.Resources = args
@ -177,15 +177,7 @@ func (o *RolloutHistoryOptions) Run() error {
} }
if o.Revision > 0 { if o.Revision > 0 {
// Ensure the specified revision exists before printing printer.PrintObj(historyInfo[o.Revision], o.Out)
revision, exists := historyInfo[o.Revision]
if !exists {
return fmt.Errorf("unable to find the specified revision")
}
if err := printer.PrintObj(revision, o.Out); err != nil {
return err
}
} else { } else {
sortedKeys := make([]int64, 0, len(historyInfo)) sortedKeys := make([]int64, 0, len(historyInfo))
for k := range historyInfo { for k := range historyInfo {

View File

@ -280,12 +280,15 @@ func TestRolloutHistoryWithOutput(t *testing.T) {
"kind": "ReplicaSet", "kind": "ReplicaSet",
"apiVersion": "apps/v1", "apiVersion": "apps/v1",
"metadata": { "metadata": {
"name": "rev2" "name": "rev2",
"creationTimestamp": null
}, },
"spec": { "spec": {
"selector": null, "selector": null,
"template": { "template": {
"metadata": {}, "metadata": {
"creationTimestamp": null
},
"spec": { "spec": {
"containers": null "containers": null
} }
@ -302,11 +305,13 @@ func TestRolloutHistoryWithOutput(t *testing.T) {
expectedOutput: `apiVersion: apps/v1 expectedOutput: `apiVersion: apps/v1
kind: ReplicaSet kind: ReplicaSet
metadata: metadata:
creationTimestamp: null
name: rev2 name: rev2
spec: spec:
selector: null selector: null
template: template:
metadata: {} metadata:
creationTimestamp: null
spec: spec:
containers: null containers: null
status: status:
@ -318,11 +323,13 @@ status:
expectedOutput: `apiVersion: apps/v1 expectedOutput: `apiVersion: apps/v1
kind: ReplicaSet kind: ReplicaSet
metadata: metadata:
creationTimestamp: null
name: rev1 name: rev1
spec: spec:
selector: null selector: null
template: template:
metadata: {} metadata:
creationTimestamp: null
spec: spec:
containers: null containers: null
status: status:
@ -331,11 +338,13 @@ status:
apiVersion: apps/v1 apiVersion: apps/v1
kind: ReplicaSet kind: ReplicaSet
metadata: metadata:
creationTimestamp: null
name: rev2 name: rev2
spec: spec:
selector: null selector: null
template: template:
metadata: {} metadata:
creationTimestamp: null
spec: spec:
containers: null containers: null
status: status:
@ -392,88 +401,6 @@ replicaset.apps/rev2
} }
} }
func TestRolloutHistoryErrors(t *testing.T) {
ns := scheme.Codecs.WithoutConversion()
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
info, _ := runtime.SerializerInfoForMediaType(ns.SupportedMediaTypes(), runtime.ContentTypeJSON)
encoder := ns.EncoderForVersion(info.Serializer, rolloutPauseGroupVersionEncoder)
tf.Client = &RolloutPauseRESTClient{
RESTClient: &fake.RESTClient{
GroupVersion: rolloutPauseGroupVersionEncoder,
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/namespaces/test/deployments/foo" && m == "GET":
responseDeployment := &appsv1.Deployment{}
responseDeployment.Name = "foo"
body := io.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(encoder, responseDeployment))))
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: body}, nil
default:
t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
return nil, nil
}
}),
},
}
testCases := map[string]struct {
revision int64
outputFormat string
expectedError string
}{
"get non-existing revision as yaml": {
revision: 999,
outputFormat: "yaml",
expectedError: "unable to find the specified revision",
},
"get non-existing revision as json": {
revision: 999,
outputFormat: "json",
expectedError: "unable to find the specified revision",
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
fhv := setupFakeHistoryViewer(t)
fhv.getHistoryFn = func(namespace, name string) (map[int64]runtime.Object, error) {
return map[int64]runtime.Object{
1: &appsv1.ReplicaSet{ObjectMeta: v1.ObjectMeta{Name: "rev1"}},
2: &appsv1.ReplicaSet{ObjectMeta: v1.ObjectMeta{Name: "rev2"}},
}, nil
}
streams := genericiooptions.NewTestIOStreamsDiscard()
o := NewRolloutHistoryOptions(streams)
printFlags := &genericclioptions.PrintFlags{
JSONYamlPrintFlags: &genericclioptions.JSONYamlPrintFlags{
ShowManagedFields: true,
},
OutputFormat: &tc.outputFormat,
OutputFlagSpecified: func() bool {
return true
},
}
o.PrintFlags = printFlags
o.Revision = tc.revision
if err := o.Complete(tf, nil, []string{"deployment/foo"}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
err := o.Run()
if err != nil && err.Error() != tc.expectedError {
t.Fatalf("expected '%s' error, but got: %v", tc.expectedError, err)
}
})
}
}
func TestValidate(t *testing.T) { func TestValidate(t *testing.T) {
opts := RolloutHistoryOptions{ opts := RolloutHistoryOptions{
Revision: 0, Revision: 0,

View File

@ -504,7 +504,7 @@ func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *corev1.Po
return err return err
} }
for _, request := range requests { for _, request := range requests {
if err := logs.DefaultConsumeRequest(context.Background(), request, opts.Out); err != nil { if err := logs.DefaultConsumeRequest(request, opts.Out); err != nil {
return err return err
} }
} }

View File

@ -655,6 +655,7 @@ func TestRunOverride(t *testing.T) {
expectedOutput: `apiVersion: v1 expectedOutput: `apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
labels: labels:
run: test run: test
name: test name: test
@ -677,6 +678,7 @@ status: {}
expectedOutput: `apiVersion: v1 expectedOutput: `apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
labels: labels:
run: test run: test
name: test name: test
@ -699,6 +701,7 @@ status: {}
expectedOutput: `apiVersion: v1 expectedOutput: `apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
labels: labels:
run: test run: test
name: test name: test
@ -726,6 +729,7 @@ status: {}
expectedOutput: `apiVersion: v1 expectedOutput: `apiVersion: v1
kind: Pod kind: Pod
metadata: metadata:
creationTimestamp: null
labels: labels:
foo: bar foo: bar
run: test run: test

View File

@ -229,9 +229,6 @@ func (o *ScaleOptions) RunScale() error {
} }
if len(infos) == 0 { if len(infos) == 0 {
if infoErr != nil {
return fmt.Errorf("no objects passed to scale %w", infoErr)
}
return fmt.Errorf("no objects passed to scale") return fmt.Errorf("no objects passed to scale")
} }

View File

@ -15,4 +15,4 @@ limitations under the License.
*/ */
// Package env provides functions to incorporate environment variables into set env. // Package env provides functions to incorporate environment variables into set env.
package env package env // import "k8s.io/kubectl/pkg/cmd/set/env"

View File

@ -61,7 +61,7 @@ func SplitEnvironmentFromResources(args []string) (resources, envArgs []string,
// envVarType is for making errors more specific to user intentions. // envVarType is for making errors more specific to user intentions.
func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) ([]v1.EnvVar, []string, bool, error) { func parseIntoEnvVar(spec []string, defaultReader io.Reader, envVarType string) ([]v1.EnvVar, []string, bool, error) {
env := []v1.EnvVar{} env := []v1.EnvVar{}
exists := sets.New[string]() exists := sets.NewString()
var remove []string var remove []string
usedStdin := false usedStdin := false
for _, envSpec := range spec { for _, envSpec := range spec {

View File

@ -158,11 +158,11 @@ func splitMaybeSubscriptedPath(fieldPath string) (string, string, bool) {
// formatMap formats map[string]string to a string. // formatMap formats map[string]string to a string.
func formatMap(m map[string]string) (fmtStr string) { func formatMap(m map[string]string) (fmtStr string) {
// output with keys in sorted order to provide stable output // output with keys in sorted order to provide stable output
keys := sets.New[string]() keys := sets.NewString()
for key := range m { for key := range m {
keys.Insert(key) keys.Insert(key)
} }
for _, key := range sets.List(keys) { for _, key := range keys.List() {
fmtStr += fmt.Sprintf("%v=%q\n", key, m[key]) fmtStr += fmt.Sprintf("%v=%q\n", key, m[key])
} }
fmtStr = strings.TrimSuffix(fmtStr, "\n") fmtStr = strings.TrimSuffix(fmtStr, "\n")

View File

@ -19,7 +19,7 @@ package set
import ( import (
"strings" "strings"
v1 "k8s.io/api/core/v1" "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/strategicpatch" "k8s.io/apimachinery/pkg/util/strategicpatch"
@ -139,7 +139,7 @@ func findEnv(env []v1.EnvVar, name string) (v1.EnvVar, bool) {
// If a variable is both added and removed, the removal takes precedence. // If a variable is both added and removed, the removal takes precedence.
func updateEnv(existing []v1.EnvVar, env []v1.EnvVar, remove []string) []v1.EnvVar { func updateEnv(existing []v1.EnvVar, env []v1.EnvVar, remove []string) []v1.EnvVar {
out := []v1.EnvVar{} out := []v1.EnvVar{}
covered := sets.New[string](remove...) covered := sets.NewString(remove...)
for _, e := range existing { for _, e := range existing {
if covered.Has(e.Name) { if covered.Has(e.Name) {
continue continue

View File

@ -503,7 +503,7 @@ func TestSetEnvRemote(t *testing.T) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
assert.Containsf(t, string(bytes), `"value":`+`"`+"prod"+`"`, "env not updated for %#v", input.object) assert.Contains(t, string(bytes), `"value":`+`"`+"prod"+`"`, fmt.Sprintf("env not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
default: default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req) t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)

View File

@ -641,7 +641,7 @@ func TestSetImageRemote(t *testing.T) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
assert.Containsf(t, string(bytes), `"image":`+`"`+"thingy"+`"`, "image not updated for %#v", input.object) assert.Contains(t, string(bytes), `"image":`+`"`+"thingy"+`"`, fmt.Sprintf("image not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
default: default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req) t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)
@ -753,8 +753,8 @@ func TestSetImageRemoteWithSpecificContainers(t *testing.T) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
assert.Containsf(t, string(bytes), `"image":"`+"thingy"+`","name":`+`"nginx"`, "image not updated for %#v", input.object) assert.Contains(t, string(bytes), `"image":"`+"thingy"+`","name":`+`"nginx"`, fmt.Sprintf("image not updated for %#v", input.object))
assert.NotContainsf(t, string(bytes), `"image":"`+"thingy"+`","name":`+`"busybox"`, "image updated for %#v", input.object) assert.NotContains(t, string(bytes), `"image":"`+"thingy"+`","name":`+`"busybox"`, fmt.Sprintf("image updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
default: default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req) t.Errorf("%s: unexpected request: %s %#v\n%#v", "image", req.Method, req.URL, req)

View File

@ -487,7 +487,7 @@ func TestSetResourcesRemote(t *testing.T) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
assert.Containsf(t, string(bytes), "200m", "resources not updated for %#v", input.object) assert.Contains(t, string(bytes), "200m", fmt.Sprintf("resources not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
default: default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "resources", req.Method, req.URL, req) t.Errorf("%s: unexpected request: %s %#v\n%#v", "resources", req.Method, req.URL, req)

View File

@ -74,7 +74,7 @@ var (
selectorExample = templates.Examples(` selectorExample = templates.Examples(`
# Set the labels and selector before creating a deployment/service pair # Set the labels and selector before creating a deployment/service pair
kubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run=client | kubectl set selector --local -f - 'environment=qa' -o yaml | kubectl create -f - kubectl create service clusterip my-svc --clusterip="None" -o yaml --dry-run=client | kubectl set selector --local -f - 'environment=qa' -o yaml | kubectl create -f -
kubectl create deployment my-dep --image=nginx -o yaml --dry-run=client | kubectl label --local -f - environment=qa -o yaml | kubectl create -f -`) kubectl create deployment my-dep -o yaml --dry-run=client | kubectl label --local -f - environment=qa -o yaml | kubectl create -f -`)
) )
// NewSelectorOptions returns an initialized SelectorOptions instance // NewSelectorOptions returns an initialized SelectorOptions instance

View File

@ -94,7 +94,7 @@ func TestSetServiceAccountLocal(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
err = saConfig.Run() err = saConfig.Run()
assert.NoError(t, err) assert.NoError(t, err)
assert.Containsf(t, buf.String(), "serviceAccountName: "+serviceAccount, "serviceaccount not updated for %s", input.yaml) assert.Contains(t, buf.String(), "serviceAccountName: "+serviceAccount, fmt.Sprintf("serviceaccount not updated for %s", input.yaml))
}) })
} }
} }
@ -337,7 +337,7 @@ func TestSetServiceAccountRemote(t *testing.T) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
assert.Containsf(t, string(bytes), `"serviceAccountName":`+`"`+serviceAccount+`"`, "serviceaccount not updated for %#v", input.object) assert.Contains(t, string(bytes), `"serviceAccountName":`+`"`+serviceAccount+`"`, fmt.Sprintf("serviceaccount not updated for %#v", input.object))
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: objBody(input.object)}, nil
default: default:
t.Errorf("%s: unexpected request: %s %#v\n%#v", "serviceaccount", req.Method, req.URL, req) t.Errorf("%s: unexpected request: %s %#v\n%#v", "serviceaccount", req.Method, req.URL, req)

View File

@ -206,7 +206,7 @@ func (o *SubjectOptions) Validate() error {
func (o *SubjectOptions) Run(fn updateSubjects) error { func (o *SubjectOptions) Run(fn updateSubjects) error {
patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) { patches := CalculatePatches(o.Infos, scheme.DefaultJSONEncoder(), func(obj runtime.Object) ([]byte, error) {
subjects := []rbacv1.Subject{} subjects := []rbacv1.Subject{}
for _, user := range sets.List(sets.New[string](o.Users...)) { for _, user := range sets.NewString(o.Users...).List() {
subject := rbacv1.Subject{ subject := rbacv1.Subject{
Kind: rbacv1.UserKind, Kind: rbacv1.UserKind,
APIGroup: rbacv1.GroupName, APIGroup: rbacv1.GroupName,
@ -214,7 +214,7 @@ func (o *SubjectOptions) Run(fn updateSubjects) error {
} }
subjects = append(subjects, subject) subjects = append(subjects, subject)
} }
for _, group := range sets.List(sets.New[string](o.Groups...)) { for _, group := range sets.NewString(o.Groups...).List() {
subject := rbacv1.Subject{ subject := rbacv1.Subject{
Kind: rbacv1.GroupKind, Kind: rbacv1.GroupKind,
APIGroup: rbacv1.GroupName, APIGroup: rbacv1.GroupName,
@ -222,7 +222,7 @@ func (o *SubjectOptions) Run(fn updateSubjects) error {
} }
subjects = append(subjects, subject) subjects = append(subjects, subject)
} }
for _, sa := range sets.List(sets.New[string](o.ServiceAccounts...)) { for _, sa := range sets.NewString(o.ServiceAccounts...).List() {
tokens := strings.Split(sa, ":") tokens := strings.Split(sa, ":")
namespace := tokens[0] namespace := tokens[0]
name := tokens[1] name := tokens[1]

View File

@ -38,7 +38,7 @@ const (
// It also validates the spec. For example, the form `<key>` may be used to remove a taint, but not to add one. // It also validates the spec. For example, the form `<key>` may be used to remove a taint, but not to add one.
func parseTaints(spec []string) ([]corev1.Taint, []corev1.Taint, error) { func parseTaints(spec []string) ([]corev1.Taint, []corev1.Taint, error) {
var taints, taintsToRemove []corev1.Taint var taints, taintsToRemove []corev1.Taint
uniqueTaints := map[corev1.TaintEffect]sets.Set[string]{} uniqueTaints := map[corev1.TaintEffect]sets.String{}
for _, taintSpec := range spec { for _, taintSpec := range spec {
if strings.HasSuffix(taintSpec, "-") { if strings.HasSuffix(taintSpec, "-") {
@ -62,7 +62,7 @@ func parseTaints(spec []string) ([]corev1.Taint, []corev1.Taint, error) {
} }
// add taint to existingTaints for uniqueness check // add taint to existingTaints for uniqueness check
if len(uniqueTaints[newTaint.Effect]) == 0 { if len(uniqueTaints[newTaint.Effect]) == 0 {
uniqueTaints[newTaint.Effect] = sets.Set[string]{} uniqueTaints[newTaint.Effect] = sets.String{}
} }
uniqueTaints[newTaint.Effect].Insert(newTaint.Key) uniqueTaints[newTaint.Effect].Insert(newTaint.Key)

View File

@ -39,31 +39,9 @@ var (
topLong = templates.LongDesc(i18n.T(` topLong = templates.LongDesc(i18n.T(`
Display resource (CPU/memory) usage. Display resource (CPU/memory) usage.
This command provides a view of recent resource consumption for nodes and pods. The top command allows you to see the resource consumption for nodes or pods.
It fetches metrics from the Metrics Server, which aggregates this data from the
kubelet on each node. The Metrics Server must be installed and running in the
cluster for this command to work.
The metrics shown are specifically optimized for Kubernetes autoscaling This command requires Metrics Server to be correctly configured and working on the server. `))
decisions, such as those made by the Horizontal Pod Autoscaler (HPA) and
Vertical Pod Autoscaler (VPA). Because of this, the values may not match those
from standard OS tools like 'top', as the metrics are designed to provide a
stable signal for autoscalers rather than for pinpoint accuracy.
When to use this command:
* For on-the-fly spot-checks of resource usage (e.g. identify which pods
are consuming the most resources at a glance, or get a quick sense of the load
on your nodes)
* Understand current resource consumption patterns
* Validate the behavior of your HPA or VPA configurations by seeing the metrics
they use for scaling decisions.
It is not intended to be a replacement for full-featured monitoring solutions.
Its primary design goal is to provide a low-overhead signal for autoscalers,
not to be a perfectly accurate monitoring tool. For high-accuracy reporting,
historical analysis, dashboarding, or alerting, you should use a dedicated
monitoring solution.`))
) )
func NewCmdTop(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command { func NewCmdTop(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {

View File

@ -19,9 +19,9 @@ package top
import ( import (
"context" "context"
"errors" "errors"
"github.com/spf13/cobra" "github.com/spf13/cobra"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/genericiooptions"
@ -45,7 +45,6 @@ type TopNodeOptions struct {
NoHeaders bool NoHeaders bool
UseProtocolBuffers bool UseProtocolBuffers bool
ShowCapacity bool ShowCapacity bool
ShowSwap bool
NodeClient corev1client.CoreV1Interface NodeClient corev1client.CoreV1Interface
Printer *metricsutil.TopCmdPrinter Printer *metricsutil.TopCmdPrinter
@ -96,7 +95,6 @@ func NewCmdTopNode(f cmdutil.Factory, o *TopNodeOptions, streams genericiooption
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers") cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers")
cmd.Flags().BoolVar(&o.UseProtocolBuffers, "use-protocol-buffers", o.UseProtocolBuffers, "Enables using protocol-buffers to access Metrics API.") cmd.Flags().BoolVar(&o.UseProtocolBuffers, "use-protocol-buffers", o.UseProtocolBuffers, "Enables using protocol-buffers to access Metrics API.")
cmd.Flags().BoolVar(&o.ShowCapacity, "show-capacity", o.ShowCapacity, "Print node resources based on Capacity instead of Allocatable(default) of the nodes.") cmd.Flags().BoolVar(&o.ShowCapacity, "show-capacity", o.ShowCapacity, "Print node resources based on Capacity instead of Allocatable(default) of the nodes.")
cmd.Flags().BoolVar(&o.ShowSwap, "show-swap", o.ShowSwap, "Print node resources related to swap memory.")
return cmd return cmd
} }
@ -129,7 +127,7 @@ func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
o.NodeClient = clientset.CoreV1() o.NodeClient = clientset.CoreV1()
o.Printer = metricsutil.NewTopCmdPrinter(o.Out, o.ShowSwap) o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
return nil return nil
} }
@ -200,14 +198,6 @@ func (o TopNodeOptions) RunTopNode() error {
} else { } else {
availableResources[n.Name] = n.Status.Capacity availableResources[n.Name] = n.Status.Capacity
} }
if n.Status.NodeInfo.Swap != nil && n.Status.NodeInfo.Swap.Capacity != nil {
swapCapacity := *n.Status.NodeInfo.Swap.Capacity
availableResources[n.Name]["swap"] = *resource.NewQuantity(swapCapacity, resource.BinarySI)
} else {
o.Printer.RegisterMissingResource(n.Name, metricsutil.ResourceSwap)
}
} }
return o.Printer.PrintNodeMetrics(metrics.Items, availableResources, o.NoHeaders, o.SortBy) return o.Printer.PrintNodeMetrics(metrics.Items, availableResources, o.NoHeaders, o.SortBy)

View File

@ -424,111 +424,3 @@ func TestTopNodeWithSortByMemoryMetricsFrom(t *testing.T) {
} }
} }
func TestTopNodeWithSwap(t *testing.T) {
runTopCmd := func(expectedMetrics *metricsv1beta1api.NodeMetricsList, nodes *v1.NodeList) (result string) {
cmdtesting.InitTestErrorHandler(t)
expectedNodePath := fmt.Sprintf("/%s/%s/nodes", apiPrefix, apiVersion)
tf := cmdtesting.NewTestFactory().WithNamespace("test")
defer tf.Cleanup()
codec := scheme.Codecs.LegacyCodec(scheme.Scheme.PrioritizedVersionsAllGroups()...)
ns := scheme.Codecs.WithoutConversion()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch p, m := req.URL.Path, req.Method; {
case p == "/api":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
case p == "/apis":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
case p == expectedNodePath && m == "GET":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: cmdtesting.ObjBody(codec, nodes)}, nil
default:
t.Fatalf("unexpected request: %#v\nGot URL: %#v\n", req, req.URL)
return nil, nil
}
}),
}
fakemetricsClientset := &metricsfake.Clientset{}
fakemetricsClientset.AddReactor("list", "nodes", func(action core.Action) (handled bool, ret runtime.Object, err error) {
return true, expectedMetrics, nil
})
tf.ClientConfigVal = cmdtesting.DefaultClientConfig()
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdTopNode(tf, nil, streams)
// TODO in the long run, we want to test most of our commands like this. Wire the options struct with specific mocks
// TODO then check the particular Run functionality and harvest results from fake clients
cmdOptions := &TopNodeOptions{
IOStreams: streams,
ShowSwap: true,
}
if err := cmdOptions.Complete(tf, cmd, []string{}); err != nil {
t.Fatal(err)
}
cmdOptions.MetricsClient = fakemetricsClientset
if err := cmdOptions.Validate(); err != nil {
t.Fatal(err)
}
if err := cmdOptions.RunTopNode(); err != nil {
t.Fatal(err)
}
return buf.String()
}
for _, tc := range []struct {
name string
isSwapDisabledOnNodes bool
}{
{
name: "nodes with swap",
isSwapDisabledOnNodes: false,
},
{
name: "nodes without swap",
isSwapDisabledOnNodes: true,
},
} {
t.Run(tc.name, func(t *testing.T) {
expectedMetrics, nodes := testNodeV1beta1MetricsData()
if tc.isSwapDisabledOnNodes {
for i := range expectedMetrics.Items {
delete(expectedMetrics.Items[i].Usage, "swap")
}
for i := range nodes.Items {
nodes.Items[i].Status.NodeInfo.Swap = nil
}
}
result := runTopCmd(expectedMetrics, nodes)
fmt.Printf("%s\n", result)
if !strings.Contains(result, "SWAP(bytes)") {
t.Errorf("missing SWAP(bytes) header: \n%s", result)
}
if !strings.Contains(result, "SWAP(%)") {
t.Errorf("missing SWAP(%%) header: \n%s", result)
}
if tc.isSwapDisabledOnNodes {
if !strings.Contains(result, "<unknown>") {
t.Errorf("expected swap to be <unknown>: \n%s", result)
}
}
for _, m := range expectedMetrics.Items {
if !strings.Contains(result, m.Name) {
t.Errorf("missing metrics for %s: \n%s", m.Name, result)
}
if _, foundSwapMetric := m.Usage["swap"]; foundSwapMetric != !tc.isSwapDisabledOnNodes {
t.Errorf("missing swap metric for %s: \n%s", m.Name, result)
}
}
})
}
}

View File

@ -53,7 +53,6 @@ type TopPodOptions struct {
NoHeaders bool NoHeaders bool
UseProtocolBuffers bool UseProtocolBuffers bool
Sum bool Sum bool
ShowSwap bool
PodClient corev1client.PodsGetter PodClient corev1client.PodsGetter
Printer *metricsutil.TopCmdPrinter Printer *metricsutil.TopCmdPrinter
@ -118,7 +117,6 @@ func NewCmdTopPod(f cmdutil.Factory, o *TopPodOptions, streams genericiooptions.
cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers.") cmd.Flags().BoolVar(&o.NoHeaders, "no-headers", o.NoHeaders, "If present, print output without headers.")
cmd.Flags().BoolVar(&o.UseProtocolBuffers, "use-protocol-buffers", o.UseProtocolBuffers, "Enables using protocol-buffers to access Metrics API.") cmd.Flags().BoolVar(&o.UseProtocolBuffers, "use-protocol-buffers", o.UseProtocolBuffers, "Enables using protocol-buffers to access Metrics API.")
cmd.Flags().BoolVar(&o.Sum, "sum", o.Sum, "Print the sum of the resource usage") cmd.Flags().BoolVar(&o.Sum, "sum", o.Sum, "Print the sum of the resource usage")
cmd.Flags().BoolVar(&o.ShowSwap, "show-swap", o.ShowSwap, "Print pod resources related to swap memory.")
return cmd return cmd
} }
@ -154,7 +152,7 @@ func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s
o.PodClient = clientset.CoreV1() o.PodClient = clientset.CoreV1()
o.Printer = metricsutil.NewTopCmdPrinter(o.Out, o.ShowSwap) o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
return nil return nil
} }

View File

@ -168,12 +168,6 @@ func TestTopPod(t *testing.T) {
namespaces: []string{testNS, testNS, testNS}, namespaces: []string{testNS, testNS, testNS},
containers: true, containers: true,
}, },
{
name: "with swap",
options: &TopPodOptions{AllNamespaces: true, ShowSwap: true},
namespaces: []string{testNS, "secondtestns", "thirdtestns"},
listsNamespaces: true,
},
} }
cmdtesting.InitTestErrorHandler(t) cmdtesting.InitTestErrorHandler(t)
for _, testCase := range testCases { for _, testCase := range testCases {
@ -290,101 +284,10 @@ func TestTopPod(t *testing.T) {
t.Errorf("containers not matching:\n\texpectedContainers: %v\n\tresultContainers: %v\n", testCase.expectedContainers, resultContainers) t.Errorf("containers not matching:\n\texpectedContainers: %v\n\tresultContainers: %v\n", testCase.expectedContainers, resultContainers)
} }
} }
if testCase.options != nil && testCase.options.ShowSwap {
if !strings.Contains(result, "SWAP(bytes)") {
t.Errorf("missing SWAP(bytes) header: \n%s", result)
}
}
}) })
} }
} }
func TestTopPodWithSwap(t *testing.T) {
cmdtesting.InitTestErrorHandler(t)
const testName = "TestTopPodWithSwap"
t.Run(testName, func(t *testing.T) {
metricsList := testV1beta1PodMetricsData()
fakemetricsClientset := &metricsfake.Clientset{}
fakemetricsClientset.AddReactor("list", "pods", func(action core.Action) (handled bool, ret runtime.Object, err error) {
res := &metricsv1beta1api.PodMetricsList{
Items: metricsList,
}
return true, res, nil
})
tf := cmdtesting.NewTestFactory()
defer tf.Cleanup()
ns := scheme.Codecs.WithoutConversion()
tf.Client = &fake.RESTClient{
NegotiatedSerializer: ns,
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
switch req.URL.Path {
case "/api":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader([]byte(apibody)))}, nil
case "/apis":
return &http.Response{StatusCode: http.StatusOK, Header: cmdtesting.DefaultHeader(), Body: io.NopCloser(bytes.NewReader([]byte(apisbodyWithMetrics)))}, nil
default:
t.Fatalf("%s: unexpected request: %#v\nGot URL: %#v",
testName, req, req.URL)
return nil, nil
}
}),
}
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
cmd := NewCmdTopPod(tf, nil, streams)
cmdOptions := &TopPodOptions{
ShowSwap: true,
}
cmdOptions.IOStreams = streams
if err := cmdOptions.Complete(tf, cmd, nil); err != nil {
t.Fatal(err)
}
cmdOptions.MetricsClient = fakemetricsClientset
if err := cmdOptions.Validate(); err != nil {
t.Fatal(err)
}
if err := cmdOptions.RunTopPod(); err != nil {
t.Fatal(err)
}
result := buf.String()
expectedSwapBytes := map[string]string{
"pod1": "4Mi",
"pod2": "0Mi",
"pod3": "3Mi",
}
actualSwapBytes := map[string]string{}
for _, line := range strings.Split(result, "\n")[1:] {
lineFields := strings.Fields(line)
if len(lineFields) < 4 {
continue
}
podName := lineFields[0]
swapBytes := lineFields[3]
actualSwapBytes[podName] = swapBytes
}
for expectedPodName, expectedSwapBytes := range expectedSwapBytes {
actualSwapBytes, found := actualSwapBytes[expectedPodName]
if !found {
t.Errorf("missing swap metrics for pod %s", expectedPodName)
}
if actualSwapBytes != expectedSwapBytes {
t.Errorf("unexpected swap metrics for pod %s: expected %s, got %s", expectedPodName, expectedSwapBytes, actualSwapBytes)
}
}
})
}
func getResultColumnValues(result string, columnIndex int) []string { func getResultColumnValues(result string, columnIndex int) []string {
resultLines := strings.Split(result, "\n") resultLines := strings.Split(result, "\n")
values := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line values := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
@ -507,7 +410,6 @@ func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
Usage: v1.ResourceList{ Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI), v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
"swap": *resource.NewQuantity(1*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
}, },
}, },
@ -516,7 +418,6 @@ func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
Usage: v1.ResourceList{ Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI), v1.ResourceCPU: *resource.NewMilliQuantity(4, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
"swap": *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
}, },
}, },
@ -561,7 +462,6 @@ func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
Usage: v1.ResourceList{ Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI), v1.ResourceCPU: *resource.NewMilliQuantity(7, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(8*(1024*1024), resource.DecimalSI),
"swap": *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(9*(1024*1024), resource.DecimalSI),
}, },
}, },

View File

@ -30,7 +30,6 @@ import (
"k8s.io/cli-runtime/pkg/genericiooptions" "k8s.io/cli-runtime/pkg/genericiooptions"
cmdtesting "k8s.io/kubectl/pkg/cmd/testing" cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1" metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
"k8s.io/utils/ptr"
) )
func TestTopSubcommandsExist(t *testing.T) { func TestTopSubcommandsExist(t *testing.T) {
@ -64,7 +63,6 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
Window: metav1.Duration{Duration: time.Minute}, Window: metav1.Duration{Duration: time.Minute},
Usage: v1.ResourceList{ Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI), v1.ResourceCPU: *resource.NewMilliQuantity(1, resource.DecimalSI),
"swap": *resource.NewQuantity(1*(1024*1024), resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
}, },
@ -74,7 +72,6 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
Window: metav1.Duration{Duration: time.Minute}, Window: metav1.Duration{Duration: time.Minute},
Usage: v1.ResourceList{ Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(5, resource.DecimalSI), v1.ResourceCPU: *resource.NewMilliQuantity(5, resource.DecimalSI),
"swap": *resource.NewQuantity(2*(1024*1024), resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(6*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(7*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(7*(1024*1024), resource.DecimalSI),
}, },
@ -84,7 +81,6 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
Window: metav1.Duration{Duration: time.Minute}, Window: metav1.Duration{Duration: time.Minute},
Usage: v1.ResourceList{ Usage: v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(3, resource.DecimalSI), v1.ResourceCPU: *resource.NewMilliQuantity(3, resource.DecimalSI),
"swap": *resource.NewQuantity(0, resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(4*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(4*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
}, },
@ -104,11 +100,6 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
v1.ResourceMemory: *resource.NewQuantity(20*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(20*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(30*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(30*(1024*1024), resource.DecimalSI),
}, },
NodeInfo: v1.NodeSystemInfo{
Swap: &v1.NodeSwapStatus{
Capacity: ptr.To(int64(10 * (1024 * 1024 * 1024))),
},
},
}, },
}, },
{ {
@ -119,11 +110,6 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
v1.ResourceMemory: *resource.NewQuantity(60*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(60*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(70*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(70*(1024*1024), resource.DecimalSI),
}, },
NodeInfo: v1.NodeSystemInfo{
Swap: &v1.NodeSwapStatus{
Capacity: ptr.To(int64(20 * (1024 * 1024 * 1024))),
},
},
}, },
}, },
{ {
@ -134,11 +120,6 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
v1.ResourceMemory: *resource.NewQuantity(40*(1024*1024), resource.DecimalSI), v1.ResourceMemory: *resource.NewQuantity(40*(1024*1024), resource.DecimalSI),
v1.ResourceStorage: *resource.NewQuantity(50*(1024*1024), resource.DecimalSI), v1.ResourceStorage: *resource.NewQuantity(50*(1024*1024), resource.DecimalSI),
}, },
NodeInfo: v1.NodeSystemInfo{
Swap: &v1.NodeSwapStatus{
Capacity: ptr.To(int64(30 * (1024 * 1024 * 1024))),
},
},
}, },
}, },
}, },

View File

@ -1,59 +0,0 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"sync"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/resource"
)
// cachingVerifier wraps the given Verifier to cache return values forever.
type cachingVerifier struct {
cache map[schema.GroupVersionKind]error
mu sync.RWMutex
next resource.Verifier
}
// newCachingVerifier creates a new cache using the given underlying verifier.
func newCachingVerifier(next resource.Verifier) *cachingVerifier {
return &cachingVerifier{
cache: make(map[schema.GroupVersionKind]error),
next: next,
}
}
// HasSupport implements resource.Verifier. It cached return values from the underlying verifier forever.
func (cv *cachingVerifier) HasSupport(gvk schema.GroupVersionKind) error {
// Try to get the cached value.
cv.mu.RLock()
err, ok := cv.cache[gvk]
cv.mu.RUnlock()
if ok {
return err
}
// Cache miss. Get the actual result.
err = cv.next.HasSupport(gvk)
// Update the cache.
cv.mu.Lock()
cv.cache[gvk] = err
cv.mu.Unlock()
return err
}

View File

@ -1,105 +0,0 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"errors"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
)
type verifierCall struct {
GVK schema.GroupVersionKind
Err error
}
type mockVerifier struct {
t *testing.T
expectedCalls map[schema.GroupVersionKind]error
}
func (m *mockVerifier) CheckExpectations() {
if len(m.expectedCalls) != 0 {
m.t.Errorf("Expected calls remaining: %v", m.expectedCalls)
}
}
func (m *mockVerifier) HasSupport(gvk schema.GroupVersionKind) error {
returnErr, ok := m.expectedCalls[gvk]
if !ok {
m.t.Errorf("Unexpected HasSupport call with GVK=%v", gvk)
}
delete(m.expectedCalls, gvk)
return returnErr
}
func TestCachingVerifier(t *testing.T) {
gvk1 := schema.GroupVersionKind{
Group: "group",
Version: "version",
Kind: "kind",
}
gvk2 := schema.GroupVersionKind{
Group: "group2",
Version: "version2",
Kind: "kind2",
}
err1 := errors.New("some error")
testCases := []struct {
name string
calls []verifierCall
expectedUnderlyingCalls map[schema.GroupVersionKind]error
}{
{
name: "return value is cached",
calls: []verifierCall{
{GVK: gvk1, Err: nil},
{GVK: gvk1, Err: nil},
{GVK: gvk1, Err: nil},
{GVK: gvk2, Err: err1},
{GVK: gvk2, Err: err1},
{GVK: gvk2, Err: err1},
},
expectedUnderlyingCalls: map[schema.GroupVersionKind]error{
gvk1: nil,
gvk2: err1,
},
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
m := &mockVerifier{
t: t,
expectedCalls: tc.expectedUnderlyingCalls,
}
verifier := newCachingVerifier(m)
for _, call := range tc.calls {
err := verifier.HasSupport(call.GVK)
if !errors.Is(err, call.Err) {
t.Errorf("Expected error: %v, got: %v", call.Err, err)
}
}
m.CheckExpectations()
})
}
}

View File

@ -52,8 +52,11 @@ import (
"k8s.io/kubectl/pkg/cmd/util/editor/crlf" "k8s.io/kubectl/pkg/cmd/util/editor/crlf"
"k8s.io/kubectl/pkg/scheme" "k8s.io/kubectl/pkg/scheme"
"k8s.io/kubectl/pkg/util" "k8s.io/kubectl/pkg/util"
"k8s.io/kubectl/pkg/util/slice"
) )
var SupportedSubresources = []string{"status"}
// EditOptions contains all the options for running edit cli command. // EditOptions contains all the options for running edit cli command.
type EditOptions struct { type EditOptions struct {
resource.FilenameOptions resource.FilenameOptions
@ -227,6 +230,9 @@ func (o *EditOptions) Complete(f cmdutil.Factory, args []string, cmd *cobra.Comm
// Validate checks the EditOptions to see if there is sufficient information to run the command. // Validate checks the EditOptions to see if there is sufficient information to run the command.
func (o *EditOptions) Validate() error { func (o *EditOptions) Validate() error {
if len(o.Subresource) > 0 && !slice.ContainsString(SupportedSubresources, o.Subresource, nil) {
return fmt.Errorf("invalid subresource value: %q. Must be one of %v", o.Subresource, SupportedSubresources)
}
return nil return nil
} }

View File

@ -172,7 +172,7 @@ func (f *factoryImpl) Validator(validationDirective string) (validation.Schema,
// the discovery client. // the discovery client.
oapiV3Client := cached.NewClient(discoveryClient.OpenAPIV3()) oapiV3Client := cached.NewClient(discoveryClient.OpenAPIV3())
queryParam := resource.QueryParamFieldValidation queryParam := resource.QueryParamFieldValidation
primary := newCachingVerifier(resource.NewQueryParamVerifierV3(dynamicClient, oapiV3Client, queryParam)) primary := resource.NewQueryParamVerifierV3(dynamicClient, oapiV3Client, queryParam)
secondary := resource.NewQueryParamVerifier(dynamicClient, f.openAPIGetter(), queryParam) secondary := resource.NewQueryParamVerifier(dynamicClient, f.openAPIGetter(), queryParam)
fallback := resource.NewFallbackQueryParamVerifier(primary, secondary) fallback := resource.NewFallbackQueryParamVerifier(primary, secondary)
return validation.NewParamVerifyingSchema(schema, fallback, string(validationDirective)), nil return validation.NewParamVerifyingSchema(schema, fallback, string(validationDirective)), nil

Some files were not shown because too many files have changed in this diff Show More