Compare commits
107 Commits
v0.33.0-rc
...
master
Author | SHA1 | Date |
---|---|---|
|
e7f17cb570 | |
|
29d75ee358 | |
|
8067f3a1a6 | |
|
483c28b281 | |
|
2e87981eff | |
|
f47e5c84cc | |
|
76e6818d8d | |
|
f07a946956 | |
|
45a8bb4283 | |
|
0b7534c13c | |
|
e3a465587f | |
|
e470ab29d4 | |
|
02bb9287e7 | |
|
8aac463c8e | |
|
142b144574 | |
|
7c82ae36f3 | |
|
d97446c293 | |
|
a8605c1ee2 | |
|
1d576b52f0 | |
|
a8c44498b7 | |
|
daa78b3004 | |
|
9892d492ab | |
|
fe571ee1bb | |
|
4966ba7250 | |
|
1c1291fbf7 | |
|
7e06b5277c | |
|
6b06b293f9 | |
|
da311e8e7e | |
|
2fa4a24584 | |
|
a6fde79de4 | |
|
2da25b2322 | |
|
110ba5998e | |
|
716737e46a | |
|
8a11631ed9 | |
|
330f86df8e | |
|
55101ca654 | |
|
ab62ac8cf1 | |
|
af5ade99d8 | |
|
4d27286e9c | |
|
9130183f39 | |
|
e808dbbf1f | |
|
579a7a5a35 | |
|
0b4adb247f | |
|
49afb3c466 | |
|
17c0dde6b1 | |
|
bc4c094b08 | |
|
800afb48a7 | |
|
2375a3a9f6 | |
|
18f24e791d | |
|
9c13527bac | |
|
17bb82b84d | |
|
abe43f6e92 | |
|
bb9c5182ea | |
|
4ee16d2b51 | |
|
edad3048e9 | |
|
46d6f63709 | |
|
8500d2979d | |
|
4b5ec542ad | |
|
ca5a831a47 | |
|
b011cffff8 | |
|
cb7efba696 | |
|
2be4847754 | |
|
6096dfa3cf | |
|
279ddf3abe | |
|
5ff92a69e3 | |
|
777f5e3cd1 | |
|
4afda566a9 | |
|
c37ca76b9c | |
|
47f13bd18b | |
|
88bb12ba04 | |
|
52ec1da081 | |
|
307936eb9d | |
|
90ee929b88 | |
|
105c831190 | |
|
d35aa2c630 | |
|
f4a8c5b53e | |
|
5bcd2add11 | |
|
bb3c0d9f3a | |
|
3323e167c5 | |
|
3013d81bdf | |
|
38e8d36c38 | |
|
b0f5f0c0aa | |
|
65d852d39c | |
|
7ec7bb7cc1 | |
|
722397942b | |
|
0cdb311ed6 | |
|
4d172bd365 | |
|
285ed6ce48 | |
|
4627533853 | |
|
ae92d5f0bd | |
|
3a0b77ee9b | |
|
9301b2a1f6 | |
|
d40094d4e4 | |
|
46f95a7c68 | |
|
b2ed890887 | |
|
265eadfda5 | |
|
6c0aa1995f | |
|
72b3a7e9b0 | |
|
2e566591a6 | |
|
fec9d5b3d5 | |
|
399c585899 | |
|
ece3c8c1d0 | |
|
5b96de1a99 | |
|
3a69c59961 | |
|
e33a30ea7c | |
|
6323c5bc57 | |
|
81562142de |
|
@ -10,7 +10,7 @@
|
|||
# DO NOT REPORT SECURITY VULNERABILITIES DIRECTLY TO THESE NAMES, FOLLOW THE
|
||||
# INSTRUCTIONS AT https://kubernetes.io/security/
|
||||
|
||||
ardaguclu
|
||||
eddiezane
|
||||
KnVerey
|
||||
natasha41575
|
||||
mpuckett159
|
||||
soltysh
|
||||
|
|
55
go.mod
55
go.mod
|
@ -13,45 +13,46 @@ require (
|
|||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f
|
||||
github.com/fatih/camelcase v1.0.0
|
||||
github.com/go-openapi/jsonreference v0.20.2
|
||||
github.com/google/gnostic-models v0.6.9
|
||||
github.com/google/gnostic-models v0.7.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/jonboulle/clockwork v0.4.0
|
||||
github.com/jonboulle/clockwork v0.5.0
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de
|
||||
github.com/lithammer/dedent v1.1.0
|
||||
github.com/mitchellh/go-wordwrap v1.0.1
|
||||
github.com/moby/term v0.5.0
|
||||
github.com/onsi/ginkgo/v2 v2.21.0
|
||||
github.com/onsi/gomega v1.35.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/russross/blackfriday/v2 v2.1.0
|
||||
github.com/spf13/cobra v1.8.1
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
go.yaml.in/yaml/v2 v2.4.2
|
||||
golang.org/x/sys v0.31.0
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0
|
||||
k8s.io/api v0.0.0
|
||||
k8s.io/apimachinery v0.0.0
|
||||
k8s.io/cli-runtime v0.0.0
|
||||
k8s.io/client-go v0.0.0
|
||||
k8s.io/component-base v0.0.0
|
||||
k8s.io/component-helpers v0.0.0
|
||||
k8s.io/api v0.0.0-20250718010531-33ab3a26f4b3
|
||||
k8s.io/apimachinery v0.0.0-20250717210244-b92abb2d8139
|
||||
k8s.io/cli-runtime v0.0.0-20250717174531-64776d0a280f
|
||||
k8s.io/client-go v0.0.0-20250718010928-be36413bbca7
|
||||
k8s.io/component-base v0.0.0-20250717172125-4e07767df717
|
||||
k8s.io/component-helpers v0.0.0-20250717172249-5095859f5100
|
||||
k8s.io/klog/v2 v2.130.1
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff
|
||||
k8s.io/metrics v0.0.0
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
|
||||
k8s.io/metrics v0.0.0-20250717174355-244095fcc1c1
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0
|
||||
sigs.k8s.io/yaml v1.4.0
|
||||
sigs.k8s.io/randfill v1.0.0
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0
|
||||
sigs.k8s.io/yaml v1.5.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
|
@ -70,15 +71,17 @@ require (
|
|||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/moby/spdystream v0.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
|
||||
github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/peterbourgon/diskv v2.0.1+incompatible // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/oauth2 v0.27.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
|
@ -90,16 +93,6 @@ require (
|
|||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.19.0 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
)
|
||||
|
||||
replace (
|
||||
k8s.io/api => ../api
|
||||
k8s.io/apimachinery => ../apimachinery
|
||||
k8s.io/cli-runtime => ../cli-runtime
|
||||
k8s.io/client-go => ../client-go
|
||||
k8s.io/code-generator => ../code-generator
|
||||
k8s.io/component-base => ../component-base
|
||||
k8s.io/component-helpers => ../component-helpers
|
||||
k8s.io/metrics => ../metrics
|
||||
)
|
||||
replace k8s.io/code-generator => k8s.io/code-generator v0.0.0-20250718051115-9eb96548a40e
|
||||
|
|
106
go.sum
106
go.sum
|
@ -1,20 +1,14 @@
|
|||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
|
||||
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
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/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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/chzyer/readline v1.5.1/go.mod h1:Eh+b79XXUwfKfcPLepksvw2tcLE/Ct21YObkaSkeBlk=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
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/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
|
@ -23,21 +17,18 @@ 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/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
|
||||
github.com/distribution/reference v0.6.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/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU=
|
||||
github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4=
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc=
|
||||
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/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU=
|
||||
github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ=
|
||||
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-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
|
||||
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=
|
||||
|
@ -50,12 +41,10 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
|
|||
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
|
||||
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
|
||||
github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw=
|
||||
github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
|
||||
github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
|
@ -69,12 +58,10 @@ github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5T
|
|||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA=
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4=
|
||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc=
|
||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
|
@ -88,7 +75,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0=
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
|
||||
github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
|
||||
|
@ -104,8 +90,9 @@ github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3
|
|||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/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/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
|
@ -124,20 +111,16 @@ 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.22.0-rc.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0=
|
||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
|
||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
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/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/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
|
@ -157,27 +140,17 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ=
|
|||
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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
|
||||
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
|
||||
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
|
||||
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/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||
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.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
|
@ -197,7 +170,6 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
|
||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
@ -216,9 +188,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576/go.mod h1:1R3kvZ1dtP3+4p4d3G8uJ8rFk/fWlScl38vanWACI08=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241209162323-e6fa225c2576/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||
google.golang.org/grpc v1.68.1/go.mod h1:+q1XYFJjShcqn0QZHvCyeR4CXPA+llXIeUIfIe00waw=
|
||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
@ -231,26 +200,37 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
|||
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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
k8s.io/gengo/v2 v2.0.0-20250207200755-1244d31929d7/go.mod h1:EJykeLsmFC60UQbYJezXkEsG2FLrt0GPNkU5iK5GWxU=
|
||||
k8s.io/api v0.0.0-20250718010531-33ab3a26f4b3 h1:UnuyCQyBmdFlYypApF2w6Ld0R0kAt8b+0Lt9dYAr23I=
|
||||
k8s.io/api v0.0.0-20250718010531-33ab3a26f4b3/go.mod h1:K8dwhtttsRR0RHeSRF8XQ77gfMgyAj3q78/TkxEXhoc=
|
||||
k8s.io/apimachinery v0.0.0-20250717210244-b92abb2d8139 h1:jWBClrBPuk+GEA9pJzMa9IvxncSBbw7fmvey15nVm0w=
|
||||
k8s.io/apimachinery v0.0.0-20250717210244-b92abb2d8139/go.mod h1:v1p1Jsze3IHLy5gU17yVqR2qLO7jgYeX6mw3HZy2AEU=
|
||||
k8s.io/cli-runtime v0.0.0-20250717174531-64776d0a280f h1:E/GB1lzzKbz3HPJ6Zu1bJYrey6oDAIAA+RMEozCpPpU=
|
||||
k8s.io/cli-runtime v0.0.0-20250717174531-64776d0a280f/go.mod h1:SybB6wdHGt8FXxaHyNQqsUAhWcZKIDPurWPB5mfFLD0=
|
||||
k8s.io/client-go v0.0.0-20250718010928-be36413bbca7 h1:LNOJkn+3JlAEzdZzYheQM97gq6kKQfkrBN0GikI5nbc=
|
||||
k8s.io/client-go v0.0.0-20250718010928-be36413bbca7/go.mod h1:a14VvgYhux7oUSE9mWdzBuFKDZSGtperboMjQ1JtVgc=
|
||||
k8s.io/component-base v0.0.0-20250717172125-4e07767df717 h1:07oqkM0FzuGUw/bJw2rJubzccG7ShpGcTJ7SBDGp5Fc=
|
||||
k8s.io/component-base v0.0.0-20250717172125-4e07767df717/go.mod h1:/ehREU84M2OxVgU8WfxuUIi4/c5XsT6rIsEGQfhgxEQ=
|
||||
k8s.io/component-helpers v0.0.0-20250717172249-5095859f5100 h1:XEHmjwZgMNRuVgpqaRH/RR+n4BU0evfitU0RpWGPMUM=
|
||||
k8s.io/component-helpers v0.0.0-20250717172249-5095859f5100/go.mod h1:yxuY+YMknW7H9Bj7B29INyMOacJBa6oEG7gi7IKUzEQ=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4=
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA=
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts=
|
||||
k8s.io/metrics v0.0.0-20250717174355-244095fcc1c1 h1:V4I6U/hfhZYYmDotL7ukG0nua1luMQSox5QtveZaSv0=
|
||||
k8s.io/metrics v0.0.0-20250717174355-244095fcc1c1/go.mod h1:TkHVkU+vMKy7qppbMybraSCK8Y+LLpoqk/6Jl+M8EoU=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE=
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg=
|
||||
sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ=
|
||||
sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o=
|
||||
sigs.k8s.io/kustomize/cmd/config v0.19.0/go.mod h1:29Vvdl26PidPLUDi7nfjYa/I0wHBkwCZp15Nlcc4y98=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0 h1:MWtRRDWCwQEeW2rnJTqJMuV6Agy56P53SkbVoJpN7wA=
|
||||
sigs.k8s.io/kustomize/kustomize/v5 v5.6.0/go.mod h1:XuuZiQF7WdcvZzEYyNww9A0p3LazCKeJmCjeycN8e1I=
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA=
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY=
|
||||
sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU=
|
||||
sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 h1:IUA9nvMmnKWcj5jl84xn+T5MnlZKThmUW1TdblaLVAc=
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps=
|
||||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
|
||||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco=
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE=
|
||||
sigs.k8s.io/yaml v1.5.0 h1:M10b2U7aEUY6hRtU870n2VTPgR5RZiL/I6Lcc2F4NUQ=
|
||||
sigs.k8s.io/yaml v1.5.0/go.mod h1:wZs27Rbxoai4C0f8/9urLZtZtF3avA3gKvGyPdDqTO4=
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"sigs.k8s.io/structured-merge-diff/v4/fieldpath"
|
||||
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
|
|
|
@ -2605,7 +2605,6 @@ metadata:
|
|||
applyset.kubernetes.io/additional-namespaces: ""
|
||||
applyset.kubernetes.io/contains-group-kinds: ReplicationController
|
||||
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
|
||||
name: my-set
|
||||
|
@ -2639,7 +2638,6 @@ metadata:
|
|||
applyset.kubernetes.io/additional-namespaces: ""
|
||||
applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
|
||||
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
|
||||
name: my-set
|
||||
|
@ -2674,7 +2672,6 @@ metadata:
|
|||
applyset.kubernetes.io/additional-namespaces: ""
|
||||
applyset.kubernetes.io/contains-group-kinds: ReplicationController,Service
|
||||
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
|
||||
name: my-set
|
||||
|
@ -2709,7 +2706,6 @@ metadata:
|
|||
applyset.kubernetes.io/additional-namespaces: ""
|
||||
applyset.kubernetes.io/contains-group-kinds: Service
|
||||
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
|
||||
name: my-set
|
||||
|
@ -2872,7 +2868,6 @@ metadata:
|
|||
applyset.kubernetes.io/additional-namespaces: test
|
||||
applyset.kubernetes.io/contains-group-kinds: ReplicationController
|
||||
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.kubernetes.io/id: applyset-rhp1a-HVAVT_dFgyEygyA1BEB82HPp2o10UiFTpqtAs-v1
|
||||
name: my-set
|
||||
|
@ -3090,7 +3085,6 @@ metadata:
|
|||
applyset.kubernetes.io/additional-namespaces: ""
|
||||
applyset.kubernetes.io/contains-group-resources: replicationcontrollers
|
||||
applyset.kubernetes.io/tooling: kubectl/v0.0.0-master+$Format:%H$
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
applyset.kubernetes.io/id: applyset-0eFHV8ySqp7XoShsGvyWFQD3s96yqwHmzc4e0HR1dsY-v1
|
||||
name: my-set
|
||||
|
|
|
@ -23,8 +23,6 @@ import (
|
|||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/jonboulle/clockwork"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
|
@ -61,7 +59,7 @@ const (
|
|||
// patchRetryBackOffPeriod is the period to back off when apply patch results in error.
|
||||
var patchRetryBackOffPeriod = 1 * time.Second
|
||||
|
||||
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor:"
|
||||
var createPatchErrFormat = "creating patch with:\noriginal:\n%s\nmodified:\n%s\ncurrent:\n%s\nfor: %w"
|
||||
|
||||
// Patcher defines options to patch OpenAPI objects.
|
||||
type Patcher struct {
|
||||
|
@ -120,13 +118,13 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
|
|||
// Serialize the current configuration of the object from the server.
|
||||
current, err := runtime.Encode(unstructured.UnstructuredJSONScheme, obj)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "serializing current configuration from:\n%v\nfor:", obj)
|
||||
return nil, nil, fmt.Errorf("serializing current configuration from:\n%v\nfor: %w", obj, err)
|
||||
}
|
||||
|
||||
// Retrieve the original configuration of the object from the annotation.
|
||||
original, err := util.GetOriginalConfiguration(obj)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "retrieving original configuration from:\n%v\nfor:", obj)
|
||||
return nil, nil, fmt.Errorf("retrieving original configuration from:\n%v\nfor: %w", obj, err)
|
||||
}
|
||||
|
||||
var patchType types.PatchType
|
||||
|
@ -178,17 +176,17 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
|
|||
patchType = types.StrategicMergePatchType
|
||||
patch, err = p.buildStrategicMergeFromBuiltins(versionedObj, original, modified, current)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
|
||||
return nil, nil, fmt.Errorf(createPatchErrFormat, original, modified, current, err)
|
||||
}
|
||||
} else {
|
||||
if !runtime.IsNotRegisteredError(err) {
|
||||
return nil, nil, errors.Wrapf(err, "getting instance of versioned object for %v:", p.Mapping.GroupVersionKind)
|
||||
return nil, nil, fmt.Errorf("getting instance of versioned object for %v: %w", p.Mapping.GroupVersionKind, err)
|
||||
}
|
||||
|
||||
patchType = types.MergePatchType
|
||||
patch, err = p.buildMergePatch(original, modified, current)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, createPatchErrFormat, original, modified, current)
|
||||
return nil, nil, fmt.Errorf(createPatchErrFormat, original, modified, current, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +198,7 @@ func (p *Patcher) patchSimple(obj runtime.Object, modified []byte, namespace, na
|
|||
if p.ResourceVersion != nil {
|
||||
patch, err = addResourceVersion(patch, *p.ResourceVersion)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "Failed to insert resourceVersion in patch")
|
||||
return nil, nil, fmt.Errorf("failed to insert resourceVersion in patch: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -307,6 +307,7 @@ func (o *AttachOptions) Run() error {
|
|||
}
|
||||
|
||||
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.")
|
||||
}
|
||||
if err := t.Safe(o.AttachFunc(o, containerToAttach, t.Raw, sizeQueue)); err != nil {
|
||||
|
|
|
@ -242,6 +242,7 @@ func TestAttach(t *testing.T) {
|
|||
pod *corev1.Pod
|
||||
remoteAttachErr bool
|
||||
expectedErr string
|
||||
expectedErrOut []string
|
||||
}{
|
||||
{
|
||||
name: "pod attach",
|
||||
|
@ -251,6 +252,10 @@ func TestAttach(t *testing.T) {
|
|||
attachPath: "/api/" + version + "/namespaces/test/pods/foo/attach",
|
||||
pod: attachPod(),
|
||||
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",
|
||||
|
@ -305,10 +310,11 @@ func TestAttach(t *testing.T) {
|
|||
if test.remoteAttachErr {
|
||||
remoteAttach.err = fmt.Errorf("attach error")
|
||||
}
|
||||
streams, _, _, errOut := genericiooptions.NewTestIOStreams()
|
||||
options := &AttachOptions{
|
||||
StreamOptions: exec.StreamOptions{
|
||||
ContainerName: test.container,
|
||||
IOStreams: genericiooptions.NewTestIOStreamsDiscard(),
|
||||
IOStreams: streams,
|
||||
},
|
||||
Attach: remoteAttach,
|
||||
GetPodTimeout: 1000,
|
||||
|
@ -349,6 +355,14 @@ func TestAttach(t *testing.T) {
|
|||
if remoteAttach.url.Query().Get("container") != "bar" {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,9 +75,7 @@ func TestWhoAmIRun(t *testing.T) {
|
|||
`{
|
||||
"kind": "SelfSubjectReview",
|
||||
"apiVersion": "authentication.k8s.io/v1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"metadata": {},
|
||||
"status": {
|
||||
"userInfo": {
|
||||
"username": "jane.doe",
|
||||
|
@ -131,9 +129,7 @@ func TestWhoAmIRun(t *testing.T) {
|
|||
`{
|
||||
"kind": "SelfSubjectReview",
|
||||
"apiVersion": "authentication.k8s.io/v1beta1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"metadata": {},
|
||||
"status": {
|
||||
"userInfo": {
|
||||
"username": "jane.doe",
|
||||
|
@ -186,9 +182,7 @@ func TestWhoAmIRun(t *testing.T) {
|
|||
`{
|
||||
"kind": "SelfSubjectReview",
|
||||
"apiVersion": "authentication.k8s.io/v1",
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"metadata": {},
|
||||
"status": {
|
||||
"userInfo": {
|
||||
"username": "jane.doe",
|
||||
|
|
|
@ -19,14 +19,16 @@ package autoscale
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
|
@ -36,6 +38,7 @@ import (
|
|||
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/klog/v2"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util"
|
||||
|
@ -54,10 +57,16 @@ var (
|
|||
|
||||
autoscaleExample = templates.Examples(i18n.T(`
|
||||
# Auto scale a deployment "foo", with the number of pods between 2 and 10, no target CPU utilization specified so a default autoscaling policy will be used
|
||||
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%
|
||||
kubectl autoscale rc foo --max=5 --cpu-percent=80`))
|
||||
kubectl autoscale rc foo --max=5 --cpu=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
|
||||
|
@ -74,6 +83,8 @@ type AutoscaleOptions struct {
|
|||
Min int32
|
||||
Max int32
|
||||
CPUPercent int32
|
||||
CPU string
|
||||
Memory string
|
||||
|
||||
createAnnotation bool
|
||||
args []string
|
||||
|
@ -109,7 +120,7 @@ func NewCmdAutoscale(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *c
|
|||
validArgs := []string{"deployment", "replicaset", "replicationcontroller", "statefulset"}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu-percent=CPU]",
|
||||
Use: "autoscale (-f FILENAME | TYPE NAME | TYPE/NAME) [--min=MINPODS] --max=MAXPODS [--cpu=CPU] [--memory=MEMORY]",
|
||||
DisableFlagsInUseLine: true,
|
||||
Short: i18n.T("Auto-scale a deployment, replica set, stateful set, or replication controller"),
|
||||
Long: autoscaleLong,
|
||||
|
@ -129,7 +140,11 @@ 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.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().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().MarkDeprecated("cpu-percent",
|
||||
"Use --cpu with percentage or resource quantity format (e.g., '70%' for utilization or '500m' for milliCPU).")
|
||||
cmdutil.AddDryRunFlag(cmd)
|
||||
cmdutil.AddFilenameOptionFlags(cmd, o.FilenameOptions, "identifying the resource to autoscale.")
|
||||
cmdutil.AddApplyAnnotationFlags(cmd)
|
||||
|
@ -189,7 +204,22 @@ func (o *AutoscaleOptions) Validate() error {
|
|||
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)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
@ -214,16 +244,24 @@ func (o *AutoscaleOptions) Run() error {
|
|||
|
||||
mapping := info.ResourceMapping()
|
||||
gvr := mapping.GroupVersionKind.GroupVersion().WithResource(mapping.Resource.Resource)
|
||||
if _, err := o.scaleKindResolver.ScaleForResource(gvr); err != nil {
|
||||
return fmt.Errorf("cannot autoscale a %v: %v", mapping.GroupVersionKind.Kind, err)
|
||||
if _, err = o.scaleKindResolver.ScaleForResource(gvr); err != nil {
|
||||
return fmt.Errorf("cannot autoscale a %s: %w", mapping.GroupVersionKind.Kind, err)
|
||||
}
|
||||
|
||||
// handles the creation of HorizontalPodAutoscaler objects for both v2 and v1 APIs.
|
||||
// If v2 API fails, try to create and handle HorizontalPodAutoscaler using v1 API
|
||||
hpaV2 := o.createHorizontalPodAutoscalerV2(info.Name, mapping)
|
||||
if err := o.handleHPA(hpaV2); err != nil {
|
||||
klog.V(1).Infof("Encountered an error with the v2 HorizontalPodAutoscaler: %v. "+
|
||||
"Falling back to try the v1 HorizontalPodAutoscaler", err)
|
||||
// handles the creation of HorizontalPodAutoscaler objects for both autoscaling/v2 and autoscaling/v1 APIs.
|
||||
// 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
|
||||
|
@ -241,6 +279,18 @@ func (o *AutoscaleOptions) Run() error {
|
|||
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 {
|
||||
|
@ -288,7 +338,7 @@ func (o *AutoscaleOptions) handleHPA(hpa runtime.Object) error {
|
|||
return printer.PrintObj(actualHPA, o.Out)
|
||||
}
|
||||
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) *autoscalingv2.HorizontalPodAutoscaler {
|
||||
func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mapping *meta.RESTMapping) (*autoscalingv2.HorizontalPodAutoscaler, error) {
|
||||
name := o.Name
|
||||
if len(name) == 0 {
|
||||
name = refName
|
||||
|
@ -312,22 +362,83 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscalerV2(refName string, mappi
|
|||
scaler.Spec.MinReplicas = &o.Min
|
||||
}
|
||||
|
||||
if o.CPUPercent >= 0 {
|
||||
scaler.Spec.Metrics = []autoscalingv2.MetricSpec{
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: &o.CPUPercent,
|
||||
},
|
||||
},
|
||||
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)
|
||||
}
|
||||
|
||||
return &scaler
|
||||
// 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 {
|
||||
return nil, err
|
||||
}
|
||||
switch metricsType {
|
||||
case autoscalingv2.UtilizationMetricType:
|
||||
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)
|
||||
}
|
||||
|
||||
// add Memory metric if any of the memory targets are specified
|
||||
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 {
|
||||
|
@ -361,3 +472,72 @@ func (o *AutoscaleOptions) createHorizontalPodAutoscalerV1(refName string, mappi
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import (
|
|||
autoscalingv2 "k8s.io/api/autoscaling/v2"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
apiresource "k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/utils/ptr"
|
||||
|
@ -79,11 +80,113 @@ func TestAutoscaleValidate(t *testing.T) {
|
|||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "CPUPercent appears with CPU",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Min: 0,
|
||||
CPU: "800",
|
||||
CPUPercent: 20,
|
||||
},
|
||||
expectedError: fmt.Errorf("--cpu-percent and --cpu are mutually exclusive"),
|
||||
},
|
||||
{
|
||||
name: "CPUPercent default (-1) with CPU",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Min: 0,
|
||||
CPU: "800",
|
||||
CPUPercent: -1,
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid CPU percentage",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "70%",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid CPU numeric without unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "500",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid CPU with unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "500m",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid CPU value (non-numeric)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "abc",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource cpu value: abc"),
|
||||
},
|
||||
{
|
||||
name: "invalid CPU value (malformed unit)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
CPU: "500xyz",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource cpu value: 500xyz"),
|
||||
},
|
||||
{
|
||||
name: "valid memory percentage",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "60%",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid memory numeric without unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "512",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "valid memory with unit",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "512Mi",
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
name: "invalid memory value (non-numeric)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "xyz",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource memory value: xyz"),
|
||||
},
|
||||
{
|
||||
name: "invalid memory value (MiB unit)",
|
||||
options: &AutoscaleOptions{
|
||||
Max: 5,
|
||||
Memory: "512MiB",
|
||||
},
|
||||
expectedError: fmt.Errorf("invalid resource memory value: 512MiB"),
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
errorGot := tc.options.Validate()
|
||||
assert.Equal(t, tc.expectedError, errorGot)
|
||||
if errorGot != nil {
|
||||
assert.Equal(t, tc.expectedError.Error(), errorGot.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -98,6 +201,10 @@ type createHorizontalPodAutoscalerTestCase struct {
|
|||
}
|
||||
|
||||
func TestCreateHorizontalPodAutoscalerV2(t *testing.T) {
|
||||
cpu500m := apiresource.MustParse("500m")
|
||||
mem512Mi := apiresource.MustParse("512Mi")
|
||||
cpu2000m := apiresource.MustParse("2000m")
|
||||
mem3Gi := apiresource.MustParse("3Gi")
|
||||
tests := []createHorizontalPodAutoscalerTestCase{
|
||||
{
|
||||
name: "create with all options",
|
||||
|
@ -360,10 +467,396 @@ func TestCreateHorizontalPodAutoscalerV2(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(use %) and memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "70%",
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(70)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(use m unit) and memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "500m",
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu500m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(no use unit) and memory(use %) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "500",
|
||||
Memory: "50%",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu500m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.UtilizationMetricType,
|
||||
AverageUtilization: ptr.To(int32(50)),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with memory(no use unit) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
Memory: "512",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &mem512Mi,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with cpu(no use unit) and memory(no use unit) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "500",
|
||||
Memory: "512",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu500m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &mem512Mi,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "create with both cpu(use m unit) and memory(use Gi unit) options",
|
||||
options: &AutoscaleOptions{
|
||||
Name: "custom-name",
|
||||
Max: 10,
|
||||
Min: 2,
|
||||
CPU: "2000m",
|
||||
Memory: "3Gi",
|
||||
},
|
||||
refName: "deployment-1",
|
||||
mapping: &meta.RESTMapping{
|
||||
GroupVersionKind: schema.GroupVersionKind{
|
||||
Group: "apps",
|
||||
Version: "v1",
|
||||
Kind: "Deployment",
|
||||
},
|
||||
},
|
||||
expectedHPAV2: &autoscalingv2.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "custom-name",
|
||||
},
|
||||
Spec: autoscalingv2.HorizontalPodAutoscalerSpec{
|
||||
ScaleTargetRef: autoscalingv2.CrossVersionObjectReference{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "Deployment",
|
||||
Name: "deployment-1",
|
||||
},
|
||||
MinReplicas: ptr.To(int32(2)),
|
||||
MaxReplicas: int32(10),
|
||||
Metrics: []autoscalingv2.MetricSpec{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricSpec
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceCPU,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &cpu2000m,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: autoscalingv2.ResourceMetricSourceType,
|
||||
Resource: &autoscalingv2.ResourceMetricSource{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#ResourceMetricSource
|
||||
Name: corev1.ResourceMemory,
|
||||
Target: autoscalingv2.MetricTarget{
|
||||
// Reference: https://pkg.go.dev/k8s.io/api/autoscaling/v2#MetricTarget
|
||||
Type: autoscalingv2.AverageValueMetricType,
|
||||
AverageValue: &mem3Gi,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
hpa := tc.options.createHorizontalPodAutoscalerV2(tc.refName, tc.mapping)
|
||||
hpa, _ := tc.options.createHorizontalPodAutoscalerV2(tc.refName, tc.mapping)
|
||||
assert.Equal(t, tc.expectedHPAV2, hpa)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -83,8 +83,6 @@ import (
|
|||
"k8s.io/kubectl/pkg/cmd/kustomize"
|
||||
)
|
||||
|
||||
const kubectlCmdHeaders = "KUBECTL_COMMAND_HEADERS"
|
||||
|
||||
type KubectlOptions struct {
|
||||
PluginHandler PluginHandler
|
||||
Arguments []string
|
||||
|
@ -143,25 +141,23 @@ func NewDefaultKubectlCommandWithArgs(o KubectlOptions) *cobra.Command {
|
|||
}
|
||||
}
|
||||
} else if err == nil {
|
||||
if !cmdutil.CmdPluginAsSubcommand.IsDisabled() {
|
||||
// Command exists(e.g. kubectl create), but it is not certain that
|
||||
// subcommand also exists (e.g. kubectl create networkpolicy)
|
||||
// we also have to eliminate kubectl create -f
|
||||
if IsSubcommandPluginAllowed(foundCmd.Name()) && len(foundArgs) >= 1 && !strings.HasPrefix(foundArgs[0], "-") {
|
||||
subcommand := foundArgs[0]
|
||||
builtinSubcmdExist := false
|
||||
for _, subcmd := range foundCmd.Commands() {
|
||||
if subcmd.Name() == subcommand {
|
||||
builtinSubcmdExist = true
|
||||
break
|
||||
}
|
||||
// Command exists(e.g. kubectl create), but it is not certain that
|
||||
// subcommand also exists (e.g. kubectl create networkpolicy)
|
||||
// we also have to eliminate kubectl create -f
|
||||
if IsSubcommandPluginAllowed(foundCmd.Name()) && len(foundArgs) >= 1 && !strings.HasPrefix(foundArgs[0], "-") {
|
||||
subcommand := foundArgs[0]
|
||||
builtinSubcmdExist := false
|
||||
for _, subcmd := range foundCmd.Commands() {
|
||||
if subcmd.Name() == subcommand {
|
||||
builtinSubcmdExist = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !builtinSubcmdExist {
|
||||
if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, len(cmdPathPieces)-len(foundArgs)+1); err != nil {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if !builtinSubcmdExist {
|
||||
if err := HandlePluginCommand(o.PluginHandler, cmdPathPieces, len(cmdPathPieces)-len(foundArgs)+1); err != nil {
|
||||
fmt.Fprintf(o.IOStreams.ErrOut, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -363,7 +359,7 @@ 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")
|
||||
|
||||
pref := kuberc.NewPreferences()
|
||||
if cmdutil.KubeRC.IsEnabled() {
|
||||
if !cmdutil.KubeRC.IsDisabled() {
|
||||
pref.AddFlags(flags)
|
||||
}
|
||||
|
||||
|
@ -499,7 +495,7 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||
// add the klog flags later.
|
||||
cmds.SetGlobalNormalizationFunc(cliflag.WordSepNormalizeFunc)
|
||||
|
||||
if cmdutil.KubeRC.IsEnabled() {
|
||||
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)
|
||||
|
@ -524,12 +520,9 @@ func NewKubectlCommand(o KubectlOptions) *cobra.Command {
|
|||
//
|
||||
// https://github.com/kubernetes/enhancements/tree/master/keps/sig-cli/859-kubectl-headers
|
||||
func addCmdHeaderHooks(cmds *cobra.Command, kubeConfigFlags *genericclioptions.ConfigFlags) {
|
||||
// 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")
|
||||
return
|
||||
}
|
||||
if cmdutil.CmdHeaders.IsDisabled() {
|
||||
klog.V(5).Infoln("kubectl command headers turned off")
|
||||
return
|
||||
}
|
||||
klog.V(5).Infoln("kubectl command headers turned on")
|
||||
crt := &genericclioptions.CommandHeaderRoundTripper{}
|
||||
|
@ -583,3 +576,29 @@ func registerCompletionFuncForGlobalFlags(cmd *cobra.Command, f cmdutil.Factory)
|
|||
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"
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
"k8s.io/kubectl/pkg/cmd/plugin"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
)
|
||||
|
||||
func TestNormalizationFuncGlobalExistence(t *testing.T) {
|
||||
|
@ -381,10 +382,6 @@ func TestKubectlCommandHeadersHooks(t *testing.T) {
|
|||
envVar: "false",
|
||||
addsHooks: false,
|
||||
},
|
||||
"zero env var value; hooks NOT added": {
|
||||
envVar: "0",
|
||||
addsHooks: false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, testCase := range tests {
|
||||
|
@ -394,7 +391,7 @@ func TestKubectlCommandHeadersHooks(t *testing.T) {
|
|||
if kubeConfigFlags.WrapConfigFn != nil {
|
||||
t.Fatal("expected initial nil WrapConfigFn")
|
||||
}
|
||||
t.Setenv(kubectlCmdHeaders, testCase.envVar)
|
||||
t.Setenv(string(cmdutil.CmdHeaders), testCase.envVar)
|
||||
addCmdHeaderHooks(cmds, kubeConfigFlags)
|
||||
// Valdidate whether the hooks were added.
|
||||
if testCase.addsHooks && kubeConfigFlags.WrapConfigFn == nil {
|
||||
|
|
|
@ -69,7 +69,6 @@ func Example_view() {
|
|||
// name: federal-context
|
||||
// current-context: federal-context
|
||||
// kind: Config
|
||||
// preferences: {}
|
||||
// users:
|
||||
// - name: red-user
|
||||
// user:
|
||||
|
|
|
@ -85,7 +85,6 @@ contexts:
|
|||
name: my-cluster
|
||||
current-context: minikube
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: minikube
|
||||
user:
|
||||
|
@ -165,7 +164,6 @@ contexts:
|
|||
name: my-cluster
|
||||
current-context: minikube
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: minikube
|
||||
user:
|
||||
|
@ -247,7 +245,6 @@ contexts:
|
|||
name: minikube
|
||||
current-context: minikube
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: minikube
|
||||
user:
|
||||
|
@ -272,7 +269,6 @@ contexts:
|
|||
name: my-cluster
|
||||
current-context: my-cluster
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: mu-cluster
|
||||
user:
|
||||
|
|
|
@ -280,7 +280,7 @@ func (o *CopyOptions) checkDestinationIsDir(dest fileSpec) error {
|
|||
Executor: &exec.DefaultRemoteExecutor{},
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
done := make(chan error)
|
||||
|
|
|
@ -99,8 +99,7 @@ func TestCreateToken(t *testing.T) {
|
|||
serverResponseToken: "abc",
|
||||
expectStdout: `apiVersion: authentication.k8s.io/v1
|
||||
kind: TokenRequest
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
metadata: {}
|
||||
spec:
|
||||
audiences: null
|
||||
boundObjectRef: null
|
||||
|
|
|
@ -76,7 +76,7 @@ var (
|
|||
* Node: Create a new pod that runs in the node's host namespaces and can access
|
||||
the node's filesystem.
|
||||
|
||||
Note: When a non-root user is configured for the entire target Pod, some capabilities granted
|
||||
Note: When a non-root user is configured for the entire target Pod, some capabilities granted
|
||||
by debug profile may not work.
|
||||
`))
|
||||
|
||||
|
@ -727,6 +727,9 @@ func (o *DebugOptions) generateNodeDebugPod(node *corev1.Node) (*corev1.Pod, err
|
|||
p := &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: pn,
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
|
|
@ -1468,6 +1468,9 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1522,6 +1525,9 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1578,6 +1584,9 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1631,6 +1640,9 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1683,6 +1695,9 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1723,6 +1738,9 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1771,6 +1789,9 @@ func TestGenerateNodeDebugPod(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1863,6 +1884,9 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1916,6 +1940,9 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "node-debugger-node-XXX-1",
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
|
@ -1972,6 +1999,11 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
|
@ -2035,6 +2067,11 @@ func TestGenerateNodeDebugPodCustomProfile(t *testing.T) {
|
|||
},
|
||||
},
|
||||
expected: &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: map[string]string{
|
||||
"app.kubernetes.io/managed-by": "kubectl-debug",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
|
|
|
@ -506,6 +506,10 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) {
|
|||
operation = "force deleted"
|
||||
}
|
||||
|
||||
if info.Namespaced() {
|
||||
operation = fmt.Sprintf("%s from %s namespace", operation, info.Namespace)
|
||||
}
|
||||
|
||||
switch o.DryRunStrategy {
|
||||
case cmdutil.DryRunClient:
|
||||
operation = fmt.Sprintf("%s (dry run)", operation)
|
||||
|
@ -524,7 +528,7 @@ func (o *DeleteOptions) PrintObj(info *resource.Info) {
|
|||
}
|
||||
|
||||
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))
|
||||
fmt.Fprintf(o.Out, i18n.T("You are about to delete the following %d resource(s):\n"), len(infos)) //nolint:errcheck
|
||||
for _, info := range infos {
|
||||
groupKind := info.Mapping.GroupVersionKind
|
||||
kindString := fmt.Sprintf("%s.%s", strings.ToLower(groupKind.Kind), groupKind.Group)
|
||||
|
@ -532,11 +536,11 @@ func (o *DeleteOptions) confirmation(infos []*resource.Info) bool {
|
|||
kindString = strings.ToLower(groupKind.Kind)
|
||||
}
|
||||
|
||||
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name)
|
||||
fmt.Fprintf(o.Out, "%s/%s\n", kindString, info.Name) //nolint:errcheck
|
||||
}
|
||||
fmt.Fprint(o.Out, i18n.T("Do you want to continue?")+" (y/n): ")
|
||||
fmt.Fprint(o.Out, i18n.T("Do you want to continue?")+" (y/N): ") //nolint:errcheck
|
||||
var input string
|
||||
_, err := fmt.Fscan(o.In, &input)
|
||||
_, err := fmt.Fscanln(o.In, &input)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -375,7 +375,7 @@ func TestDeleteObjectWithInteractive(t *testing.T) {
|
|||
}
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -396,7 +396,7 @@ func TestDeleteObjectWithInteractive(t *testing.T) {
|
|||
}
|
||||
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())
|
||||
}
|
||||
if buf.String() == ": replicationcontroller/redis-master\n" {
|
||||
|
@ -972,3 +972,39 @@ 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())
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ func TestDiffProgram(t *testing.T) {
|
|||
externalDiffCommands := [3]string{"diff", "diff -ruN", "diff --report-identical-files"}
|
||||
|
||||
t.Setenv("LANG", "C")
|
||||
t.Setenv("LANGUAGE", "en_US")
|
||||
|
||||
for i, c := range externalDiffCommands {
|
||||
t.Setenv("KUBECTL_EXTERNAL_DIFF", c)
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
yaml "go.yaml.in/yaml/v2"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
|
@ -39,7 +40,6 @@ import (
|
|||
"k8s.io/kubectl/pkg/cmd/create"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
yaml "sigs.k8s.io/yaml/goyaml.v2"
|
||||
)
|
||||
|
||||
type EditTestCase struct {
|
||||
|
|
|
@ -25,7 +25,7 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
yaml "sigs.k8s.io/yaml/goyaml.v2"
|
||||
yaml "go.yaml.in/yaml/v2"
|
||||
)
|
||||
|
||||
type EditTestCase struct {
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"kind": "Service",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"creationTimestamp": "2017-02-27T19:40:53Z",
|
||||
"labels": {
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
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":{}}}
|
||||
{"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":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
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":{}}}
|
||||
{"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":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
|
|
|
@ -29,7 +29,6 @@ spec:
|
|||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
|
|
|
@ -29,7 +29,6 @@ spec:
|
|||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
app: nginx
|
||||
spec:
|
||||
|
|
|
@ -33,7 +33,6 @@
|
|||
},
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"labels": {
|
||||
"app": "nginx"
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"kind": "Service",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"creationTimestamp": "2017-02-27T19:40:53Z",
|
||||
"labels": {
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
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":{}}}
|
||||
{"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":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
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":{}}}
|
||||
{"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":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"kind": "Service",
|
||||
"metadata": {
|
||||
"annotations": {
|
||||
"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"
|
||||
"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"
|
||||
},
|
||||
"creationTimestamp": "2017-02-27T19:40:53Z",
|
||||
"labels": {
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
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":{}}}
|
||||
{"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":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -7,7 +7,7 @@ kind: Service
|
|||
metadata:
|
||||
annotations:
|
||||
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":{}}}
|
||||
{"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":{}}}
|
||||
creationTimestamp: "2017-02-27T19:40:53Z"
|
||||
labels:
|
||||
app: svc1
|
||||
|
|
|
@ -89,7 +89,7 @@ func NewExplainFlags(streams genericiooptions.IOStreams) *ExplainFlags {
|
|||
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().StringVar(&flags.OutputFormat, "output", plaintextTemplateName, "Format in which to render the schema (plaintext, plaintext-openapiv2)")
|
||||
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
|
||||
|
|
|
@ -694,7 +694,6 @@ func TestExposeOverride(t *testing.T) {
|
|||
expected: `apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
svc: test
|
||||
name: foo
|
||||
|
@ -717,7 +716,6 @@ status:
|
|||
expected: `apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
svc: test
|
||||
name: foo
|
||||
|
@ -745,7 +743,6 @@ status:
|
|||
expected: `apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
svc: test
|
||||
name: foo
|
||||
|
@ -773,7 +770,6 @@ status:
|
|||
expected: `apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
svc: test
|
||||
name: foo
|
||||
|
|
|
@ -182,7 +182,7 @@ 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().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.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
|
||||
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().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.")
|
||||
addServerPrintColumnFlags(cmd, o)
|
||||
|
@ -623,6 +623,10 @@ func (o *GetOptions) watch(f cmdutil.Factory, args []string) error {
|
|||
}
|
||||
infos, err := r.Infos()
|
||||
if err != nil {
|
||||
// Ignore "NotFound" error when ignore-not-found is set to true
|
||||
if apierrors.IsNotFound(err) && o.IgnoreNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if multipleGVKsRequested(infos) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
@ -711,7 +712,7 @@ func TestGetEmptyTable(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetObjectIgnoreNotFound(t *testing.T) {
|
||||
func TestGetNonExistObject(t *testing.T) {
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
|
||||
ns := &corev1.NamespaceList{
|
||||
|
@ -745,6 +746,63 @@ func TestGetObjectIgnoreNotFound(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()
|
||||
cmd := NewCmdGet("kubectl", tf, streams)
|
||||
cmd.SetOut(buf)
|
||||
|
@ -1438,7 +1496,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "foo",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "10"
|
||||
|
@ -1457,7 +1514,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Pod",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "bar",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "11"
|
||||
|
@ -1476,7 +1532,6 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) {
|
|||
"apiVersion": "v1",
|
||||
"kind": "Service",
|
||||
"metadata": {
|
||||
"creationTimestamp": null,
|
||||
"name": "baz",
|
||||
"namespace": "test",
|
||||
"resourceVersion": "12"
|
||||
|
@ -2129,6 +2184,93 @@ 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) {
|
||||
pods, events := watchTestData()
|
||||
events = append(events, watch.Event{Type: "ERROR", Object: &metav1.Status{Status: "Failure", Reason: "InternalServerError", Message: "Something happened"}})
|
||||
|
@ -2367,10 +2509,10 @@ DELETED test pod/foo 0/0 0 <unknown> <none>
|
|||
},
|
||||
{
|
||||
format: "json",
|
||||
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":{"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":{"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":{"creationTimestamp":null,"name":"foo","namespace":"test","resourceVersion":"12"},"spec":{"containers":null,"dnsPolicy":"ClusterFirst","enableServiceLinks":true,"restartPolicy":"Always","securityContext":{},"terminationGracePeriodSeconds":30},"status":{}}}
|
||||
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":{}}}
|
||||
{"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":"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":"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":{}}}
|
||||
`,
|
||||
},
|
||||
{
|
||||
|
@ -2379,7 +2521,6 @@ DELETED test pod/foo 0/0 0 <unknown> <none>
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: bar
|
||||
namespace: test
|
||||
resourceVersion: "9"
|
||||
|
@ -2397,7 +2538,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "10"
|
||||
|
@ -2415,7 +2555,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "11"
|
||||
|
@ -2433,7 +2572,6 @@ object:
|
|||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: foo
|
||||
namespace: test
|
||||
resourceVersion: "12"
|
||||
|
|
|
@ -22,7 +22,6 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/spf13/cobra"
|
||||
jsonpatch "gopkg.in/evanphx/json-patch.v4"
|
||||
"k8s.io/klog/v2"
|
||||
|
@ -260,7 +259,7 @@ func (o *PatchOptions) RunPatch() error {
|
|||
patchedObj, err := helper.Patch(namespace, name, patchType, patchBytes, nil)
|
||||
if err != nil {
|
||||
if apierrors.IsUnsupportedMediaType(err) {
|
||||
return errors.Wrap(err, fmt.Sprintf("%s is not supported by %s", patchType, mapping.GroupVersionKind))
|
||||
return fmt.Errorf("%s is not supported by %s: %w", patchType, mapping.GroupVersionKind, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -105,7 +105,7 @@ func NewCmdRolloutHistory(f cmdutil.Factory, streams genericiooptions.IOStreams)
|
|||
return cmd
|
||||
}
|
||||
|
||||
// Complete completes al the required options
|
||||
// Complete completes all the required options
|
||||
func (o *RolloutHistoryOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
o.Resources = args
|
||||
|
||||
|
@ -177,7 +177,15 @@ func (o *RolloutHistoryOptions) Run() error {
|
|||
}
|
||||
|
||||
if o.Revision > 0 {
|
||||
printer.PrintObj(historyInfo[o.Revision], o.Out)
|
||||
// Ensure the specified revision exists before printing
|
||||
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 {
|
||||
sortedKeys := make([]int64, 0, len(historyInfo))
|
||||
for k := range historyInfo {
|
||||
|
|
|
@ -280,15 +280,12 @@ func TestRolloutHistoryWithOutput(t *testing.T) {
|
|||
"kind": "ReplicaSet",
|
||||
"apiVersion": "apps/v1",
|
||||
"metadata": {
|
||||
"name": "rev2",
|
||||
"creationTimestamp": null
|
||||
"name": "rev2"
|
||||
},
|
||||
"spec": {
|
||||
"selector": null,
|
||||
"template": {
|
||||
"metadata": {
|
||||
"creationTimestamp": null
|
||||
},
|
||||
"metadata": {},
|
||||
"spec": {
|
||||
"containers": null
|
||||
}
|
||||
|
@ -305,13 +302,11 @@ func TestRolloutHistoryWithOutput(t *testing.T) {
|
|||
expectedOutput: `apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: rev2
|
||||
spec:
|
||||
selector: null
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
metadata: {}
|
||||
spec:
|
||||
containers: null
|
||||
status:
|
||||
|
@ -323,13 +318,11 @@ status:
|
|||
expectedOutput: `apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: rev1
|
||||
spec:
|
||||
selector: null
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
metadata: {}
|
||||
spec:
|
||||
containers: null
|
||||
status:
|
||||
|
@ -338,13 +331,11 @@ status:
|
|||
apiVersion: apps/v1
|
||||
kind: ReplicaSet
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: rev2
|
||||
spec:
|
||||
selector: null
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
metadata: {}
|
||||
spec:
|
||||
containers: null
|
||||
status:
|
||||
|
@ -401,6 +392,88 @@ 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) {
|
||||
opts := RolloutHistoryOptions{
|
||||
Revision: 0,
|
||||
|
|
|
@ -655,7 +655,6 @@ func TestRunOverride(t *testing.T) {
|
|||
expectedOutput: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: test
|
||||
name: test
|
||||
|
@ -678,7 +677,6 @@ status: {}
|
|||
expectedOutput: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: test
|
||||
name: test
|
||||
|
@ -701,7 +699,6 @@ status: {}
|
|||
expectedOutput: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
run: test
|
||||
name: test
|
||||
|
@ -729,7 +726,6 @@ status: {}
|
|||
expectedOutput: `apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
foo: bar
|
||||
run: test
|
||||
|
|
|
@ -39,9 +39,31 @@ var (
|
|||
topLong = templates.LongDesc(i18n.T(`
|
||||
Display resource (CPU/memory) usage.
|
||||
|
||||
The top command allows you to see the resource consumption for nodes or pods.
|
||||
This command provides a view of recent resource consumption for nodes and 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.
|
||||
|
||||
This command requires Metrics Server to be correctly configured and working on the server. `))
|
||||
The metrics shown are specifically optimized for Kubernetes autoscaling
|
||||
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 {
|
||||
|
|
|
@ -19,9 +19,9 @@ package top
|
|||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
|
@ -45,6 +45,7 @@ type TopNodeOptions struct {
|
|||
NoHeaders bool
|
||||
UseProtocolBuffers bool
|
||||
ShowCapacity bool
|
||||
ShowSwap bool
|
||||
|
||||
NodeClient corev1client.CoreV1Interface
|
||||
Printer *metricsutil.TopCmdPrinter
|
||||
|
@ -95,6 +96,7 @@ 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.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.ShowSwap, "show-swap", o.ShowSwap, "Print node resources related to swap memory.")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -127,7 +129,7 @@ func (o *TopNodeOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []
|
|||
|
||||
o.NodeClient = clientset.CoreV1()
|
||||
|
||||
o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
|
||||
o.Printer = metricsutil.NewTopCmdPrinter(o.Out, o.ShowSwap)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -198,6 +200,14 @@ func (o TopNodeOptions) RunTopNode() error {
|
|||
} else {
|
||||
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)
|
||||
|
|
|
@ -424,3 +424,111 @@ 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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,7 @@ type TopPodOptions struct {
|
|||
NoHeaders bool
|
||||
UseProtocolBuffers bool
|
||||
Sum bool
|
||||
ShowSwap bool
|
||||
|
||||
PodClient corev1client.PodsGetter
|
||||
Printer *metricsutil.TopCmdPrinter
|
||||
|
@ -117,6 +118,7 @@ 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.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.ShowSwap, "show-swap", o.ShowSwap, "Print pod resources related to swap memory.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -152,7 +154,7 @@ func (o *TopPodOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []s
|
|||
|
||||
o.PodClient = clientset.CoreV1()
|
||||
|
||||
o.Printer = metricsutil.NewTopCmdPrinter(o.Out)
|
||||
o.Printer = metricsutil.NewTopCmdPrinter(o.Out, o.ShowSwap)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -168,6 +168,12 @@ func TestTopPod(t *testing.T) {
|
|||
namespaces: []string{testNS, testNS, testNS},
|
||||
containers: true,
|
||||
},
|
||||
{
|
||||
name: "with swap",
|
||||
options: &TopPodOptions{AllNamespaces: true, ShowSwap: true},
|
||||
namespaces: []string{testNS, "secondtestns", "thirdtestns"},
|
||||
listsNamespaces: true,
|
||||
},
|
||||
}
|
||||
cmdtesting.InitTestErrorHandler(t)
|
||||
for _, testCase := range testCases {
|
||||
|
@ -284,10 +290,101 @@ func TestTopPod(t *testing.T) {
|
|||
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 {
|
||||
resultLines := strings.Split(result, "\n")
|
||||
values := make([]string, len(resultLines)-2) // don't process first (header) and last (empty) line
|
||||
|
@ -410,6 +507,7 @@ func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
|
|||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(1, 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),
|
||||
},
|
||||
},
|
||||
|
@ -418,6 +516,7 @@ func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
|
|||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(4, 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),
|
||||
},
|
||||
},
|
||||
|
@ -462,6 +561,7 @@ func testV1beta1PodMetricsData() []metricsv1beta1api.PodMetrics {
|
|||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(7, 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),
|
||||
},
|
||||
},
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
metricsv1beta1api "k8s.io/metrics/pkg/apis/metrics/v1beta1"
|
||||
"k8s.io/utils/ptr"
|
||||
)
|
||||
|
||||
func TestTopSubcommandsExist(t *testing.T) {
|
||||
|
@ -63,6 +64,7 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
|
|||
Window: metav1.Duration{Duration: time.Minute},
|
||||
Usage: v1.ResourceList{
|
||||
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.ResourceStorage: *resource.NewQuantity(3*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
|
@ -72,6 +74,7 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
|
|||
Window: metav1.Duration{Duration: time.Minute},
|
||||
Usage: v1.ResourceList{
|
||||
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.ResourceStorage: *resource.NewQuantity(7*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
|
@ -81,6 +84,7 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
|
|||
Window: metav1.Duration{Duration: time.Minute},
|
||||
Usage: v1.ResourceList{
|
||||
v1.ResourceCPU: *resource.NewMilliQuantity(3, resource.DecimalSI),
|
||||
"swap": *resource.NewQuantity(0, resource.DecimalSI),
|
||||
v1.ResourceMemory: *resource.NewQuantity(4*(1024*1024), resource.DecimalSI),
|
||||
v1.ResourceStorage: *resource.NewQuantity(5*(1024*1024), resource.DecimalSI),
|
||||
},
|
||||
|
@ -100,6 +104,11 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
|
|||
v1.ResourceMemory: *resource.NewQuantity(20*(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))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -110,6 +119,11 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
|
|||
v1.ResourceMemory: *resource.NewQuantity(60*(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))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -120,6 +134,11 @@ func testNodeV1beta1MetricsData() (*metricsv1beta1api.NodeMetricsList, *v1.NodeL
|
|||
v1.ResourceMemory: *resource.NewQuantity(40*(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))),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
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
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
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()
|
||||
})
|
||||
}
|
||||
}
|
|
@ -172,7 +172,7 @@ func (f *factoryImpl) Validator(validationDirective string) (validation.Schema,
|
|||
// the discovery client.
|
||||
oapiV3Client := cached.NewClient(discoveryClient.OpenAPIV3())
|
||||
queryParam := resource.QueryParamFieldValidation
|
||||
primary := resource.NewQueryParamVerifierV3(dynamicClient, oapiV3Client, queryParam)
|
||||
primary := newCachingVerifier(resource.NewQueryParamVerifierV3(dynamicClient, oapiV3Client, queryParam))
|
||||
secondary := resource.NewQueryParamVerifier(dynamicClient, f.openAPIGetter(), queryParam)
|
||||
fallback := resource.NewFallbackQueryParamVerifier(primary, secondary)
|
||||
return validation.NewParamVerifyingSchema(schema, fallback, string(validationDirective)), nil
|
||||
|
|
|
@ -425,14 +425,36 @@ func GetPodRunningTimeoutFlag(cmd *cobra.Command) (time.Duration, error) {
|
|||
type FeatureGate string
|
||||
|
||||
const (
|
||||
ApplySet FeatureGate = "KUBECTL_APPLYSET"
|
||||
CmdPluginAsSubcommand FeatureGate = "KUBECTL_ENABLE_CMD_SHADOW"
|
||||
OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
|
||||
// owner: @soltysh
|
||||
// kep: https://kep.k8s.io/859
|
||||
//
|
||||
// HTTP headers with command name and flags used.
|
||||
CmdHeaders FeatureGate = "KUBECTL_COMMAND_HEADERS"
|
||||
|
||||
// owner: @ardaguclu
|
||||
// kep: https://kep.k8s.io/3104
|
||||
//
|
||||
// Separate kubectl user preferences.
|
||||
KubeRC FeatureGate = "KUBECTL_KUBERC"
|
||||
|
||||
// owner: @soltysh
|
||||
// kep: https://kep.k8s.io/3515
|
||||
//
|
||||
// Improved kubectl apply --prune behavior.
|
||||
OpenAPIV3Patch FeatureGate = "KUBECTL_OPENAPIV3_PATCH"
|
||||
|
||||
// owner: @justinb
|
||||
// kep: https://kep.k8s.io/3659
|
||||
//
|
||||
// Improved kubectl apply --prune behavior.
|
||||
ApplySet FeatureGate = "KUBECTL_APPLYSET"
|
||||
|
||||
// owner: @seans
|
||||
// kep: https://kep.k8s.io/4006
|
||||
//
|
||||
// Transition to WebSockets.
|
||||
RemoteCommandWebsockets FeatureGate = "KUBECTL_REMOTE_COMMAND_WEBSOCKETS"
|
||||
PortForwardWebsockets FeatureGate = "KUBECTL_PORT_FORWARD_WEBSOCKETS"
|
||||
// DebugCustomProfile should be dropped in 1.34
|
||||
DebugCustomProfile FeatureGate = "KUBECTL_DEBUG_CUSTOM_PROFILE"
|
||||
KubeRC FeatureGate = "KUBECTL_KUBERC"
|
||||
)
|
||||
|
||||
// IsEnabled returns true iff environment variable is set to true.
|
||||
|
|
|
@ -18,10 +18,10 @@ package version
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/version"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
"math"
|
||||
)
|
||||
|
||||
// supportedMinorVersionSkew is the maximum supported difference between the client and server minor versions.
|
||||
|
@ -29,26 +29,26 @@ import (
|
|||
// and server versions 1.18, 1.19, and 1.20 would be within the supported version skew for client version 1.19.
|
||||
const supportedMinorVersionSkew = 1
|
||||
|
||||
// printVersionSkewWarning prints a warning message if the difference between the client and version is greater than
|
||||
// getVersionSkewWarning returns a warning message if the difference between the client and version is greater than
|
||||
// the supported version skew.
|
||||
func printVersionSkewWarning(w io.Writer, clientVersion, serverVersion apimachineryversion.Info) error {
|
||||
func getVersionSkewWarning(clientVersion, serverVersion apimachineryversion.Info) (string, error) {
|
||||
parsedClientVersion, err := version.ParseSemantic(clientVersion.GitVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", fmt.Errorf("client version error: %w", err)
|
||||
}
|
||||
|
||||
parsedServerVersion, err := version.ParseSemantic(serverVersion.GitVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
return "", fmt.Errorf("server version error: %w", err)
|
||||
}
|
||||
|
||||
majorVersionDifference := math.Abs(float64(parsedClientVersion.Major()) - float64(parsedServerVersion.Major()))
|
||||
minorVersionDifference := math.Abs(float64(parsedClientVersion.Minor()) - float64(parsedServerVersion.Minor()))
|
||||
|
||||
if majorVersionDifference > 0 || minorVersionDifference > supportedMinorVersionSkew {
|
||||
fmt.Fprintf(w, "WARNING: version difference between client (%d.%d) and server (%d.%d) exceeds the supported minor version skew of +/-%d\n",
|
||||
warningMessage := fmt.Sprintf("version difference between client (%d.%d) and server (%d.%d) exceeds the supported minor version skew of +/-%d",
|
||||
parsedClientVersion.Major(), parsedClientVersion.Minor(), parsedServerVersion.Major(), parsedServerVersion.Minor(), supportedMinorVersionSkew)
|
||||
return warningMessage, nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return "", nil
|
||||
}
|
||||
|
|
|
@ -17,14 +17,12 @@ limitations under the License.
|
|||
package version
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
"testing"
|
||||
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
func TestPrintVersionSkewWarning(t *testing.T) {
|
||||
output := &bytes.Buffer{}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
clientVersion apimachineryversion.Info
|
||||
|
@ -82,14 +80,15 @@ func TestPrintVersionSkewWarning(t *testing.T) {
|
|||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
output.Reset()
|
||||
warningMessage, err := getVersionSkewWarning(tc.clientVersion, tc.serverVersion)
|
||||
if err != nil {
|
||||
t.Errorf("error: %s", err)
|
||||
}
|
||||
|
||||
printVersionSkewWarning(output, tc.clientVersion, tc.serverVersion)
|
||||
|
||||
if tc.isWarningExpected && output.Len() == 0 {
|
||||
t.Error("warning was expected, but not written to the output")
|
||||
} else if !tc.isWarningExpected && output.Len() > 0 {
|
||||
t.Errorf("warning was not expected, but was written to the output: %s", output.String())
|
||||
if tc.isWarningExpected && warningMessage == "" {
|
||||
t.Error("warning was expected")
|
||||
} else if !tc.isWarningExpected && warningMessage != "" {
|
||||
t.Errorf("warning was not expected. but got %s", warningMessage)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -53,8 +53,9 @@ var (
|
|||
|
||||
// Options is a struct to support version command
|
||||
type Options struct {
|
||||
ClientOnly bool
|
||||
Output string
|
||||
ClientOnly bool
|
||||
Output string
|
||||
WarningsAsErrors bool
|
||||
|
||||
args []string
|
||||
|
||||
|
@ -93,6 +94,9 @@ func NewCmdVersion(f cmdutil.Factory, ioStreams genericiooptions.IOStreams) *cob
|
|||
// Complete completes all the required options
|
||||
func (o *Options) Complete(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
|
||||
var err error
|
||||
|
||||
o.WarningsAsErrors = cmd.Flags().Lookup("warnings-as-errors").Value.String() == "true"
|
||||
|
||||
if o.ClientOnly {
|
||||
return nil
|
||||
}
|
||||
|
@ -162,11 +166,17 @@ func (o *Options) Run() error {
|
|||
}
|
||||
|
||||
if versionInfo.ServerVersion != nil {
|
||||
if err := printVersionSkewWarning(o.ErrOut, *versionInfo.ClientVersion, *versionInfo.ServerVersion); err != nil {
|
||||
warningMessage, err := getVersionSkewWarning(*versionInfo.ClientVersion, *versionInfo.ServerVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if warningMessage != "" {
|
||||
if o.WarningsAsErrors {
|
||||
return errors.New(warningMessage)
|
||||
}
|
||||
fmt.Fprintf(o.ErrOut, "Warning: %s\n", warningMessage) //nolint:errcheck
|
||||
}
|
||||
}
|
||||
|
||||
return serverErr
|
||||
}
|
||||
|
||||
|
|
|
@ -20,8 +20,6 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"k8s.io/cli-runtime/pkg/genericiooptions"
|
||||
|
||||
cmdtesting "k8s.io/kubectl/pkg/cmd/testing"
|
||||
|
@ -32,13 +30,18 @@ func TestNewCmdVersionClientVersion(t *testing.T) {
|
|||
defer tf.Cleanup()
|
||||
streams, _, buf, _ := genericiooptions.NewTestIOStreams()
|
||||
o := NewOptions(streams)
|
||||
if err := o.Complete(tf, &cobra.Command{}, nil); err != nil {
|
||||
|
||||
cmd := NewCmdVersion(tf, streams)
|
||||
cmd.Flags().Bool("warnings-as-errors", false, "")
|
||||
|
||||
if err := o.Complete(tf, cmd, nil); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if err := o.Validate(); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if err := o.Complete(tf, &cobra.Command{}, []string{"extraParameter0"}); err != nil {
|
||||
|
||||
if err := o.Complete(tf, cmd, []string{"extraParameter0"}); err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if err := o.Validate(); !strings.Contains(err.Error(), "extra arguments") {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2024 The Kubernetes Authors.
|
||||
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.
|
||||
|
@ -14,19 +14,20 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package install installs the experimental API group, making it available as
|
||||
// an option to all of the API encoding/decoding machinery.
|
||||
package install
|
||||
package fuzzer
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"sigs.k8s.io/randfill"
|
||||
|
||||
runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/kubectl/pkg/config"
|
||||
"k8s.io/kubectl/pkg/config/v1alpha1"
|
||||
)
|
||||
|
||||
// Install registers the API group and adds types to a scheme
|
||||
func Install(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(config.AddToScheme(scheme))
|
||||
utilruntime.Must(v1alpha1.AddToScheme(scheme))
|
||||
// Funcs returns the fuzzer functions for the kubectl apis.
|
||||
func Funcs(codecs runtimeserializer.CodecFactory) []interface{} {
|
||||
return []interface{}{
|
||||
func(obj *config.Preference, c randfill.Continue) {
|
||||
c.FillNoCustom(obj)
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
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 scheme
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/kubectl/pkg/config"
|
||||
"k8s.io/kubectl/pkg/config/v1alpha1"
|
||||
"k8s.io/kubectl/pkg/config/v1beta1"
|
||||
)
|
||||
|
||||
var (
|
||||
// Scheme defines methods for serializing and deserializing API objects.
|
||||
Scheme = runtime.NewScheme()
|
||||
// StrictCodecs provides methods for retrieving codecs and serializers
|
||||
// for specific versions and content types.
|
||||
StrictCodecs = serializer.NewCodecFactory(Scheme, serializer.EnableStrict)
|
||||
// LenientCodecs provides methods for retrieving codecs and serializers
|
||||
// for specific versions and content types.
|
||||
LenientCodecs = serializer.NewCodecFactory(Scheme, serializer.DisableStrict)
|
||||
)
|
||||
|
||||
func init() {
|
||||
AddToScheme(Scheme)
|
||||
}
|
||||
|
||||
// AddToScheme registers the API group and adds types to a scheme
|
||||
func AddToScheme(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(config.AddToScheme(scheme))
|
||||
utilruntime.Must(v1beta1.AddToScheme(scheme))
|
||||
utilruntime.Must(v1alpha1.AddToScheme(scheme))
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
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 scheme
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/apitesting/roundtrip"
|
||||
"k8s.io/kubectl/pkg/config/fuzzer"
|
||||
)
|
||||
|
||||
func TestRoundTripTypes(t *testing.T) {
|
||||
roundtrip.RoundTripTestForScheme(t, Scheme, fuzzer.Funcs)
|
||||
}
|
|
@ -24,26 +24,26 @@ import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|||
type Preference struct {
|
||||
metav1.TypeMeta
|
||||
|
||||
// overrides allows changing default flag values of commands.
|
||||
// Defaults allow changing default option values of commands.
|
||||
// This is especially useful, when user doesn't want to explicitly
|
||||
// set flags each time.
|
||||
// set options each time.
|
||||
// +optional
|
||||
Overrides []CommandOverride
|
||||
Defaults []CommandDefaults
|
||||
|
||||
// aliases allows defining command aliases for existing kubectl commands, with optional default flag values.
|
||||
// Aliases allow defining command aliases for existing kubectl commands, with optional default option values.
|
||||
// If the alias name collides with a built-in command, built-in command always takes precedence.
|
||||
// Flag overrides defined in the overrides section do NOT apply to aliases for the same command.
|
||||
// kubectl [ALIAS NAME] [USER_FLAGS] [USER_EXPLICIT_ARGS] expands to
|
||||
// Option overrides defined in the defaults section do NOT apply to aliases for the same command.
|
||||
// kubectl [ALIAS NAME] [USER_OPTIONS] [USER_EXPLICIT_ARGS] expands to
|
||||
// kubectl [COMMAND] # built-in command alias points to
|
||||
// [KUBERC_PREPEND_ARGS]
|
||||
// [USER_FLAGS]
|
||||
// [KUBERC_FLAGS] # rest of the flags that are not passed by user in [USER_FLAGS]
|
||||
// [USER_OPTIONS]
|
||||
// [KUBERC_OPTIONS] # rest of the options that are not passed by user in [USER_OPTIONS]
|
||||
// [USER_EXPLICIT_ARGS]
|
||||
// [KUBERC_APPEND_ARGS]
|
||||
// e.g.
|
||||
// - name: runx
|
||||
// command: run
|
||||
// flags:
|
||||
// options:
|
||||
// - name: image
|
||||
// default: nginx
|
||||
// appendArgs:
|
||||
|
@ -53,7 +53,7 @@ type Preference struct {
|
|||
// this will be expanded to "kubectl run --image=nginx test-pod -- custom-arg1"
|
||||
// - name: getn
|
||||
// command: get
|
||||
// flags:
|
||||
// options:
|
||||
// - name: output
|
||||
// default: wide
|
||||
// prependArgs:
|
||||
|
@ -78,26 +78,28 @@ type AliasOverride struct {
|
|||
// AppendArgs stores the arguments such as resource names, etc.
|
||||
// These arguments are appended to the USER_ARGS.
|
||||
AppendArgs []string
|
||||
// Flag is allocated to store the flag definitions of alias
|
||||
Flags []CommandOverrideFlag
|
||||
// Options is allocated to store the option definitions of alias.
|
||||
// Options only modify the default value of the option and if
|
||||
// user explicitly passes a value, explicit one is used.
|
||||
Options []CommandOptionDefault
|
||||
}
|
||||
|
||||
// CommandOverride stores the commands and their associated flag's
|
||||
// CommandDefaults stores the commands and their associated option's
|
||||
// default values.
|
||||
type CommandOverride struct {
|
||||
type CommandDefaults struct {
|
||||
// Command refers to a command whose flag's default value is changed.
|
||||
Command string
|
||||
// Flags is a list of flags storing different default values.
|
||||
Flags []CommandOverrideFlag
|
||||
// Options is a list of options storing different default values.
|
||||
Options []CommandOptionDefault
|
||||
}
|
||||
|
||||
// CommandOverrideFlag stores the name and the specified default
|
||||
// value of the flag.
|
||||
type CommandOverrideFlag struct {
|
||||
// Flag name (long form, without dashes).
|
||||
Name string `json:"name"`
|
||||
// CommandOptionDefault stores the name and the specified default
|
||||
// value of an option.
|
||||
type CommandOptionDefault struct {
|
||||
// Option name (long form, without dashes).
|
||||
Name string
|
||||
|
||||
// In a string format of a default value. It will be parsed
|
||||
// by kubectl to the compatible value of the flag.
|
||||
Default string `json:"default"`
|
||||
// by kubectl to the compatible value of the option.
|
||||
Default string
|
||||
}
|
||||
|
|
|
@ -28,9 +28,9 @@ type Preference struct {
|
|||
// This is especially useful, when user doesn't want to explicitly
|
||||
// set flags each time.
|
||||
// +listType=atomic
|
||||
Overrides []CommandOverride `json:"overrides"`
|
||||
Defaults []CommandDefaults `json:"overrides"`
|
||||
|
||||
// aliases allows defining command aliases for existing kubectl commands, with optional default flag values.
|
||||
// aliases allow defining command aliases for existing kubectl commands, with optional default flag values.
|
||||
// If the alias name collides with a built-in command, built-in command always takes precedence.
|
||||
// Flag overrides defined in the overrides section do NOT apply to aliases for the same command.
|
||||
// kubectl [ALIAS NAME] [USER_FLAGS] [USER_EXPLICIT_ARGS] expands to
|
||||
|
@ -66,40 +66,40 @@ type Preference struct {
|
|||
|
||||
// AliasOverride stores the alias definitions.
|
||||
type AliasOverride struct {
|
||||
// Name is the name of alias that can only include alphabetical characters
|
||||
// name is the name of alias that can only include alphabetical characters
|
||||
// If the alias name conflicts with the built-in command,
|
||||
// built-in command will be used.
|
||||
Name string `json:"name"`
|
||||
// Command is the single or set of commands to execute, such as "set env" or "create"
|
||||
// command is the single or set of commands to execute, such as "set env" or "create"
|
||||
Command string `json:"command"`
|
||||
// PrependArgs stores the arguments such as resource names, etc.
|
||||
// prependArgs stores the arguments such as resource names, etc.
|
||||
// These arguments are inserted after the alias name.
|
||||
// +listType=atomic
|
||||
PrependArgs []string `json:"prependArgs,omitempty"`
|
||||
// AppendArgs stores the arguments such as resource names, etc.
|
||||
// appendArgs stores the arguments such as resource names, etc.
|
||||
// These arguments are appended to the USER_ARGS.
|
||||
// +listType=atomic
|
||||
AppendArgs []string `json:"appendArgs,omitempty"`
|
||||
// Flag is allocated to store the flag definitions of alias.
|
||||
// Flag only modifies the default value of the flag and if
|
||||
// flags is allocated to store the flag definitions of alias.
|
||||
// flags only modifies the default value of the flag and if
|
||||
// user explicitly passes a value, explicit one is used.
|
||||
// +listType=atomic
|
||||
Flags []CommandOverrideFlag `json:"flags,omitempty"`
|
||||
Options []CommandOptionDefault `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// CommandOverride stores the commands and their associated flag's
|
||||
// CommandDefaults stores the commands and their associated option's
|
||||
// default values.
|
||||
type CommandOverride struct {
|
||||
// Command refers to a command whose flag's default value is changed.
|
||||
type CommandDefaults struct {
|
||||
// command refers to a command whose flag's default value is changed.
|
||||
Command string `json:"command"`
|
||||
// Flags is a list of flags storing different default values.
|
||||
// flags is a list of flags storing different default values.
|
||||
// +listType=atomic
|
||||
Flags []CommandOverrideFlag `json:"flags"`
|
||||
Options []CommandOptionDefault `json:"flags"`
|
||||
}
|
||||
|
||||
// CommandOverrideFlag stores the name and the specified default
|
||||
// value of the flag.
|
||||
type CommandOverrideFlag struct {
|
||||
// CommandOptionDefault stores the name and the specified default
|
||||
// value of an option.
|
||||
type CommandOptionDefault struct {
|
||||
// Flag name (long form, without dashes).
|
||||
Name string `json:"name"`
|
||||
|
||||
|
|
|
@ -46,23 +46,23 @@ func RegisterConversions(s *runtime.Scheme) error {
|
|||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*CommandOverride)(nil), (*config.CommandOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_CommandOverride_To_config_CommandOverride(a.(*CommandOverride), b.(*config.CommandOverride), scope)
|
||||
if err := s.AddGeneratedConversionFunc((*CommandDefaults)(nil), (*config.CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_CommandDefaults_To_config_CommandDefaults(a.(*CommandDefaults), b.(*config.CommandDefaults), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.CommandOverride)(nil), (*CommandOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_CommandOverride_To_v1alpha1_CommandOverride(a.(*config.CommandOverride), b.(*CommandOverride), scope)
|
||||
if err := s.AddGeneratedConversionFunc((*config.CommandDefaults)(nil), (*CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_CommandDefaults_To_v1alpha1_CommandDefaults(a.(*config.CommandDefaults), b.(*CommandDefaults), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*CommandOverrideFlag)(nil), (*config.CommandOverrideFlag)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(a.(*CommandOverrideFlag), b.(*config.CommandOverrideFlag), scope)
|
||||
if err := s.AddGeneratedConversionFunc((*CommandOptionDefault)(nil), (*config.CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(a.(*CommandOptionDefault), b.(*config.CommandOptionDefault), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.CommandOverrideFlag)(nil), (*CommandOverrideFlag)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(a.(*config.CommandOverrideFlag), b.(*CommandOverrideFlag), scope)
|
||||
if err := s.AddGeneratedConversionFunc((*config.CommandOptionDefault)(nil), (*CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(a.(*config.CommandOptionDefault), b.(*CommandOptionDefault), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ func autoConvert_v1alpha1_AliasOverride_To_config_AliasOverride(in *AliasOverrid
|
|||
out.Command = in.Command
|
||||
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
|
||||
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
|
||||
out.Flags = *(*[]config.CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
|
||||
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -98,7 +98,7 @@ func autoConvert_config_AliasOverride_To_v1alpha1_AliasOverride(in *config.Alias
|
|||
out.Command = in.Command
|
||||
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
|
||||
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
|
||||
out.Flags = *(*[]CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
|
||||
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -107,52 +107,52 @@ func Convert_config_AliasOverride_To_v1alpha1_AliasOverride(in *config.AliasOver
|
|||
return autoConvert_config_AliasOverride_To_v1alpha1_AliasOverride(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_CommandOverride_To_config_CommandOverride(in *CommandOverride, out *config.CommandOverride, s conversion.Scope) error {
|
||||
func autoConvert_v1alpha1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
|
||||
out.Command = in.Command
|
||||
out.Flags = *(*[]config.CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
|
||||
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_CommandOverride_To_config_CommandOverride is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_CommandOverride_To_config_CommandOverride(in *CommandOverride, out *config.CommandOverride, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_CommandOverride_To_config_CommandOverride(in, out, s)
|
||||
// Convert_v1alpha1_CommandDefaults_To_config_CommandDefaults is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_CommandDefaults_To_config_CommandDefaults(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_CommandOverride_To_v1alpha1_CommandOverride(in *config.CommandOverride, out *CommandOverride, s conversion.Scope) error {
|
||||
func autoConvert_config_CommandDefaults_To_v1alpha1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
|
||||
out.Command = in.Command
|
||||
out.Flags = *(*[]CommandOverrideFlag)(unsafe.Pointer(&in.Flags))
|
||||
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_CommandOverride_To_v1alpha1_CommandOverride is an autogenerated conversion function.
|
||||
func Convert_config_CommandOverride_To_v1alpha1_CommandOverride(in *config.CommandOverride, out *CommandOverride, s conversion.Scope) error {
|
||||
return autoConvert_config_CommandOverride_To_v1alpha1_CommandOverride(in, out, s)
|
||||
// Convert_config_CommandDefaults_To_v1alpha1_CommandDefaults is an autogenerated conversion function.
|
||||
func Convert_config_CommandDefaults_To_v1alpha1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
|
||||
return autoConvert_config_CommandDefaults_To_v1alpha1_CommandDefaults(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in *CommandOverrideFlag, out *config.CommandOverrideFlag, s conversion.Scope) error {
|
||||
func autoConvert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Default = in.Default
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in *CommandOverrideFlag, out *config.CommandOverrideFlag, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_CommandOverrideFlag_To_config_CommandOverrideFlag(in, out, s)
|
||||
// Convert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault is an autogenerated conversion function.
|
||||
func Convert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
|
||||
return autoConvert_v1alpha1_CommandOptionDefault_To_config_CommandOptionDefault(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in *config.CommandOverrideFlag, out *CommandOverrideFlag, s conversion.Scope) error {
|
||||
func autoConvert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Default = in.Default
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag is an autogenerated conversion function.
|
||||
func Convert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in *config.CommandOverrideFlag, out *CommandOverrideFlag, s conversion.Scope) error {
|
||||
return autoConvert_config_CommandOverrideFlag_To_v1alpha1_CommandOverrideFlag(in, out, s)
|
||||
// Convert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault is an autogenerated conversion function.
|
||||
func Convert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
|
||||
return autoConvert_config_CommandOptionDefault_To_v1alpha1_CommandOptionDefault(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1alpha1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
|
||||
out.Overrides = *(*[]config.CommandOverride)(unsafe.Pointer(&in.Overrides))
|
||||
out.Defaults = *(*[]config.CommandDefaults)(unsafe.Pointer(&in.Defaults))
|
||||
out.Aliases = *(*[]config.AliasOverride)(unsafe.Pointer(&in.Aliases))
|
||||
return nil
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ func Convert_v1alpha1_Preference_To_config_Preference(in *Preference, out *confi
|
|||
}
|
||||
|
||||
func autoConvert_config_Preference_To_v1alpha1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
|
||||
out.Overrides = *(*[]CommandOverride)(unsafe.Pointer(&in.Overrides))
|
||||
out.Defaults = *(*[]CommandDefaults)(unsafe.Pointer(&in.Defaults))
|
||||
out.Aliases = *(*[]AliasOverride)(unsafe.Pointer(&in.Aliases))
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -38,9 +38,9 @@ func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Flags != nil {
|
||||
in, out := &in.Flags, &out.Flags
|
||||
*out = make([]CommandOverrideFlag, len(*in))
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make([]CommandOptionDefault, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
|
@ -57,38 +57,38 @@ func (in *AliasOverride) DeepCopy() *AliasOverride {
|
|||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommandOverride) DeepCopyInto(out *CommandOverride) {
|
||||
func (in *CommandDefaults) DeepCopyInto(out *CommandDefaults) {
|
||||
*out = *in
|
||||
if in.Flags != nil {
|
||||
in, out := &in.Flags, &out.Flags
|
||||
*out = make([]CommandOverrideFlag, len(*in))
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make([]CommandOptionDefault, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverride.
|
||||
func (in *CommandOverride) DeepCopy() *CommandOverride {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandDefaults.
|
||||
func (in *CommandDefaults) DeepCopy() *CommandDefaults {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommandOverride)
|
||||
out := new(CommandDefaults)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommandOverrideFlag) DeepCopyInto(out *CommandOverrideFlag) {
|
||||
func (in *CommandOptionDefault) DeepCopyInto(out *CommandOptionDefault) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverrideFlag.
|
||||
func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOptionDefault.
|
||||
func (in *CommandOptionDefault) DeepCopy() *CommandOptionDefault {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommandOverrideFlag)
|
||||
out := new(CommandOptionDefault)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
@ -97,9 +97,9 @@ func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
|
|||
func (in *Preference) DeepCopyInto(out *Preference) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Overrides != nil {
|
||||
in, out := &in.Overrides, &out.Overrides
|
||||
*out = make([]CommandOverride, len(*in))
|
||||
if in.Defaults != nil {
|
||||
in, out := &in.Defaults, &out.Defaults
|
||||
*out = make([]CommandDefaults, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:openapi-gen=true
|
||||
// +groupName=kubectl.config.k8s.io
|
||||
// +k8s:conversion-gen=k8s.io/kubectl/pkg/config
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
|
||||
package v1beta1 // Package v1beta1 import "k8s.io/kubectl/pkg/config/v1beta1"
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 v1beta1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// GroupName is the group name used in this package
|
||||
const GroupName = "kubectl.config.k8s.io"
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"}
|
||||
|
||||
var (
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
func init() {
|
||||
// We only register manually written functions here. The registration of the
|
||||
// generated functions takes place in the generated files. The separation
|
||||
// makes the code compile even when the generated files are missing.
|
||||
localSchemeBuilder.Register(addKnownTypes)
|
||||
}
|
||||
|
||||
// addKnownTypes registers known types to the given scheme
|
||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||
&Preference{},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
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 v1beta1
|
||||
|
||||
import metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
|
||||
// Preference stores elements of KubeRC configuration file
|
||||
type Preference struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
|
||||
// defaults allow changing default option values of commands.
|
||||
// This is especially useful, when user doesn't want to explicitly
|
||||
// set options each time.
|
||||
// +listType=atomic
|
||||
Defaults []CommandDefaults `json:"defaults"`
|
||||
|
||||
// aliases allow defining command aliases for existing kubectl commands, with optional default option values.
|
||||
// If the alias name collides with a built-in command, built-in command always takes precedence.
|
||||
// Option overrides defined in the defaults section do NOT apply to aliases for the same command.
|
||||
// kubectl [ALIAS NAME] [USER_OPTIONS] [USER_EXPLICIT_ARGS] expands to
|
||||
// kubectl [COMMAND] # built-in command alias points to
|
||||
// [KUBERC_PREPEND_ARGS]
|
||||
// [USER_OPTIONS]
|
||||
// [KUBERC_OPTIONS] # rest of the options that are not passed by user in [USER_OPTIONS]
|
||||
// [USER_EXPLICIT_ARGS]
|
||||
// [KUBERC_APPEND_ARGS]
|
||||
// e.g.
|
||||
// - name: runx
|
||||
// command: run
|
||||
// options:
|
||||
// - name: image
|
||||
// default: nginx
|
||||
// appendArgs:
|
||||
// - --
|
||||
// - custom-arg1
|
||||
// For example, if user invokes "kubectl runx test-pod" command,
|
||||
// this will be expanded to "kubectl run --image=nginx test-pod -- custom-arg1"
|
||||
// - name: getn
|
||||
// command: get
|
||||
// options:
|
||||
// - name: output
|
||||
// default: wide
|
||||
// prependArgs:
|
||||
// - node
|
||||
// "kubectl getn control-plane-1" expands to "kubectl get node control-plane-1 --output=wide"
|
||||
// "kubectl getn control-plane-1 --output=json" expands to "kubectl get node --output=json control-plane-1"
|
||||
// +listType=atomic
|
||||
Aliases []AliasOverride `json:"aliases"`
|
||||
}
|
||||
|
||||
// AliasOverride stores the alias definitions.
|
||||
type AliasOverride struct {
|
||||
// name is the name of alias that can only include alphabetical characters
|
||||
// If the alias name conflicts with the built-in command,
|
||||
// built-in command will be used.
|
||||
Name string `json:"name"`
|
||||
// command is the single or set of commands to execute, such as "set env" or "create"
|
||||
Command string `json:"command"`
|
||||
// prependArgs stores the arguments such as resource names, etc.
|
||||
// These arguments are inserted after the alias name.
|
||||
// +listType=atomic
|
||||
PrependArgs []string `json:"prependArgs,omitempty"`
|
||||
// appendArgs stores the arguments such as resource names, etc.
|
||||
// These arguments are appended to the USER_ARGS.
|
||||
// +listType=atomic
|
||||
AppendArgs []string `json:"appendArgs,omitempty"`
|
||||
// options is allocated to store the option definitions of alias.
|
||||
// options only modify the default value of the option and if
|
||||
// user explicitly passes a value, explicit one is used.
|
||||
// +listType=atomic
|
||||
Options []CommandOptionDefault `json:"options,omitempty"`
|
||||
}
|
||||
|
||||
// CommandDefaults stores the commands and their associated option's
|
||||
// default values.
|
||||
type CommandDefaults struct {
|
||||
// command refers to a command whose option's default value is changed.
|
||||
Command string `json:"command"`
|
||||
// options is a list of options storing different default values.
|
||||
// +listType=atomic
|
||||
Options []CommandOptionDefault `json:"options"`
|
||||
}
|
||||
|
||||
// CommandOptionDefault stores the name and the specified default
|
||||
// value of an option.
|
||||
type CommandOptionDefault struct {
|
||||
// Option name (long form, without dashes).
|
||||
Name string `json:"name"`
|
||||
|
||||
// In a string format of a default value. It will be parsed
|
||||
// by kubectl to the compatible value of the option.
|
||||
Default string `json:"default"`
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by conversion-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
unsafe "unsafe"
|
||||
|
||||
conversion "k8s.io/apimachinery/pkg/conversion"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
config "k8s.io/kubectl/pkg/config"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(RegisterConversions)
|
||||
}
|
||||
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*AliasOverride)(nil), (*config.AliasOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_AliasOverride_To_config_AliasOverride(a.(*AliasOverride), b.(*config.AliasOverride), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.AliasOverride)(nil), (*AliasOverride)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_AliasOverride_To_v1beta1_AliasOverride(a.(*config.AliasOverride), b.(*AliasOverride), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*CommandDefaults)(nil), (*config.CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_CommandDefaults_To_config_CommandDefaults(a.(*CommandDefaults), b.(*config.CommandDefaults), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.CommandDefaults)(nil), (*CommandDefaults)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_CommandDefaults_To_v1beta1_CommandDefaults(a.(*config.CommandDefaults), b.(*CommandDefaults), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*CommandOptionDefault)(nil), (*config.CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(a.(*CommandOptionDefault), b.(*config.CommandOptionDefault), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.CommandOptionDefault)(nil), (*CommandOptionDefault)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(a.(*config.CommandOptionDefault), b.(*CommandOptionDefault), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*Preference)(nil), (*config.Preference)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_Preference_To_config_Preference(a.(*Preference), b.(*config.Preference), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*config.Preference)(nil), (*Preference)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_config_Preference_To_v1beta1_Preference(a.(*config.Preference), b.(*Preference), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_AliasOverride_To_config_AliasOverride(in *AliasOverride, out *config.AliasOverride, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Command = in.Command
|
||||
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
|
||||
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
|
||||
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_AliasOverride_To_config_AliasOverride is an autogenerated conversion function.
|
||||
func Convert_v1beta1_AliasOverride_To_config_AliasOverride(in *AliasOverride, out *config.AliasOverride, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_AliasOverride_To_config_AliasOverride(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_AliasOverride_To_v1beta1_AliasOverride(in *config.AliasOverride, out *AliasOverride, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Command = in.Command
|
||||
out.PrependArgs = *(*[]string)(unsafe.Pointer(&in.PrependArgs))
|
||||
out.AppendArgs = *(*[]string)(unsafe.Pointer(&in.AppendArgs))
|
||||
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_AliasOverride_To_v1beta1_AliasOverride is an autogenerated conversion function.
|
||||
func Convert_config_AliasOverride_To_v1beta1_AliasOverride(in *config.AliasOverride, out *AliasOverride, s conversion.Scope) error {
|
||||
return autoConvert_config_AliasOverride_To_v1beta1_AliasOverride(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
|
||||
out.Command = in.Command
|
||||
out.Options = *(*[]config.CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_CommandDefaults_To_config_CommandDefaults is an autogenerated conversion function.
|
||||
func Convert_v1beta1_CommandDefaults_To_config_CommandDefaults(in *CommandDefaults, out *config.CommandDefaults, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_CommandDefaults_To_config_CommandDefaults(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_CommandDefaults_To_v1beta1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
|
||||
out.Command = in.Command
|
||||
out.Options = *(*[]CommandOptionDefault)(unsafe.Pointer(&in.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_CommandDefaults_To_v1beta1_CommandDefaults is an autogenerated conversion function.
|
||||
func Convert_config_CommandDefaults_To_v1beta1_CommandDefaults(in *config.CommandDefaults, out *CommandDefaults, s conversion.Scope) error {
|
||||
return autoConvert_config_CommandDefaults_To_v1beta1_CommandDefaults(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Default = in.Default
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault is an autogenerated conversion function.
|
||||
func Convert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(in *CommandOptionDefault, out *config.CommandOptionDefault, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_CommandOptionDefault_To_config_CommandOptionDefault(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Default = in.Default
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault is an autogenerated conversion function.
|
||||
func Convert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(in *config.CommandOptionDefault, out *CommandOptionDefault, s conversion.Scope) error {
|
||||
return autoConvert_config_CommandOptionDefault_To_v1beta1_CommandOptionDefault(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
|
||||
out.Defaults = *(*[]config.CommandDefaults)(unsafe.Pointer(&in.Defaults))
|
||||
out.Aliases = *(*[]config.AliasOverride)(unsafe.Pointer(&in.Aliases))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_Preference_To_config_Preference is an autogenerated conversion function.
|
||||
func Convert_v1beta1_Preference_To_config_Preference(in *Preference, out *config.Preference, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_Preference_To_config_Preference(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_config_Preference_To_v1beta1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
|
||||
out.Defaults = *(*[]CommandDefaults)(unsafe.Pointer(&in.Defaults))
|
||||
out.Aliases = *(*[]AliasOverride)(unsafe.Pointer(&in.Aliases))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_config_Preference_To_v1beta1_Preference is an autogenerated conversion function.
|
||||
func Convert_config_Preference_To_v1beta1_Preference(in *config.Preference, out *Preference, s conversion.Scope) error {
|
||||
return autoConvert_config_Preference_To_v1beta1_Preference(in, out, s)
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
|
||||
*out = *in
|
||||
if in.PrependArgs != nil {
|
||||
in, out := &in.PrependArgs, &out.PrependArgs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.AppendArgs != nil {
|
||||
in, out := &in.AppendArgs, &out.AppendArgs
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make([]CommandOptionDefault, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AliasOverride.
|
||||
func (in *AliasOverride) DeepCopy() *AliasOverride {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(AliasOverride)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommandDefaults) DeepCopyInto(out *CommandDefaults) {
|
||||
*out = *in
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make([]CommandOptionDefault, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandDefaults.
|
||||
func (in *CommandDefaults) DeepCopy() *CommandDefaults {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommandDefaults)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommandOptionDefault) DeepCopyInto(out *CommandOptionDefault) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOptionDefault.
|
||||
func (in *CommandOptionDefault) DeepCopy() *CommandOptionDefault {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommandOptionDefault)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Preference) DeepCopyInto(out *Preference) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Defaults != nil {
|
||||
in, out := &in.Defaults, &out.Defaults
|
||||
*out = make([]CommandDefaults, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
if in.Aliases != nil {
|
||||
in, out := &in.Aliases, &out.Aliases
|
||||
*out = make([]AliasOverride, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Preference.
|
||||
func (in *Preference) DeepCopy() *Preference {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Preference)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *Preference) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
/*
|
||||
Copyright 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.
|
||||
*/
|
||||
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
|
@ -38,9 +38,9 @@ func (in *AliasOverride) DeepCopyInto(out *AliasOverride) {
|
|||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Flags != nil {
|
||||
in, out := &in.Flags, &out.Flags
|
||||
*out = make([]CommandOverrideFlag, len(*in))
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make([]CommandOptionDefault, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
|
@ -57,38 +57,38 @@ func (in *AliasOverride) DeepCopy() *AliasOverride {
|
|||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommandOverride) DeepCopyInto(out *CommandOverride) {
|
||||
func (in *CommandDefaults) DeepCopyInto(out *CommandDefaults) {
|
||||
*out = *in
|
||||
if in.Flags != nil {
|
||||
in, out := &in.Flags, &out.Flags
|
||||
*out = make([]CommandOverrideFlag, len(*in))
|
||||
if in.Options != nil {
|
||||
in, out := &in.Options, &out.Options
|
||||
*out = make([]CommandOptionDefault, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverride.
|
||||
func (in *CommandOverride) DeepCopy() *CommandOverride {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandDefaults.
|
||||
func (in *CommandDefaults) DeepCopy() *CommandDefaults {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommandOverride)
|
||||
out := new(CommandDefaults)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *CommandOverrideFlag) DeepCopyInto(out *CommandOverrideFlag) {
|
||||
func (in *CommandOptionDefault) DeepCopyInto(out *CommandOptionDefault) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOverrideFlag.
|
||||
func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CommandOptionDefault.
|
||||
func (in *CommandOptionDefault) DeepCopy() *CommandOptionDefault {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(CommandOverrideFlag)
|
||||
out := new(CommandOptionDefault)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
@ -97,9 +97,9 @@ func (in *CommandOverrideFlag) DeepCopy() *CommandOverrideFlag {
|
|||
func (in *Preference) DeepCopyInto(out *Preference) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
if in.Overrides != nil {
|
||||
in, out := &in.Overrides, &out.Overrides
|
||||
*out = make([]CommandOverride, len(*in))
|
||||
if in.Defaults != nil {
|
||||
in, out := &in.Defaults, &out.Defaults
|
||||
*out = make([]CommandDefaults, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
|
|
|
@ -22,9 +22,11 @@ import (
|
|||
"crypto/x509"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -1863,7 +1865,11 @@ func describeContainerBasicInfo(container corev1.Container, status corev1.Contai
|
|||
func describeContainerPorts(cPorts []corev1.ContainerPort) string {
|
||||
ports := make([]string, 0, len(cPorts))
|
||||
for _, cPort := range cPorts {
|
||||
ports = append(ports, fmt.Sprintf("%d/%s", cPort.ContainerPort, cPort.Protocol))
|
||||
portStr := fmt.Sprintf("%d/%s", cPort.ContainerPort, cPort.Protocol)
|
||||
if cPort.Name != "" {
|
||||
portStr = fmt.Sprintf("%s (%s)", portStr, cPort.Name)
|
||||
}
|
||||
ports = append(ports, portStr)
|
||||
}
|
||||
return strings.Join(ports, ", ")
|
||||
}
|
||||
|
@ -1871,7 +1877,11 @@ func describeContainerPorts(cPorts []corev1.ContainerPort) string {
|
|||
func describeContainerHostPorts(cPorts []corev1.ContainerPort) string {
|
||||
ports := make([]string, 0, len(cPorts))
|
||||
for _, cPort := range cPorts {
|
||||
ports = append(ports, fmt.Sprintf("%d/%s", cPort.HostPort, cPort.Protocol))
|
||||
portStr := fmt.Sprintf("%d/%s", cPort.HostPort, cPort.Protocol)
|
||||
if cPort.Name != "" {
|
||||
portStr = fmt.Sprintf("%s (%s)", portStr, cPort.Name)
|
||||
}
|
||||
ports = append(ports, portStr)
|
||||
}
|
||||
return strings.Join(ports, ", ")
|
||||
}
|
||||
|
@ -2580,12 +2590,12 @@ func describeSecret(secret *corev1.Secret) (string, error) {
|
|||
w.Write(LEVEL_0, "\nType:\t%s\n", secret.Type)
|
||||
|
||||
w.Write(LEVEL_0, "\nData\n====\n")
|
||||
for k, v := range secret.Data {
|
||||
for _, k := range slices.Sorted(maps.Keys(secret.Data)) {
|
||||
switch {
|
||||
case k == corev1.ServiceAccountTokenKey && secret.Type == corev1.SecretTypeServiceAccountToken:
|
||||
w.Write(LEVEL_0, "%s:\t%s\n", k, string(v))
|
||||
w.Write(LEVEL_0, "%s:\t%s\n", k, string(secret.Data[k]))
|
||||
default:
|
||||
w.Write(LEVEL_0, "%s:\t%d bytes\n", k, len(v))
|
||||
w.Write(LEVEL_0, "%s:\t%d bytes\n", k, len(secret.Data[k]))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3155,6 +3165,9 @@ func describeService(service *corev1.Service, endpointSlices []discoveryv1.Endpo
|
|||
if len(service.Spec.LoadBalancerSourceRanges) > 0 {
|
||||
w.Write(LEVEL_0, "LoadBalancer Source Ranges:\t%v\n", strings.Join(service.Spec.LoadBalancerSourceRanges, ","))
|
||||
}
|
||||
if service.Spec.TrafficDistribution != nil {
|
||||
w.Write(LEVEL_0, "Traffic Distribution:\t%s\n", *service.Spec.TrafficDistribution)
|
||||
}
|
||||
if events != nil {
|
||||
DescribeEvents(events, w)
|
||||
}
|
||||
|
@ -4563,13 +4576,15 @@ func (d *ConfigMapDescriber) Describe(namespace, name string, describerSettings
|
|||
printAnnotationsMultiline(w, "Annotations", configMap.Annotations)
|
||||
|
||||
w.Write(LEVEL_0, "\nData\n====\n")
|
||||
for k, v := range configMap.Data {
|
||||
for _, k := range slices.Sorted(maps.Keys(configMap.Data)) {
|
||||
v := configMap.Data[k]
|
||||
w.Write(LEVEL_0, "%s:\n----\n", k)
|
||||
w.Write(LEVEL_0, "%s\n", string(v))
|
||||
w.Write(LEVEL_0, "\n")
|
||||
}
|
||||
w.Write(LEVEL_0, "\nBinaryData\n====\n")
|
||||
for k, v := range configMap.BinaryData {
|
||||
for _, k := range slices.Sorted(maps.Keys(configMap.BinaryData)) {
|
||||
v := configMap.BinaryData[k]
|
||||
w.Write(LEVEL_0, "%s: %s bytes\n", k, strconv.Itoa(len(v)))
|
||||
}
|
||||
w.Write(LEVEL_0, "\n")
|
||||
|
|
|
@ -19,6 +19,7 @@ package describe
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"maps"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -350,27 +351,79 @@ func TestDescribeTopologySpreadConstraints(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestDescribeSecret(t *testing.T) {
|
||||
fake := fake.NewSimpleClientset(&corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
testCases := []struct {
|
||||
description string
|
||||
data map[string][]byte // secret key -> secret in bytes
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
description: "alphabetical ordering",
|
||||
data: map[string][]byte{
|
||||
"username": []byte("YWRtaW4="),
|
||||
"password": []byte("MWYyZDFlMmU2N2Rm"),
|
||||
},
|
||||
expected: []string{"password", "username"},
|
||||
},
|
||||
Data: map[string][]byte{
|
||||
"username": []byte("YWRtaW4="),
|
||||
"password": []byte("MWYyZDFlMmU2N2Rm"),
|
||||
{
|
||||
description: "uppercase takes precedence",
|
||||
data: map[string][]byte{
|
||||
"text": []byte("a3ViZXJuZXRlcwo="),
|
||||
"Text": []byte("dGhpcyBpcyBhIHRlc3QK"),
|
||||
"tExt": []byte("d2VpcmQgY2FzaW5nCg=="),
|
||||
},
|
||||
expected: []string{"Text", "tExt", "text"},
|
||||
},
|
||||
{
|
||||
description: "numbers take precedence",
|
||||
data: map[string][]byte{
|
||||
"key_1": []byte("c29tZV9zZWNyZXQK"),
|
||||
"1_key": []byte("c29tZV90ZXh0Cg=="),
|
||||
},
|
||||
expected: []string{"1_key", "key_1"},
|
||||
},
|
||||
})
|
||||
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
|
||||
d := SecretDescriber{c}
|
||||
out, err := d.Describe("foo", "bar", DescriberSettings{})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !strings.Contains(out, "bar") || !strings.Contains(out, "foo") || !strings.Contains(out, "username") || !strings.Contains(out, "8 bytes") || !strings.Contains(out, "password") || !strings.Contains(out, "16 bytes") {
|
||||
t.Errorf("unexpected out: %s", out)
|
||||
}
|
||||
if strings.Contains(out, "YWRtaW4=") || strings.Contains(out, "MWYyZDFlMmU2N2Rm") {
|
||||
t.Errorf("sensitive data should not be shown, unexpected out: %s", out)
|
||||
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.description, func(t *testing.T) {
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Data: testCase.data,
|
||||
}
|
||||
fake := fake.NewSimpleClientset(secret)
|
||||
c := &describeClient{T: t, Namespace: "foo", Interface: fake}
|
||||
d := SecretDescriber{c}
|
||||
|
||||
out, err := d.Describe("foo", "bar", DescriberSettings{})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
for value := range maps.Values(testCase.data) {
|
||||
if strings.Contains(out, string(value)) {
|
||||
t.Errorf("sensitive data should not be shown, unexpected out: %s", out)
|
||||
}
|
||||
}
|
||||
|
||||
expectedOut := `Name: bar
|
||||
Namespace: foo
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
|
||||
Type:
|
||||
|
||||
Data
|
||||
====`
|
||||
|
||||
for _, expectedKey := range testCase.expected {
|
||||
expectedOut = fmt.Sprintf("%s\n%s: %d bytes", expectedOut, expectedKey, len(testCase.data[expectedKey]))
|
||||
}
|
||||
expectedOut = fmt.Sprintf("%s\n", expectedOut)
|
||||
|
||||
assert.Equal(t, expectedOut, out)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -660,6 +713,32 @@ func TestDescribeConfigMap(t *testing.T) {
|
|||
if !strings.Contains(out, "binarykey1") || !strings.Contains(out, "5 bytes") || !strings.Contains(out, "binarykey2") || !strings.Contains(out, "6 bytes") {
|
||||
t.Errorf("unexpected out: %s", out)
|
||||
}
|
||||
|
||||
expectedOut := `Name: mycm
|
||||
Namespace: foo
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
|
||||
Data
|
||||
====
|
||||
key1:
|
||||
----
|
||||
value1
|
||||
|
||||
key2:
|
||||
----
|
||||
value2
|
||||
|
||||
|
||||
BinaryData
|
||||
====
|
||||
binarykey1: 5 bytes
|
||||
binarykey2: 6 bytes
|
||||
|
||||
Events: <none>
|
||||
`
|
||||
|
||||
assert.Equal(t, expectedOut, out)
|
||||
}
|
||||
|
||||
func TestDescribeLimitRange(t *testing.T) {
|
||||
|
@ -728,6 +807,7 @@ func getResourceList(cpu, memory string) corev1.ResourceList {
|
|||
|
||||
func TestDescribeService(t *testing.T) {
|
||||
singleStack := corev1.IPFamilyPolicySingleStack
|
||||
preferClose := corev1.ServiceTrafficDistributionPreferClose
|
||||
testCases := []struct {
|
||||
name string
|
||||
service *corev1.Service
|
||||
|
@ -1086,6 +1166,54 @@ func TestDescribeService(t *testing.T) {
|
|||
Events: <none>
|
||||
`)[1:],
|
||||
},
|
||||
{
|
||||
name: "test-TrafficDistribution",
|
||||
service: &corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar",
|
||||
Namespace: "foo",
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeLoadBalancer,
|
||||
Ports: []corev1.ServicePort{{
|
||||
Name: "port-tcp",
|
||||
Port: 8080,
|
||||
Protocol: corev1.ProtocolTCP,
|
||||
TargetPort: intstr.FromString("targetPort"),
|
||||
NodePort: 31111,
|
||||
}},
|
||||
Selector: map[string]string{"blah": "heh"},
|
||||
ClusterIP: "1.2.3.4",
|
||||
IPFamilies: []corev1.IPFamily{corev1.IPv4Protocol},
|
||||
LoadBalancerIP: "5.6.7.8",
|
||||
SessionAffinity: corev1.ServiceAffinityNone,
|
||||
ExternalTrafficPolicy: corev1.ServiceExternalTrafficPolicyLocal,
|
||||
TrafficDistribution: &preferClose,
|
||||
HealthCheckNodePort: 32222,
|
||||
},
|
||||
},
|
||||
expected: dedent.Dedent(`
|
||||
Name: bar
|
||||
Namespace: foo
|
||||
Labels: <none>
|
||||
Annotations: <none>
|
||||
Selector: blah=heh
|
||||
Type: LoadBalancer
|
||||
IP Families: IPv4
|
||||
IP: 1.2.3.4
|
||||
IPs: <none>
|
||||
Desired LoadBalancer IP: 5.6.7.8
|
||||
Port: port-tcp 8080/TCP
|
||||
TargetPort: targetPort/TCP
|
||||
NodePort: port-tcp 31111/TCP
|
||||
Endpoints: <none>
|
||||
Session Affinity: None
|
||||
External Traffic Policy: Local
|
||||
HealthCheck NodePort: 32222
|
||||
Traffic Distribution: PreferClose
|
||||
Events: <none>
|
||||
`)[1:],
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
@ -1244,6 +1372,74 @@ func TestDescribeResources(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestDescribeContainerPorts(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
ports []corev1.ContainerPort
|
||||
expectedContainer string
|
||||
expectedHost string
|
||||
}{
|
||||
{
|
||||
name: "no ports",
|
||||
ports: []corev1.ContainerPort{},
|
||||
expectedContainer: "",
|
||||
expectedHost: "",
|
||||
},
|
||||
{
|
||||
name: "container and host port, with name",
|
||||
ports: []corev1.ContainerPort{
|
||||
{Name: "web", ContainerPort: 8080, HostPort: 8080, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
expectedContainer: "8080/TCP (web)",
|
||||
expectedHost: "8080/TCP (web)",
|
||||
},
|
||||
{
|
||||
name: "container and host port, no name",
|
||||
ports: []corev1.ContainerPort{
|
||||
{ContainerPort: 8080, HostPort: 8080, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
expectedContainer: "8080/TCP",
|
||||
expectedHost: "8080/TCP",
|
||||
},
|
||||
{
|
||||
name: "multiple ports with mixed configuration",
|
||||
ports: []corev1.ContainerPort{
|
||||
{Name: "controller", ContainerPort: 9093, HostPort: 9093, Protocol: corev1.ProtocolTCP},
|
||||
{ContainerPort: 9092, Protocol: corev1.ProtocolTCP},
|
||||
{Name: "interbroker", ContainerPort: 9094, HostPort: 9094, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
expectedContainer: "9093/TCP (controller), 9092/TCP, 9094/TCP (interbroker)",
|
||||
expectedHost: "9093/TCP (controller), 0/TCP, 9094/TCP (interbroker)",
|
||||
},
|
||||
{
|
||||
name: "all ports with mixed configuration",
|
||||
ports: []corev1.ContainerPort{
|
||||
{Name: "controller", ContainerPort: 9093, HostPort: 9093, Protocol: corev1.ProtocolTCP},
|
||||
{Name: "client", ContainerPort: 9092, HostPort: 9092, Protocol: corev1.ProtocolTCP},
|
||||
{Name: "interbroker", ContainerPort: 9094, HostPort: 9094, Protocol: corev1.ProtocolTCP},
|
||||
},
|
||||
expectedContainer: "9093/TCP (controller), 9092/TCP (client), 9094/TCP (interbroker)",
|
||||
expectedHost: "9093/TCP (controller), 9092/TCP (client), 9094/TCP (interbroker)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name+" - container ports", func(t *testing.T) {
|
||||
result := describeContainerPorts(tc.ports)
|
||||
if result != tc.expectedContainer {
|
||||
t.Errorf("describeContainerPorts: expected %q, got %q", tc.expectedContainer, result)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run(tc.name+" - host ports", func(t *testing.T) {
|
||||
result := describeContainerHostPorts(tc.ports)
|
||||
if result != tc.expectedHost {
|
||||
t.Errorf("describeContainerHostPorts: expected %q, got %q", tc.expectedHost, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDescribeContainers(t *testing.T) {
|
||||
trueVal := true
|
||||
testCases := []struct {
|
||||
|
|
|
@ -171,6 +171,12 @@ func hasLocalStorage(pod corev1.Pod) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func isControllerRefDaemonSet(workloadRef *metav1.OwnerReference) bool {
|
||||
// find if workloadRef is daemonSet
|
||||
daemonSetAPIVersion, daemonSetKind := appsv1.SchemeGroupVersion.WithKind("DaemonSet").ToAPIVersionAndKind()
|
||||
return workloadRef.Kind == daemonSetKind && workloadRef.APIVersion == daemonSetAPIVersion
|
||||
}
|
||||
|
||||
func (d *Helper) daemonSetFilter(pod corev1.Pod) PodDeleteStatus {
|
||||
// Note that we return false in cases where the pod is DaemonSet managed,
|
||||
// regardless of flags.
|
||||
|
@ -179,7 +185,7 @@ func (d *Helper) daemonSetFilter(pod corev1.Pod) PodDeleteStatus {
|
|||
// management resource - including DaemonSet - is not found).
|
||||
// Such pods will be deleted if --force is used.
|
||||
controllerRef := metav1.GetControllerOf(&pod)
|
||||
if controllerRef == nil || controllerRef.Kind != appsv1.SchemeGroupVersion.WithKind("DaemonSet").Kind {
|
||||
if controllerRef == nil || !isControllerRefDaemonSet(controllerRef) {
|
||||
return MakePodDeleteStatusOkay()
|
||||
}
|
||||
// Any finished pod can be removed.
|
||||
|
|
|
@ -24,16 +24,13 @@ import (
|
|||
"regexp"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubectl/pkg/config"
|
||||
kuberc "k8s.io/kubectl/pkg/config/install"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
"k8s.io/client-go/util/homedir"
|
||||
"k8s.io/kubectl/pkg/config"
|
||||
)
|
||||
|
||||
const RecommendedKubeRCFileName = "kuberc"
|
||||
|
@ -44,16 +41,8 @@ var (
|
|||
|
||||
aliasNameRegex = regexp.MustCompile("^[a-zA-Z]+$")
|
||||
shortHandRegex = regexp.MustCompile("^-[a-zA-Z]+$")
|
||||
|
||||
scheme = runtime.NewScheme()
|
||||
strictCodecs = serializer.NewCodecFactory(scheme, serializer.EnableStrict)
|
||||
lenientCodecs = serializer.NewCodecFactory(scheme, serializer.DisableStrict)
|
||||
)
|
||||
|
||||
func init() {
|
||||
kuberc.Install(scheme)
|
||||
}
|
||||
|
||||
// PreferencesHandler is responsible for setting default flags
|
||||
// arguments based on user's kuberc configuration.
|
||||
type PreferencesHandler interface {
|
||||
|
@ -80,13 +69,13 @@ func NewPreferences() PreferencesHandler {
|
|||
type aliasing struct {
|
||||
appendArgs []string
|
||||
prependArgs []string
|
||||
flags []config.CommandOverrideFlag
|
||||
flags []config.CommandOptionDefault
|
||||
command *cobra.Command
|
||||
}
|
||||
|
||||
// AddFlags adds kuberc related flags into the command.
|
||||
func (p *Preferences) AddFlags(flags *pflag.FlagSet) {
|
||||
flags.String("kuberc", "", "Path to the kuberc file to use for preferences. This can be disabled by exporting KUBECTL_KUBERC=false.")
|
||||
flags.String("kuberc", "", "Path to the kuberc file to use for preferences. This can be disabled by exporting KUBECTL_KUBERC=false feature gate or turning off the feature KUBERC=off.")
|
||||
}
|
||||
|
||||
// Apply firstly applies the aliases in the preferences file and secondly overrides
|
||||
|
@ -133,7 +122,7 @@ func (p *Preferences) applyOverrides(rootCmd *cobra.Command, kuberc *config.Pref
|
|||
return nil
|
||||
}
|
||||
|
||||
for _, c := range kuberc.Overrides {
|
||||
for _, c := range kuberc.Defaults {
|
||||
parsedCmds := strings.Fields(c.Command)
|
||||
overrideCmd, _, err := rootCmd.Find(parsedCmds)
|
||||
if err != nil {
|
||||
|
@ -158,7 +147,7 @@ func (p *Preferences) applyOverrides(rootCmd *cobra.Command, kuberc *config.Pref
|
|||
}
|
||||
})
|
||||
|
||||
for _, fl := range c.Flags {
|
||||
for _, fl := range c.Options {
|
||||
existingFlag := cmd.Flag(fl.Name)
|
||||
if existingFlag == nil {
|
||||
return fmt.Errorf("invalid flag %s for command %s", fl.Name, c.Command)
|
||||
|
@ -194,7 +183,7 @@ func (p *Preferences) applyAliases(rootCmd *cobra.Command, kuberc *config.Prefer
|
|||
var commandName string // first "non-flag" arguments
|
||||
var commandIndex int
|
||||
for index, arg := range args[1:] {
|
||||
if !strings.HasPrefix(arg, "-") {
|
||||
if !strings.HasPrefix(arg, "-") && !strings.HasPrefix(arg, cobra.ShellCompRequestCmd) {
|
||||
commandName = arg
|
||||
commandIndex = index + 1
|
||||
break
|
||||
|
@ -227,7 +216,7 @@ func (p *Preferences) applyAliases(rootCmd *cobra.Command, kuberc *config.Prefer
|
|||
aliasArgs = &aliasing{
|
||||
prependArgs: alias.PrependArgs,
|
||||
appendArgs: alias.AppendArgs,
|
||||
flags: alias.Flags,
|
||||
flags: alias.Options,
|
||||
command: aliasCmd,
|
||||
}
|
||||
break
|
||||
|
@ -318,9 +307,9 @@ func DefaultGetPreferences(kuberc string, errOut io.Writer) (*config.Preference,
|
|||
|
||||
preference, err := decodePreference(kubeRCFile)
|
||||
switch {
|
||||
case explicitly && preference != nil && runtime.IsStrictDecodingError(err):
|
||||
// if explicitly requested, just warn about strict decoding errors if we got a usable Preference object back
|
||||
fmt.Fprintf(errOut, "kuberc: ignoring strict decoding error in %s: %v", kubeRCFile, err)
|
||||
case preference != nil && runtime.IsStrictDecodingError(err):
|
||||
// just warn about strict decoding errors if we got a usable Preference object back
|
||||
fmt.Fprintf(errOut, "kuberc: ignoring strict decoding error in %s: %v", kubeRCFile, err) //nolint:errcheck
|
||||
return preference, nil
|
||||
|
||||
case explicitly && err != nil:
|
||||
|
@ -333,7 +322,7 @@ func DefaultGetPreferences(kuberc string, errOut io.Writer) (*config.Preference,
|
|||
|
||||
case !explicitly && err != nil:
|
||||
// if not explicitly requested, only warn on any other error
|
||||
fmt.Fprintf(errOut, "kuberc: no preferences loaded from %s: %v", kubeRCFile, err)
|
||||
fmt.Fprintf(errOut, "kuberc: no preferences loaded from %s: %v", kubeRCFile, err) //nolint:errcheck
|
||||
return nil, nil
|
||||
|
||||
default:
|
||||
|
@ -424,7 +413,7 @@ func searchInArgs(flagName string, shorthand string, allShorthands map[string]st
|
|||
}
|
||||
|
||||
func validate(plugin *config.Preference) error {
|
||||
validateFlag := func(flags []config.CommandOverrideFlag) error {
|
||||
validateFlag := func(flags []config.CommandOptionDefault) error {
|
||||
for _, flag := range flags {
|
||||
if strings.HasPrefix(flag.Name, "-") {
|
||||
return fmt.Errorf("flag name %s should be in long form without dashes", flag.Name)
|
||||
|
@ -438,7 +427,7 @@ func validate(plugin *config.Preference) error {
|
|||
return fmt.Errorf("invalid alias name, can only include alphabetical characters")
|
||||
}
|
||||
|
||||
if err := validateFlag(alias.Flags); err != nil {
|
||||
if err := validateFlag(alias.Options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -448,8 +437,8 @@ func validate(plugin *config.Preference) error {
|
|||
aliases[alias.Name] = struct{}{}
|
||||
}
|
||||
|
||||
for _, override := range plugin.Overrides {
|
||||
if err := validateFlag(override.Flags); err != nil {
|
||||
for _, override := range plugin.Defaults {
|
||||
if err := validateFlag(override.Options); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -24,12 +24,11 @@ import (
|
|||
"io"
|
||||
"os"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubectl/pkg/config"
|
||||
"k8s.io/kubectl/pkg/config/scheme"
|
||||
)
|
||||
|
||||
// decodePreference iterates over the yamls in kuberc file to find the supported kuberc version.
|
||||
|
@ -58,10 +57,10 @@ func decodePreference(kubercFile string) (*config.Preference, error) {
|
|||
}
|
||||
// remember we attempted
|
||||
attemptedItems++
|
||||
pref, gvk, strictDecodeErr := strictCodecs.UniversalDecoder().Decode(doc, nil, nil)
|
||||
pref, gvk, strictDecodeErr := scheme.StrictCodecs.UniversalDecoder().Decode(doc, nil, nil)
|
||||
if strictDecodeErr != nil {
|
||||
var lenientDecodeErr error
|
||||
pref, gvk, lenientDecodeErr = lenientCodecs.UniversalDecoder().Decode(doc, nil, nil)
|
||||
pref, gvk, lenientDecodeErr = scheme.LenientCodecs.UniversalDecoder().Decode(doc, nil, nil)
|
||||
if lenientDecodeErr != nil {
|
||||
// both strict and lenient failed
|
||||
// verbose log the error with the most information about this item and continue
|
||||
|
@ -88,13 +87,14 @@ func decodePreference(kubercFile string) (*config.Preference, error) {
|
|||
}
|
||||
|
||||
// we have a usable preferences to return
|
||||
klog.V(5).Infof("kuberc: successfully decoded entry %d in %s", attemptedItems, kubercFile)
|
||||
klog.V(5).Infof("kuberc: using entry %d (%s) in %s", attemptedItems, gvk.GroupVersion(), kubercFile)
|
||||
return preferences, strictDecodeErr
|
||||
|
||||
}
|
||||
|
||||
if attemptedItems > 0 {
|
||||
return nil, fmt.Errorf("no valid preferences found in %s, use --v=5 to see details", kubercFile)
|
||||
}
|
||||
|
||||
// empty doc
|
||||
klog.V(5).Infof("kuberc: no preferences found in %s", kubercFile)
|
||||
return nil, nil
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
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 kuberc
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDecodePreference(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
kuberc string
|
||||
expectedAliases []string
|
||||
expectedDefaults []string
|
||||
expectedError string
|
||||
}{
|
||||
"v1alpha1": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "v1alpha1.kuberc"),
|
||||
expectedDefaults: []string{"v1alpha1-apply", "v1alpha1-delete"},
|
||||
},
|
||||
"v1beta1": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "v1beta1.kuberc"),
|
||||
expectedDefaults: []string{"v1beta1-apply", "v1beta1-delete"},
|
||||
},
|
||||
"first known version (v1beta1) with all versions": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "allversions.kuberc"),
|
||||
expectedAliases: []string{"getn", "runx"},
|
||||
expectedDefaults: []string{"v1beta1-apply", "v1beta1-delete"},
|
||||
},
|
||||
"first known (v1beta1) with multiple versions (unknown, v1beta1, v1alpha1)": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple1.kuberc"),
|
||||
expectedDefaults: []string{"v1beta1-apply", "v1beta1-delete"},
|
||||
},
|
||||
"first known (v1beta1) with multiple versions (unknown, v1beta1, v1beta1, v1alpha1)": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple2.kuberc"),
|
||||
expectedDefaults: []string{"v1beta1-apply-first", "v1beta1-delete-first"},
|
||||
},
|
||||
"first known older (v1alpha1) with multiple versions (unknown, v1alpha1)": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple3.kuberc"),
|
||||
expectedDefaults: []string{"v1alpha1-apply-first", "v1alpha1-delete-first"},
|
||||
},
|
||||
"first v1alpha1 with multiple versions (unknown, v1alpha1, v1beta1)": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "multiple4.kuberc"),
|
||||
expectedDefaults: []string{"v1alpha1-apply-first", "v1alpha1-delete-first"},
|
||||
},
|
||||
"single unknown version": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "unknown.kuberc"),
|
||||
expectedError: "no valid preferences found",
|
||||
},
|
||||
"multiple unknown version": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "unknown.kuberc"),
|
||||
expectedError: "no valid preferences found",
|
||||
},
|
||||
"non-existent file": {
|
||||
kuberc: filepath.Join("..", "..", "testdata", "kuberc", "non-existent"),
|
||||
expectedError: "no such file or directory",
|
||||
},
|
||||
}
|
||||
|
||||
for name, tc := range testCases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
actual, err := decodePreference(tc.kuberc)
|
||||
if len(tc.expectedError) != 0 {
|
||||
require.ErrorContains(t, err, tc.expectedError, "wrong expected error")
|
||||
return
|
||||
}
|
||||
require.NoError(t, err, "unexpected error")
|
||||
require.NotNil(t, actual, "missing preferences when decoding")
|
||||
defaults := []string{}
|
||||
for _, o := range actual.Defaults {
|
||||
defaults = append(defaults, o.Command)
|
||||
}
|
||||
require.ElementsMatch(t, defaults, tc.expectedDefaults, "defaults mismatch")
|
||||
aliases := []string{}
|
||||
for _, o := range actual.Aliases {
|
||||
aliases = append(aliases, o.Name)
|
||||
}
|
||||
require.ElementsMatch(t, aliases, tc.expectedAliases, "aliases mismatch")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodeEmptyPreference(t *testing.T) {
|
||||
actual, err := decodePreference(filepath.Join("..", "..", "testdata", "kuberc", "empty.kuberc"))
|
||||
require.NoError(t, err, "unexpected error")
|
||||
require.Nil(t, actual, "unexpected preferences")
|
||||
}
|
|
@ -19,6 +19,7 @@ package metricsutil
|
|||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"slices"
|
||||
"sort"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
|
@ -28,16 +29,12 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
MeasuredResources = []v1.ResourceName{
|
||||
v1.ResourceCPU,
|
||||
v1.ResourceMemory,
|
||||
}
|
||||
NodeColumns = []string{"NAME", "CPU(cores)", "CPU(%)", "MEMORY(bytes)", "MEMORY(%)"}
|
||||
PodColumns = []string{"NAME", "CPU(cores)", "MEMORY(bytes)"}
|
||||
NamespaceColumn = "NAMESPACE"
|
||||
PodColumn = "POD"
|
||||
)
|
||||
|
||||
const ResourceSwap = "swap"
|
||||
|
||||
type ResourceMetricsInfo struct {
|
||||
Name string
|
||||
Metrics v1.ResourceList
|
||||
|
@ -45,11 +42,33 @@ type ResourceMetricsInfo struct {
|
|||
}
|
||||
|
||||
type TopCmdPrinter struct {
|
||||
out io.Writer
|
||||
out io.Writer
|
||||
measuredResources []v1.ResourceName
|
||||
nodeColumns []string
|
||||
podColumns []string
|
||||
// a map from a node name to its missing resources
|
||||
nodesMissingResources map[string][]string
|
||||
}
|
||||
|
||||
func NewTopCmdPrinter(out io.Writer) *TopCmdPrinter {
|
||||
return &TopCmdPrinter{out: out}
|
||||
func NewTopCmdPrinter(out io.Writer, showSwap bool) *TopCmdPrinter {
|
||||
printer := &TopCmdPrinter{
|
||||
out: out,
|
||||
measuredResources: []v1.ResourceName{
|
||||
v1.ResourceCPU,
|
||||
v1.ResourceMemory,
|
||||
},
|
||||
nodeColumns: []string{"NAME", "CPU(cores)", "CPU(%)", "MEMORY(bytes)", "MEMORY(%)"},
|
||||
podColumns: []string{"NAME", "CPU(cores)", "MEMORY(bytes)"},
|
||||
nodesMissingResources: make(map[string][]string),
|
||||
}
|
||||
|
||||
if showSwap {
|
||||
printer.measuredResources = append(printer.measuredResources, ResourceSwap)
|
||||
printer.nodeColumns = append(printer.nodeColumns, "SWAP(bytes)", "SWAP(%)")
|
||||
printer.podColumns = append(printer.podColumns, "SWAP(bytes)")
|
||||
}
|
||||
|
||||
return printer
|
||||
}
|
||||
|
||||
func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics, availableResources map[string]v1.ResourceList, noHeaders bool, sortBy string) error {
|
||||
|
@ -59,25 +78,26 @@ func (printer *TopCmdPrinter) PrintNodeMetrics(metrics []metricsapi.NodeMetrics,
|
|||
w := printers.GetNewTabWriter(printer.out)
|
||||
defer w.Flush()
|
||||
|
||||
measuredResources := printer.measuredResources
|
||||
sort.Sort(NewNodeMetricsSorter(metrics, sortBy))
|
||||
|
||||
if !noHeaders {
|
||||
printColumnNames(w, NodeColumns)
|
||||
printColumnNames(w, printer.nodeColumns)
|
||||
}
|
||||
var usage v1.ResourceList
|
||||
for _, m := range metrics {
|
||||
m.Usage.DeepCopyInto(&usage)
|
||||
printMetricsLine(w, &ResourceMetricsInfo{
|
||||
printer.printMetricsLine(w, &ResourceMetricsInfo{
|
||||
Name: m.Name,
|
||||
Metrics: usage,
|
||||
Available: availableResources[m.Name],
|
||||
})
|
||||
}, measuredResources)
|
||||
delete(availableResources, m.Name)
|
||||
}
|
||||
|
||||
// print lines for nodes of which the metrics is unreachable.
|
||||
for nodeName := range availableResources {
|
||||
printMissingMetricsNodeLine(w, nodeName)
|
||||
printer.printMissingMetricsNodeLine(w, nodeName, measuredResources)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -89,7 +109,7 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, p
|
|||
w := printers.GetNewTabWriter(printer.out)
|
||||
defer w.Flush()
|
||||
|
||||
columnWidth := len(PodColumns)
|
||||
columnWidth := len(printer.podColumns)
|
||||
if !noHeaders {
|
||||
if withNamespace {
|
||||
printValue(w, NamespaceColumn)
|
||||
|
@ -99,32 +119,39 @@ func (printer *TopCmdPrinter) PrintPodMetrics(metrics []metricsapi.PodMetrics, p
|
|||
printValue(w, PodColumn)
|
||||
columnWidth++
|
||||
}
|
||||
printColumnNames(w, PodColumns)
|
||||
printColumnNames(w, printer.podColumns)
|
||||
}
|
||||
|
||||
sort.Sort(NewPodMetricsSorter(metrics, withNamespace, sortBy))
|
||||
sort.Sort(NewPodMetricsSorter(metrics, withNamespace, sortBy, printer.measuredResources))
|
||||
|
||||
for _, m := range metrics {
|
||||
if printContainers {
|
||||
sort.Sort(NewContainerMetricsSorter(m.Containers, sortBy))
|
||||
printSinglePodContainerMetrics(w, &m, withNamespace)
|
||||
printer.printSinglePodContainerMetrics(w, &m, withNamespace, printer.measuredResources)
|
||||
} else {
|
||||
printSinglePodMetrics(w, &m, withNamespace)
|
||||
printer.printSinglePodMetrics(w, &m, withNamespace, printer.measuredResources)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if sum {
|
||||
adder := NewResourceAdder(MeasuredResources)
|
||||
adder := NewResourceAdder(printer.measuredResources)
|
||||
for _, m := range metrics {
|
||||
adder.AddPodMetrics(&m)
|
||||
}
|
||||
printPodResourcesSum(w, adder.total, columnWidth)
|
||||
printer.printPodResourcesSum(w, adder.total, columnWidth, printer.measuredResources)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (printer *TopCmdPrinter) RegisterMissingResource(nodeName, resourceName string) {
|
||||
if slices.Contains(printer.nodesMissingResources[nodeName], resourceName) {
|
||||
return
|
||||
}
|
||||
printer.nodesMissingResources[nodeName] = append(printer.nodesMissingResources[nodeName], resourceName)
|
||||
}
|
||||
|
||||
func printColumnNames(out io.Writer, names []string) {
|
||||
for _, name := range names {
|
||||
printValue(out, name)
|
||||
|
@ -132,40 +159,40 @@ func printColumnNames(out io.Writer, names []string) {
|
|||
fmt.Fprint(out, "\n")
|
||||
}
|
||||
|
||||
func printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
|
||||
podMetrics := getPodMetrics(m)
|
||||
func (printer *TopCmdPrinter) printSinglePodMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool, measuredResources []v1.ResourceName) {
|
||||
podMetrics := getPodMetrics(m, measuredResources)
|
||||
if withNamespace {
|
||||
printValue(out, m.Namespace)
|
||||
}
|
||||
printMetricsLine(out, &ResourceMetricsInfo{
|
||||
printer.printMetricsLine(out, &ResourceMetricsInfo{
|
||||
Name: m.Name,
|
||||
Metrics: podMetrics,
|
||||
Available: v1.ResourceList{},
|
||||
})
|
||||
}, measuredResources)
|
||||
}
|
||||
|
||||
func printSinglePodContainerMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool) {
|
||||
func (printer *TopCmdPrinter) printSinglePodContainerMetrics(out io.Writer, m *metricsapi.PodMetrics, withNamespace bool, measuredResources []v1.ResourceName) {
|
||||
for _, c := range m.Containers {
|
||||
if withNamespace {
|
||||
printValue(out, m.Namespace)
|
||||
}
|
||||
printValue(out, m.Name)
|
||||
printMetricsLine(out, &ResourceMetricsInfo{
|
||||
printer.printMetricsLine(out, &ResourceMetricsInfo{
|
||||
Name: c.Name,
|
||||
Metrics: c.Usage,
|
||||
Available: v1.ResourceList{},
|
||||
})
|
||||
}, measuredResources)
|
||||
}
|
||||
}
|
||||
|
||||
func getPodMetrics(m *metricsapi.PodMetrics) v1.ResourceList {
|
||||
func getPodMetrics(m *metricsapi.PodMetrics, measuredResources []v1.ResourceName) v1.ResourceList {
|
||||
podMetrics := make(v1.ResourceList)
|
||||
for _, res := range MeasuredResources {
|
||||
for _, res := range measuredResources {
|
||||
podMetrics[res], _ = resource.ParseQuantity("0")
|
||||
}
|
||||
|
||||
for _, c := range m.Containers {
|
||||
for _, res := range MeasuredResources {
|
||||
for _, res := range measuredResources {
|
||||
quantity := podMetrics[res]
|
||||
quantity.Add(c.Usage[res])
|
||||
podMetrics[res] = quantity
|
||||
|
@ -174,16 +201,16 @@ func getPodMetrics(m *metricsapi.PodMetrics) v1.ResourceList {
|
|||
return podMetrics
|
||||
}
|
||||
|
||||
func printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo) {
|
||||
func (printer *TopCmdPrinter) printMetricsLine(out io.Writer, metrics *ResourceMetricsInfo, measuredResources []v1.ResourceName) {
|
||||
printValue(out, metrics.Name)
|
||||
printAllResourceUsages(out, metrics)
|
||||
printer.printAllResourceUsages(out, metrics, measuredResources)
|
||||
fmt.Fprint(out, "\n")
|
||||
}
|
||||
|
||||
func printMissingMetricsNodeLine(out io.Writer, nodeName string) {
|
||||
func (printer *TopCmdPrinter) printMissingMetricsNodeLine(out io.Writer, nodeName string, measuredResources []v1.ResourceName) {
|
||||
printValue(out, nodeName)
|
||||
unknownMetricsStatus := "<unknown>"
|
||||
for i := 0; i < len(MeasuredResources); i++ {
|
||||
for i := 0; i < len(measuredResources); i++ {
|
||||
printValue(out, unknownMetricsStatus)
|
||||
printValue(out, unknownMetricsStatus)
|
||||
}
|
||||
|
@ -194,13 +221,21 @@ func printValue(out io.Writer, value interface{}) {
|
|||
fmt.Fprintf(out, "%v\t", value)
|
||||
}
|
||||
|
||||
func printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo) {
|
||||
for _, res := range MeasuredResources {
|
||||
func (printer *TopCmdPrinter) printAllResourceUsages(out io.Writer, metrics *ResourceMetricsInfo, measuredResources []v1.ResourceName) {
|
||||
for _, res := range measuredResources {
|
||||
if missingResources, found := printer.nodesMissingResources[metrics.Name]; found && slices.Contains(missingResources, string(res)) {
|
||||
printSingleMissingResource(out)
|
||||
continue
|
||||
}
|
||||
|
||||
quantity := metrics.Metrics[res]
|
||||
printSingleResourceUsage(out, res, quantity)
|
||||
fmt.Fprint(out, "\t")
|
||||
if available, found := metrics.Available[res]; found {
|
||||
fraction := float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
|
||||
fraction := 0.0
|
||||
if !available.IsZero() {
|
||||
fraction = float64(quantity.MilliValue()) / float64(available.MilliValue()) * 100
|
||||
}
|
||||
fmt.Fprintf(out, "%d%%\t", int64(fraction))
|
||||
}
|
||||
}
|
||||
|
@ -210,14 +245,19 @@ func printSingleResourceUsage(out io.Writer, resourceType v1.ResourceName, quant
|
|||
switch resourceType {
|
||||
case v1.ResourceCPU:
|
||||
fmt.Fprintf(out, "%vm", quantity.MilliValue())
|
||||
case v1.ResourceMemory:
|
||||
case v1.ResourceMemory, ResourceSwap:
|
||||
fmt.Fprintf(out, "%vMi", quantity.Value()/(1024*1024))
|
||||
default:
|
||||
fmt.Fprintf(out, "%v", quantity.Value())
|
||||
}
|
||||
}
|
||||
|
||||
func printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int) {
|
||||
func printSingleMissingResource(out io.Writer) {
|
||||
const unavailableStr = "<unknown>"
|
||||
_, _ = fmt.Fprintf(out, "%s\t%s\t", unavailableStr, unavailableStr)
|
||||
}
|
||||
|
||||
func (printer *TopCmdPrinter) printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int, measuredResources []v1.ResourceName) {
|
||||
for i := 0; i < columnWidth-2; i++ {
|
||||
printValue(out, "")
|
||||
}
|
||||
|
@ -227,10 +267,10 @@ func printPodResourcesSum(out io.Writer, total v1.ResourceList, columnWidth int)
|
|||
for i := 0; i < columnWidth-3; i++ {
|
||||
printValue(out, "")
|
||||
}
|
||||
printMetricsLine(out, &ResourceMetricsInfo{
|
||||
printer.printMetricsLine(out, &ResourceMetricsInfo{
|
||||
Name: "",
|
||||
Metrics: total,
|
||||
Available: v1.ResourceList{},
|
||||
})
|
||||
}, measuredResources)
|
||||
|
||||
}
|
||||
|
|
|
@ -130,7 +130,7 @@ node1 1000m 10% 1024Mi 10%
|
|||
for _, n := range test.nodes {
|
||||
availableResources[n.Name] = n.Status.Capacity
|
||||
}
|
||||
top := NewTopCmdPrinter(out)
|
||||
top := NewTopCmdPrinter(out, false)
|
||||
err := top.PrintNodeMetrics(test.nodeMetric, availableResources, test.noHeader, test.sortBy)
|
||||
assert.Equal(t, test.expectedErr, err)
|
||||
assert.Equal(t, test.expectedOutput, out.String())
|
||||
|
@ -374,7 +374,7 @@ test-1 400m 5120Mi
|
|||
// Create a new TopCmdPrinter with a test writer.
|
||||
_, _, out, _ := genericiooptions.NewTestIOStreams()
|
||||
|
||||
top := NewTopCmdPrinter(out)
|
||||
top := NewTopCmdPrinter(out, false)
|
||||
err := top.PrintPodMetrics(test.podMetric, test.printContainers,
|
||||
test.withNamespace, test.noHeader, test.sortBy, test.sum)
|
||||
assert.Equal(t, test.expectedErr, err)
|
||||
|
|
|
@ -82,11 +82,11 @@ func (p *PodMetricsSorter) Less(i, j int) bool {
|
|||
}
|
||||
}
|
||||
|
||||
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string) *PodMetricsSorter {
|
||||
func NewPodMetricsSorter(metrics []metricsapi.PodMetrics, withNamespace bool, sortBy string, measuredResources []v1.ResourceName) *PodMetricsSorter {
|
||||
var podMetrics = make([]v1.ResourceList, len(metrics))
|
||||
if len(sortBy) > 0 {
|
||||
for i, v := range metrics {
|
||||
podMetrics[i] = getPodMetrics(&v)
|
||||
podMetrics[i] = getPodMetrics(&v, measuredResources)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,7 @@ func TestGetDeploymentPatch(t *testing.T) {
|
|||
t.Errorf("expected strategic merge patch, got %v", patchType)
|
||||
}
|
||||
expectedPatch := `[` +
|
||||
`{"op":"replace","path":"/spec/template","value":{"metadata":{"creationTimestamp":null},"spec":{"containers":[{"name":"","image":"foo","resources":{}}]}}},` +
|
||||
`{"op":"replace","path":"/spec/template","value":{"metadata":{},"spec":{"containers":[{"name":"","image":"foo","resources":{}}]}}},` +
|
||||
`{"op":"replace","path":"/metadata/annotations","value":{"a":"true"}}` +
|
||||
`]`
|
||||
if string(patchBytes) != expectedPatch {
|
||||
|
|
|
@ -154,7 +154,6 @@ func LoadTranslations(root string, getLanguageFn func() string) error {
|
|||
fmt.Sprintf("%s/%s/LC_MESSAGES/k8s.mo", root, langStr),
|
||||
}
|
||||
|
||||
klog.V(3).Infof("Setting language to %s", langStr)
|
||||
// TODO: list the directory and load all files.
|
||||
buf := new(bytes.Buffer)
|
||||
w := zip.NewWriter(buf)
|
||||
|
|
|
@ -3,7 +3,7 @@ kind: Deployment
|
|||
metadata:
|
||||
annotations:
|
||||
deployment.kubernetes.io/revision: "1"
|
||||
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"nginx-deployment","creationTimestamp":null,"labels":{"name":"nginx"}},"spec":{"selector":{"matchLabels":{"name":"nginx"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"name":"nginx"}},"spec":{"containers":[{"name":"nginx","image":"nginx","resources":{}}]}},"strategy":{}},"status":{}}'
|
||||
kubectl.kubernetes.io/last-applied-configuration: '{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"nginx-deployment","labels":{"name":"nginx"}},"spec":{"selector":{"matchLabels":{"name":"nginx"}},"template":{"metadata":{"labels":{"name":"nginx"}},"spec":{"containers":[{"name":"nginx","image":"nginx","resources":{}}]}},"strategy":{}},"status":{}}'
|
||||
creationTimestamp: "2016-10-24T22:15:06Z"
|
||||
generation: 6
|
||||
labels:
|
||||
|
@ -25,7 +25,6 @@ spec:
|
|||
type: RollingUpdate
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
name: nginx
|
||||
spec:
|
||||
|
|
|
@ -77,7 +77,6 @@ spec:
|
|||
name: test-rc
|
||||
template:
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
labels:
|
||||
name: test-rc
|
||||
spec:
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1beta1
|
||||
kind: Preference
|
||||
aliases:
|
||||
- name: getn
|
||||
command: get
|
||||
prependArgs:
|
||||
- namespace
|
||||
- name: runx
|
||||
command: run
|
||||
appendArgs:
|
||||
- --
|
||||
- custom-arg
|
||||
defaults:
|
||||
- command: v1beta1-apply
|
||||
options:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1beta1-delete
|
||||
options:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1unknown1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1unknown1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1unknown1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1beta1
|
||||
kind: Preference
|
||||
defaults:
|
||||
- command: v1beta1-apply
|
||||
options:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1beta1-delete
|
||||
options:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1unknown1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1unknown1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1unknown1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1beta1
|
||||
kind: Preference
|
||||
defaults:
|
||||
- command: v1beta1-apply-first
|
||||
options:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1beta1-delete-first
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1beta1
|
||||
kind: Preference
|
||||
defaults:
|
||||
- command: v1beta1-apply-second
|
||||
options:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1beta1-delete-second
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
|
@ -0,0 +1,36 @@
|
|||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1unknown1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1unknown1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1unknown1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply-first
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete-first
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply-second
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete-second
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
|
@ -0,0 +1,45 @@
|
|||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1unknown1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1unknown1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1unknown1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply-first
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete-first
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply-second
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete-second
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1beta1
|
||||
kind: Preference
|
||||
defaults:
|
||||
- command: v1beta1-apply-first
|
||||
options:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1beta1-delete-first
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: kubectl.config.k8s.io/v1unknown1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1unknown1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1unknown1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
|
@ -0,0 +1,24 @@
|
|||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1unknown1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1unknown1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1unknown1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
||||
---
|
||||
apiVersion: kubectl.config.k8s.io/v1unknown2
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1unknown2-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1unknown2-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: kubectl.config.k8s.io/v1alpha1
|
||||
kind: Preference
|
||||
overrides:
|
||||
- command: v1alpha1-apply
|
||||
flags:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1alpha1-delete
|
||||
flags:
|
||||
- name: interactive
|
||||
default: "true"
|
|
@ -0,0 +1,11 @@
|
|||
apiVersion: kubectl.config.k8s.io/v1beta1
|
||||
kind: Preference
|
||||
defaults:
|
||||
- command: v1beta1-apply
|
||||
options:
|
||||
- name: server-side
|
||||
default: "true"
|
||||
- command: v1beta1-delete
|
||||
options:
|
||||
- name: interactive
|
||||
default: "true"
|
Loading…
Reference in New Issue